@zenit-hosting/zenit-ui 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/README.md +400 -0
- package/fesm2022/zenit-hosting-zenit-ui.mjs +11289 -0
- package/fesm2022/zenit-hosting-zenit-ui.mjs.map +1 -0
- package/llms-full.txt +5120 -0
- package/llms.txt +179 -0
- package/package.json +44 -0
- package/schematics/collection.json +15 -0
- package/schematics/migrate-material/component.js +241 -0
- package/schematics/migrate-material/index.js +463 -0
- package/schematics/migrate-material/report.js +125 -0
- package/schematics/migrate-material/schema.js +2 -0
- package/schematics/migrate-material/schema.json +32 -0
- package/schematics/migrate-material/tables.js +138 -0
- package/schematics/migrate-material/template.js +871 -0
- package/schematics/ng-add/html.js +151 -0
- package/schematics/ng-add/index.js +609 -0
- package/schematics/ng-add/init-script.js +108 -0
- package/schematics/ng-add/schema.js +2 -0
- package/schematics/ng-add/schema.json +34 -0
- package/schematics/package.json +3 -0
- package/styles/_daten.css +138 -0
- package/styles/_formulare.css +97 -0
- package/styles/_grundlage.css +424 -0
- package/styles/_konfigurator.css +216 -0
- package/styles/_navigation.css +179 -0
- package/styles/_overlays.css +86 -0
- package/styles/_rueckmeldung.css +100 -0
- package/styles/_werkzeuge.css +119 -0
- package/styles/themes/accents.css +91 -0
- package/styles/themes/base.css +26 -0
- package/styles/themes/contrast.css +67 -0
- package/styles/themes/light.css +74 -0
- package/styles/themes.css +21 -0
- package/styles/tokens.css +76 -0
- package/styles/zenit-ui.css +12 -0
- package/types/zenit-hosting-zenit-ui.d.ts +5957 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/* Package navigation: Tabs, Sidebar, AppHeader, PageHeader, Stepper, Footer.
|
|
2
|
+
Verbatim from spec/components/bundle.css. The transition block and the
|
|
3
|
+
layout helpers are already in _grundlage.css, .z-segment belongs to
|
|
4
|
+
_formulare.css. */
|
|
5
|
+
|
|
6
|
+
/* Tabs */
|
|
7
|
+
.z-tabs { display: flex; gap: var(--space-5); border-bottom: 1px solid var(--border); overflow-x: auto; }
|
|
8
|
+
.z-tab {
|
|
9
|
+
display: inline-flex; align-items: center; height: var(--control-md); padding: 0; border: 0; background: none; cursor: pointer; white-space: nowrap;
|
|
10
|
+
color: var(--text-muted); font-weight: 500;
|
|
11
|
+
}
|
|
12
|
+
.z-tab:hover { color: var(--text); }
|
|
13
|
+
.z-tab[aria-selected="true"], .z-tab[aria-current="page"] { color: var(--text); box-shadow: inset 0 -2px 0 var(--accent-text); }
|
|
14
|
+
|
|
15
|
+
/* Sidebar */
|
|
16
|
+
.z-side { width: var(--sidebar); max-width: 100%; display: grid; gap: var(--space-4); padding: var(--space-3); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); }
|
|
17
|
+
.z-side__group { display: grid; gap: 2px; }
|
|
18
|
+
.z-side__label { padding: 0 var(--space-3) var(--space-1); font-size: 12px; line-height: 16px; color: var(--text-subtle); }
|
|
19
|
+
.z-side__item {
|
|
20
|
+
display: flex; align-items: center; gap: var(--space-3); width: 100%; height: 36px; padding: 0 var(--space-3);
|
|
21
|
+
border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer; text-align: left; color: var(--text-muted);
|
|
22
|
+
}
|
|
23
|
+
.z-side__item:hover { background: var(--surface-raised); color: var(--text); }
|
|
24
|
+
.z-side__item[aria-current="page"] { background: var(--surface-hover); color: var(--text); font-weight: 600; }
|
|
25
|
+
.z-side__item[aria-current="page"] .z-icon { color: var(--accent-text); }
|
|
26
|
+
.z-theme-mc .z-side__item[aria-current="page"] .z-icon { color: var(--mc-accent); }
|
|
27
|
+
.z-side__count { margin-left: auto; font-size: 12px; color: var(--text-subtle); font-family: var(--font-mono); }
|
|
28
|
+
|
|
29
|
+
/* AppHeader */
|
|
30
|
+
.z-header { display: flex; align-items: center; gap: var(--space-5); height: var(--header); padding: 0 var(--space-5); border-bottom: 1px solid var(--border); background: var(--bg); }
|
|
31
|
+
.z-header__brand { font-family: var(--font-display); font-weight: 700; font-size: 16px; color: var(--text); }
|
|
32
|
+
.z-header__nav { display: flex; gap: var(--space-1); min-width: 0; overflow-x: auto; }
|
|
33
|
+
.z-header__link { display: inline-flex; align-items: center; gap: var(--space-2); height: var(--control-sm); padding: 0 var(--space-3); border-radius: var(--radius-sm); color: var(--text-muted); font-weight: 500; white-space: nowrap; }
|
|
34
|
+
.z-root a.z-header__link { color: var(--text-muted); }
|
|
35
|
+
.z-root a.z-header__link:hover { color: var(--text); background: var(--surface-raised); text-decoration: none; }
|
|
36
|
+
.z-root a.z-header__link[aria-current="page"] { color: var(--text); background: var(--accent-subtle); }
|
|
37
|
+
.z-root a.z-header__link[aria-current="page"] .z-icon { color: var(--accent-text); }
|
|
38
|
+
.z-header__end { margin-left: auto; display: flex; align-items: center; gap: var(--space-3); }
|
|
39
|
+
.z-avatar { width: 32px; height: 32px; border-radius: var(--radius-full); background: var(--surface-hover); display: grid; place-items: center; font-weight: 600; font-size: 12px; }
|
|
40
|
+
|
|
41
|
+
/* PageHeader */
|
|
42
|
+
.z-pagehead { display: flex; align-items: flex-end; justify-content: space-between; flex-wrap: wrap; gap: var(--space-4); }
|
|
43
|
+
.z-pagehead__title { margin: 0; font-family: var(--font-display); font-size: 28px; line-height: 34px; font-weight: 600; letter-spacing: -0.01em; }
|
|
44
|
+
.z-pagehead__sub { margin: var(--space-1) 0 0; color: var(--text-muted); }
|
|
45
|
+
.z-pagehead__actions { display: flex; flex-wrap: wrap; gap: var(--space-2); }
|
|
46
|
+
|
|
47
|
+
/* Stepper */
|
|
48
|
+
.z-steps { display: flex; flex-wrap: wrap; gap: var(--space-5); margin: 0; padding: 0; list-style: none; }
|
|
49
|
+
.z-step { display: flex; align-items: center; gap: var(--space-2); color: var(--text-muted); }
|
|
50
|
+
.z-step__num { display: grid; place-items: center; width: 24px; height: 24px; border: 1px solid var(--border-control); border-radius: var(--radius-full); font-family: var(--font-mono); font-size: 12px; }
|
|
51
|
+
.z-step[aria-current="step"] { color: var(--text); font-weight: 600; }
|
|
52
|
+
.z-step[aria-current="step"] .z-step__num { border-color: var(--text); background: var(--text); color: var(--bg); }
|
|
53
|
+
.z-step--done .z-step__num { border-color: transparent; background: var(--surface-hover); color: var(--text); }
|
|
54
|
+
|
|
55
|
+
/* Footer */
|
|
56
|
+
.z-footer { border-top: 1px solid var(--border); padding-block: var(--space-7) var(--space-5); }
|
|
57
|
+
/* Addition to the reference: the footer carries its own ground, like .z-header,
|
|
58
|
+
so it stays readable above a legacy surface while an app migrates. Same
|
|
59
|
+
colour as the page, so nothing changes on a migrated page. */
|
|
60
|
+
.z-footer { background: var(--bg); }
|
|
61
|
+
.z-footer__cols { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); gap: var(--space-6); }
|
|
62
|
+
.z-footer__head { margin: 0 0 var(--space-3); font-size: 12px; line-height: 16px; font-weight: 400; color: var(--text-subtle); }
|
|
63
|
+
.z-footer__list { display: grid; gap: var(--space-2); margin: 0; padding: 0; list-style: none; }
|
|
64
|
+
.z-root .z-footer__list a { color: var(--text-muted); }
|
|
65
|
+
.z-root .z-footer__list a:hover { color: var(--text); text-decoration: none; }
|
|
66
|
+
.z-footer__base { display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-7); padding-top: var(--space-4); border-top: 1px solid var(--border); font-size: 12px; line-height: 16px; color: var(--text-subtle); }
|
|
67
|
+
.z-root .z-footer__base a { color: var(--text-muted); }
|
|
68
|
+
|
|
69
|
+
/* ---------- Angular hosts and additions of the library ---------- */
|
|
70
|
+
|
|
71
|
+
.z-root a.z-tab, .z-root a.z-tab:hover { color: var(--text-muted); text-decoration: none; }
|
|
72
|
+
.z-root a.z-tab:hover, .z-root a.z-tab[aria-current="page"] { color: var(--text); }
|
|
73
|
+
.z-root a.z-side__item, .z-root a.z-side__item:hover { text-decoration: none; }
|
|
74
|
+
.z-root a.z-side__item { color: var(--text-muted); }
|
|
75
|
+
.z-root a.z-side__item:hover, .z-root a.z-side__item[aria-current="page"] { color: var(--text); }
|
|
76
|
+
.z-footer, z-stepper { display: block; }
|
|
77
|
+
|
|
78
|
+
/* New, because bundle.css does not cover it:
|
|
79
|
+
1. z-sidebar carries list and select, only the list inside it is .z-side.
|
|
80
|
+
2. Below 900px the sidebar turns into the select (Sidebar/README), above it
|
|
81
|
+
the select is hidden. 899px instead of 900px, so the rule does not
|
|
82
|
+
overlap with .z-panel-shell (min-width: 900px).
|
|
83
|
+
3. In the customer area the footer has no columns (Footer/README). The empty
|
|
84
|
+
grid collapses, and the gap above it then falls away too.
|
|
85
|
+
4. Below 900px the navigation folds into a menu while the balance stays
|
|
86
|
+
visible (AppHeader/README). The menu sits as a surface below the bar, so
|
|
87
|
+
the bar keeps its height.
|
|
88
|
+
5. Below 900px the bar may wrap: on public pages two buttons stand on the
|
|
89
|
+
right, which would otherwise run out of the page horizontally at 360px.
|
|
90
|
+
The height header remains as the minimum height.
|
|
91
|
+
Addition to the reference: what wraps, and when. Measured in the first
|
|
92
|
+
integration: a 60x40 image logo, "Login" and "Registrieren" in the end
|
|
93
|
+
slot, 360px. The three parts add up to 282px of the 312px between the
|
|
94
|
+
paddings, yet the two gaps of space-5 pushed the end slot onto a second
|
|
95
|
+
row and the bar to 126px, with the menu button stranded next to the
|
|
96
|
+
logo. Three declarations define the behaviour instead:
|
|
97
|
+
a. gap space-3, the gap the end slot already uses between its own items.
|
|
98
|
+
282 + 24 fits 312, so that bar is one row of 57px again.
|
|
99
|
+
b. order 1 on the menu button: logo, end slot, menu button. The DOM keeps
|
|
100
|
+
the button in front of the <nav>, so Tab still goes from the button
|
|
101
|
+
into the links it has just opened.
|
|
102
|
+
c. The end slot takes the room that is left (flex 1 1 0) and wraps its
|
|
103
|
+
own items, right aligned, when they do not fit in it. "Anmelden" and
|
|
104
|
+
"Server erstellen" next to that logo need 362px, which a window has
|
|
105
|
+
from 410px on: below that they stack inside the slot and the bar is
|
|
106
|
+
109px, logo and menu button centred. Its
|
|
107
|
+
automatic minimum width stays, which is the widest single item, so
|
|
108
|
+
the bar itself still wraps as the last resort and nothing runs out of
|
|
109
|
+
the page.
|
|
110
|
+
An image or svg in the brand is a block: as inline content it sits on
|
|
111
|
+
the baseline of a 16px line and made the brand 45px tall instead of 40.
|
|
112
|
+
6. Below 900px the links of the header are as tall as a control, because
|
|
113
|
+
click targets are at least 40px tall on mobile. */
|
|
114
|
+
z-sidebar { display: block; }
|
|
115
|
+
z-sidebar > .z-select { display: none; }
|
|
116
|
+
.z-footer__cols:empty + .z-footer__base { margin-top: 0; }
|
|
117
|
+
.z-header__menu { display: none; }
|
|
118
|
+
.z-header__brand :where(img, svg) { display: block; }
|
|
119
|
+
@media (max-width: 899px) {
|
|
120
|
+
z-sidebar > .z-side { display: none; }
|
|
121
|
+
z-sidebar > .z-select { display: block; }
|
|
122
|
+
.z-header { position: relative; flex-wrap: wrap; gap: var(--space-3); height: auto; min-height: var(--header); padding-block: var(--space-2); }
|
|
123
|
+
.z-header__menu { display: inline-flex; order: 1; }
|
|
124
|
+
.z-header__end { flex: 1 1 0; flex-wrap: wrap; justify-content: flex-end; }
|
|
125
|
+
.z-header__nav { display: none; }
|
|
126
|
+
.z-header .z-header__link { height: var(--control-md); }
|
|
127
|
+
.z-header--open .z-header__nav {
|
|
128
|
+
display: grid; gap: var(--space-1);
|
|
129
|
+
position: absolute; top: 100%; left: 0; right: 0; z-index: var(--z-header);
|
|
130
|
+
padding: var(--space-2) var(--space-5);
|
|
131
|
+
background: var(--surface-raised); border-bottom: 1px solid var(--border);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* Addition to the reference: click targets at least 40px on mobile (CLAUDE.md, Abstand und Layout).
|
|
136
|
+
Affected are only the links in the footer (columns and base row) and the
|
|
137
|
+
sidebar entry with its 36px. Tab, sidebar select, header links, menu button
|
|
138
|
+
and the actions in the PageHeader are already control-md tall, and the
|
|
139
|
+
stepper cannot be operated. The base row stays flex with wrapping. */
|
|
140
|
+
@media (max-width: 640px) {
|
|
141
|
+
.z-footer__list a, .z-footer__base a, .z-side__item {
|
|
142
|
+
display: inline-flex; align-items: center; min-height: var(--control-md);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* Addition to the reference: text-subtle never sits on surface-hover
|
|
147
|
+
(CLAUDE.md, Farbe). The active sidebar entry is the only place in the library
|
|
148
|
+
where it did: the entry takes surface-hover and .z-side__count keeps
|
|
149
|
+
text-subtle. text-muted is the next step up and is the colour the entry uses
|
|
150
|
+
for its icons anyway.
|
|
151
|
+
Every other surface-hover ground was checked and is clean: .z-segment
|
|
152
|
+
button[aria-pressed="true"], .z-menu__item:hover and :focus-visible,
|
|
153
|
+
.z-btn--secondary:hover, .z-avatar and .z-step--done .z-step__num all set
|
|
154
|
+
text or text-muted on themselves, .z-row__thumb sets text-muted, .z-meter
|
|
155
|
+
and .z-skel carry no text at all, and the selected game tile changes only
|
|
156
|
+
its outline over surface-raised. */
|
|
157
|
+
.z-side__item[aria-current="page"] .z-side__count { color: var(--text-muted); }
|
|
158
|
+
|
|
159
|
+
/* Addition to the reference: the in-page tab group (z-tab-group, z-tab-panel).
|
|
160
|
+
Its markup is the one Tabs/preview.html shows, so .z-tabs and .z-tab above
|
|
161
|
+
already carry it: .z-tab resets border, background and padding of a button,
|
|
162
|
+
is control-md tall and never wraps, and .z-tabs scrolls horizontally when
|
|
163
|
+
the bar does not fit. Three things the reference does not have:
|
|
164
|
+
1. The group as a grid, so the bar and the panel below it keep one gap
|
|
165
|
+
instead of a margin on a sibling (CLAUDE.md, Abstand und Layout). An
|
|
166
|
+
inactive panel carries the hidden attribute and is therefore display:
|
|
167
|
+
none and leaves no gap behind. Nothing here may set display on
|
|
168
|
+
z-tab-panel: an author rule of any specificity beats the browser's
|
|
169
|
+
[hidden], and the panel would stay visible. As a grid item it becomes a
|
|
170
|
+
block on its own.
|
|
171
|
+
2. The bar is the grid item that scrolls, so its automatic minimum size is
|
|
172
|
+
0 and five tabs cannot push the page sideways at 360px.
|
|
173
|
+
3. The locked tab as everywhere else in the library: 45 % and not-allowed
|
|
174
|
+
(15-zustaende.md). It is a natively disabled button, so the reason
|
|
175
|
+
belongs next to the group, not on the tab.
|
|
176
|
+
The focus ring is the global one from _grundlage.css, on the tab as well as
|
|
177
|
+
on the panel, which is a tab stop of its own. */
|
|
178
|
+
z-tab-group { display: grid; gap: var(--space-4); }
|
|
179
|
+
.z-tab:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/* Package overlays: Dialog and Menu.
|
|
2
|
+
The sections Dialog, Menu and CDK overlays stand verbatim as in
|
|
3
|
+
spec/components/bundle.css. `.z-scrim` already gets its display here from
|
|
4
|
+
the Dialog section and therefore appears in no second host rule. */
|
|
5
|
+
|
|
6
|
+
/* Dialog */
|
|
7
|
+
.z-scrim { display: grid; place-items: center; padding: var(--space-5); background: var(--scrim); }
|
|
8
|
+
.z-dialog { width: min(480px, 100%); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--shadow-overlay); }
|
|
9
|
+
.z-dialog__header { padding: var(--space-5) var(--space-5) 0; }
|
|
10
|
+
.z-dialog__title { margin: 0; font-family: var(--font-display); font-size: 20px; line-height: 28px; font-weight: 600; }
|
|
11
|
+
.z-dialog__body { display: grid; gap: var(--space-4); padding: var(--space-3) var(--space-5) var(--space-5); color: var(--text-muted); }
|
|
12
|
+
.z-dialog__body .z-input { background: var(--bg); }
|
|
13
|
+
.z-dialog__footer { display: flex; justify-content: flex-end; flex-wrap: wrap; gap: var(--space-2); padding: var(--space-4) var(--space-5); border-top: 1px solid var(--border); }
|
|
14
|
+
|
|
15
|
+
/* Menu */
|
|
16
|
+
.z-menu { display: grid; min-width: 200px; padding: var(--space-1); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--shadow-overlay); }
|
|
17
|
+
.z-menu__item { display: flex; align-items: center; gap: var(--space-3); height: 36px; padding: 0 var(--space-3); border: 0; border-radius: var(--radius-sm); background: none; cursor: pointer; text-align: left; color: var(--text); }
|
|
18
|
+
.z-menu__item:hover { background: var(--surface-hover); }
|
|
19
|
+
.z-menu__item--danger, .z-menu__item--danger .z-icon { color: var(--danger); }
|
|
20
|
+
.z-menu__key { margin-left: auto; font-family: var(--font-mono); font-size: 12px; color: var(--text-muted); }
|
|
21
|
+
.z-menu__sep { height: 1px; margin: var(--space-1) 0; background: var(--border); }
|
|
22
|
+
|
|
23
|
+
/* CDK overlays */
|
|
24
|
+
.z-backdrop { background: var(--scrim); }
|
|
25
|
+
.z-dialog-panel { width: min(480px, calc(100vw - 2 * var(--space-4))); max-height: calc(100vh - 2 * var(--space-5)); }
|
|
26
|
+
.z-dialog-panel .z-dialog { width: 100%; max-height: inherit; overflow: auto; }
|
|
27
|
+
.z-dialog { display: block; }
|
|
28
|
+
@media (max-width: 640px) {
|
|
29
|
+
.z-dialog-panel { width: 100vw; align-self: flex-end; }
|
|
30
|
+
.z-dialog-panel .z-dialog { border-radius: var(--radius-md) var(--radius-md) 0 0; }
|
|
31
|
+
}
|
|
32
|
+
.z-menu__item:disabled, .z-menu__item[aria-disabled="true"] { opacity: 0.45; cursor: not-allowed; }
|
|
33
|
+
.z-menu__item:focus-visible { background: var(--surface-hover); }
|
|
34
|
+
|
|
35
|
+
/* Angular hosts */
|
|
36
|
+
/* Addition to the reference: CDK backdrop without a fade in (CLAUDE.md, Bewegung). */
|
|
37
|
+
.z-backdrop.cdk-overlay-backdrop { transition: none; }
|
|
38
|
+
/* The separator is an element of its own and therefore inline otherwise. */
|
|
39
|
+
z-menu-separator { display: block; }
|
|
40
|
+
/* The body of the dialog colours its text text-muted, the label of a field
|
|
41
|
+
stays text (in the preview as a style on the label). */
|
|
42
|
+
.z-dialog__body .z-field__label { color: var(--text); }
|
|
43
|
+
/* Click target at least 40px on mobile, the entry measures 36px otherwise. */
|
|
44
|
+
@media (max-width: 640px) {
|
|
45
|
+
.z-menu__item { height: var(--control-md); }
|
|
46
|
+
}
|
|
47
|
+
/* Addition to the reference: an entry may be a link when it leads somewhere.
|
|
48
|
+
`.z-root a` (0,1,1) beats `.z-menu__item` (0,1,0) and would colour it
|
|
49
|
+
accent-text and underline it on hover, so the colours of the reference are
|
|
50
|
+
restored with the same weight, exactly as the a.z-btn rules in
|
|
51
|
+
_grundlage.css do. */
|
|
52
|
+
.z-root a.z-menu__item, .z-root a.z-menu__item:hover { text-decoration: none; }
|
|
53
|
+
.z-root a.z-menu__item { color: var(--text); }
|
|
54
|
+
.z-root a.z-menu__item--danger { color: var(--danger); }
|
|
55
|
+
/* Addition to the reference: a long dialog scrolls inside instead of growing
|
|
56
|
+
past the screen. Two links of the chain were missing. The CDK loads the
|
|
57
|
+
styles of `.cdk-overlay-pane` at runtime, so they stand after this stylesheet
|
|
58
|
+
and its `max-height: 100%` beat the reference rule of the same specificity;
|
|
59
|
+
the limit is therefore repeated with the pane class in front of it. And the
|
|
60
|
+
reference hands the limit down to `.z-dialog` with `max-height: inherit`,
|
|
61
|
+
while through `ZDialog.open()` the CDK container and the host element of the
|
|
62
|
+
opened component stand in between: `inherit` only arrives when every one of
|
|
63
|
+
them passes the limit on. Both become flex columns, so the limit reaches the
|
|
64
|
+
dialog and, inside it, only the body moves: header and footer with the
|
|
65
|
+
actions stay put. */
|
|
66
|
+
.cdk-overlay-pane.z-dialog-panel { max-height: calc(100vh - 2 * var(--space-5)); }
|
|
67
|
+
.z-dialog-panel > cdk-dialog-container,
|
|
68
|
+
.z-dialog-panel > cdk-dialog-container > * { display: flex; flex-direction: column; width: 100%; min-height: 0; max-height: inherit; }
|
|
69
|
+
.z-dialog-panel .z-dialog { display: flex; flex-direction: column; }
|
|
70
|
+
.z-dialog-panel .z-dialog__header, .z-dialog-panel .z-dialog__footer { flex: none; }
|
|
71
|
+
/* `scroll-padding` keeps the 2px focus ring of a control at the edge of the
|
|
72
|
+
scrolling body from being cut off when the browser scrolls it into view. The
|
|
73
|
+
ring of the body itself lies inside for the same reason: `overflow: auto`
|
|
74
|
+
clips at its padding box, so a ring outside it would be cut off by the header
|
|
75
|
+
and the footer (same idiom as .z-game__cover in _werkzeuge.css). */
|
|
76
|
+
.z-dialog-panel .z-dialog__body { min-height: 0; overflow: auto; scroll-padding: var(--space-1); }
|
|
77
|
+
.z-dialog-panel .z-dialog__body:focus-visible { outline-offset: -2px; }
|
|
78
|
+
/* dvh where the browser has it, so the URL bar of a phone does not cut the
|
|
79
|
+
footer off; the vh rule of the reference stays as the fallback. */
|
|
80
|
+
@supports (max-height: 100dvh) {
|
|
81
|
+
.cdk-overlay-pane.z-dialog-panel { max-height: calc(100dvh - 2 * var(--space-5)); }
|
|
82
|
+
}
|
|
83
|
+
/* Docked at the bottom on mobile, so the footer keeps clear of the home bar. */
|
|
84
|
+
@media (max-width: 640px) {
|
|
85
|
+
.z-dialog-panel .z-dialog__footer { padding-bottom: calc(var(--space-4) + env(safe-area-inset-bottom)); }
|
|
86
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/* Package rueckmeldung: Alert, EmptyState, Skeleton, Toast, Toast outlet and
|
|
2
|
+
Tooltip. Verbatim from spec/components/bundle.css. */
|
|
3
|
+
|
|
4
|
+
/* Alert */
|
|
5
|
+
.z-alert { display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-3) var(--space-4); border-radius: var(--radius-md); background: var(--surface-raised); }
|
|
6
|
+
.z-alert__text { display: grid; gap: 2px; min-width: 0; flex: 1; }
|
|
7
|
+
.z-alert__title { font-weight: 600; }
|
|
8
|
+
.z-alert__body { color: var(--text-muted); }
|
|
9
|
+
.z-alert--info { background: var(--info-subtle); } .z-alert--info .z-icon { color: var(--info); }
|
|
10
|
+
.z-alert--success { background: var(--success-subtle); } .z-alert--success .z-icon { color: var(--success); }
|
|
11
|
+
.z-alert--warning { background: var(--warning-subtle); } .z-alert--warning .z-icon { color: var(--warning); }
|
|
12
|
+
.z-alert--danger { background: var(--danger-subtle); } .z-alert--danger .z-icon { color: var(--danger); }
|
|
13
|
+
@media (max-width: 640px) { .z-alert { flex-wrap: wrap; } .z-alert > .z-btn { margin-left: 32px; } }
|
|
14
|
+
/* Addition to the reference: only with flex-basis does the button wrap below
|
|
15
|
+
the text (Alert/README.md "Mobil bricht der Button unter den Text um"). The
|
|
16
|
+
indent of 32px puts it below the text, not below the icon. */
|
|
17
|
+
@media (max-width: 640px) { .z-alert > .z-btn { flex-basis: 100%; } }
|
|
18
|
+
|
|
19
|
+
/* EmptyState */
|
|
20
|
+
.z-empty { display: grid; justify-items: center; gap: var(--space-2); padding: var(--space-7) var(--space-4); text-align: center; }
|
|
21
|
+
/* margin and font-size: addition for headingLevel, so an h1 to h4 looks
|
|
22
|
+
exactly like the span of the reference (no browser margin, no 2em). */
|
|
23
|
+
.z-empty__title { font-weight: 600; margin: 0; font-size: inherit; }
|
|
24
|
+
.z-empty__body { max-width: 40ch; color: var(--text-muted); }
|
|
25
|
+
.z-empty .z-btn { margin-top: var(--space-2); }
|
|
26
|
+
|
|
27
|
+
/* Skeleton */
|
|
28
|
+
.z-skel { display: block; height: 12px; border-radius: var(--radius-sm); background: var(--surface-hover); }
|
|
29
|
+
.z-skel--thumb { width: 32px; height: 32px; }
|
|
30
|
+
/* Addition to the reference: the placeholder of a button[zGameTile] for a
|
|
31
|
+
z-game-grid that loads. It repeats the boxes of .z-game (_werkzeuge.css):
|
|
32
|
+
a grid with the gap space-2, a cover area in radius-md whose aspect ratio
|
|
33
|
+
is set together with .z-game__cover in _werkzeuge.css, a title line
|
|
34
|
+
one line box of the inherited type tall and a price line of 12px/16px, the
|
|
35
|
+
values of .z-game__price. Each text line draws a bar of the 12px skeleton
|
|
36
|
+
height centred in its line box, so the cell is exactly the cell of a tile.
|
|
37
|
+
The host keeps the opacity animation; its own fill is dropped. */
|
|
38
|
+
.z-skel--tile { display: grid; gap: var(--space-2); height: auto; min-width: 0; border-radius: 0; background: none; }
|
|
39
|
+
.z-skel__cover { width: 100%; border-radius: var(--radius-md); background: var(--surface-hover); }
|
|
40
|
+
.z-skel__line { height: 12px; margin-block: calc((1lh - 12px) / 2); border-radius: var(--radius-sm); background: var(--surface-hover); }
|
|
41
|
+
.z-skel__line--title { width: 72%; font-weight: 600; }
|
|
42
|
+
.z-skel__line--price { width: 48%; font-size: 12px; line-height: 16px; }
|
|
43
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
44
|
+
.z-skel { animation: z-skel 1.2s ease-in-out infinite alternate; }
|
|
45
|
+
@keyframes z-skel { from { opacity: 1; } to { opacity: 0.5; } }
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* Toast */
|
|
49
|
+
.z-toast { display: inline-flex; align-items: center; gap: var(--space-3); max-width: 420px; padding: var(--space-3) var(--space-4); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface-raised); box-shadow: var(--shadow-overlay); }
|
|
50
|
+
.z-toast--success .z-icon { color: var(--success); }
|
|
51
|
+
.z-toast--danger .z-icon { color: var(--danger); }
|
|
52
|
+
.z-toast__action { margin-left: var(--space-2); padding: 0; border: 0; background: none; cursor: pointer; color: var(--accent-text); font-weight: 500; white-space: nowrap; }
|
|
53
|
+
|
|
54
|
+
/* Toast outlet */
|
|
55
|
+
.z-toast-outlet { position: fixed; right: var(--space-5); bottom: var(--space-5); z-index: var(--z-toast); display: grid; justify-items: end; gap: var(--space-2); pointer-events: none; }
|
|
56
|
+
.z-toast-outlet .z-toast { pointer-events: auto; }
|
|
57
|
+
.z-toast__close { margin-left: var(--space-1); }
|
|
58
|
+
@media (max-width: 640px) {
|
|
59
|
+
.z-toast-outlet { left: var(--space-4); right: var(--space-4); bottom: var(--space-4); justify-items: stretch; }
|
|
60
|
+
.z-toast-outlet .z-toast { max-width: none; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Tooltip */
|
|
64
|
+
.z-tooltip { max-width: 240px; padding: var(--space-1) var(--space-2); border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-raised); box-shadow: var(--shadow-overlay); color: var(--text); font-size: 12px; line-height: 16px; }
|
|
65
|
+
|
|
66
|
+
/* Addition to the reference: the tooltip has to stay reachable with the
|
|
67
|
+
pointer (WCAG 2.1 SC 1.4.13 "Hoverable"). The panel hangs in the overlay on
|
|
68
|
+
the body, space-2 away from the trigger, and the pointer would cross nothing
|
|
69
|
+
on the way. The transparent padding of the pane covers exactly that gap, so
|
|
70
|
+
the directive positions the pane flush against the trigger and this padding
|
|
71
|
+
is the visible distance. */
|
|
72
|
+
.cdk-overlay-pane.z-tooltip-pane { padding-block: var(--space-2); }
|
|
73
|
+
|
|
74
|
+
/* Addition to the reference: click targets at least 40px on mobile (CLAUDE.md,
|
|
75
|
+
Abstand und Layout). The height of the close button comes from the rule for
|
|
76
|
+
z-btn--sm in _grundlage.css; all that remains here is that the buttons in
|
|
77
|
+
the toast do not shrink and the action itself becomes 40px tall. */
|
|
78
|
+
@media (max-width: 640px) {
|
|
79
|
+
.z-toast .z-btn, .z-toast__action { flex: none; }
|
|
80
|
+
.z-toast__action { display: inline-flex; align-items: center; min-height: var(--control-md); }
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* Addition to the reference: the statuses info and warning, mirrored from the
|
|
84
|
+
rules the reference has for success and danger. Only the icon takes the
|
|
85
|
+
colour; the toast keeps the plain surface of the reference, without a tinted
|
|
86
|
+
fill and without a coloured frame, and the status also stands in the text.
|
|
87
|
+
With a title the two lines share one column, built like .z-alert__text. */
|
|
88
|
+
.z-toast--info .z-icon { color: var(--info); }
|
|
89
|
+
.z-toast--warning .z-icon { color: var(--warning); }
|
|
90
|
+
.z-toast__text { display: grid; gap: 2px; min-width: 0; }
|
|
91
|
+
.z-toast__title { font-weight: 600; }
|
|
92
|
+
.z-toast__body { color: var(--text-muted); }
|
|
93
|
+
|
|
94
|
+
/* Addition to the reference: the outlet keeps two live regions, one polite and
|
|
95
|
+
one assertive, so a screen reader announces a toast that appears inside them.
|
|
96
|
+
display: contents removes the two boxes again, so the toasts themselves stay
|
|
97
|
+
the grid items of .z-toast-outlet and keep gap, alignment and the mobile
|
|
98
|
+
rules; the order set per toast restores the order of the list across both
|
|
99
|
+
regions. */
|
|
100
|
+
.z-toast-outlet__live { display: contents; }
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/* Package werkzeuge: Console, Hero, GameTile, PriceSummary, SpecList, Faq.
|
|
2
|
+
Verbatim from spec/components/bundle.css. */
|
|
3
|
+
|
|
4
|
+
/* GameTile */
|
|
5
|
+
.z-games { display: grid; grid-template-columns: repeat(auto-fill, minmax(128px, 1fr)); gap: var(--space-4); }
|
|
6
|
+
.z-game { display: grid; gap: var(--space-2); padding: 0; border: 0; background: none; text-align: left; cursor: pointer; min-width: 0; }
|
|
7
|
+
.z-game__cover {
|
|
8
|
+
width: 100%; border-radius: var(--radius-md); overflow: hidden;
|
|
9
|
+
background: var(--surface-raised); outline: 1px solid var(--border); outline-offset: -1px;
|
|
10
|
+
display: grid; place-items: center; text-align: center;
|
|
11
|
+
font-family: var(--font-display); font-weight: 700; font-size: 16px; line-height: 20px; color: var(--text-muted);
|
|
12
|
+
}
|
|
13
|
+
/* Deviation from the reference (3:4 cover): the cover area is landscape in the
|
|
14
|
+
ratio of a store header, 460:215, the only images the games have until real
|
|
15
|
+
3:4 covers arrive. This is the one place of that ratio: the placeholder of
|
|
16
|
+
z-skeleton tile (_rueckmeldung.css) takes it from here, so loading does not
|
|
17
|
+
jump. A header fills the area uncropped, any other ratio is cut centred. */
|
|
18
|
+
.z-game__cover, .z-skel__cover { aspect-ratio: 460 / 215; }
|
|
19
|
+
.z-game__cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
20
|
+
/* The text fallback (no cover, or one that failed) in the same area: padding
|
|
21
|
+
here instead of on the area, so an image reaches the edges, and at most two
|
|
22
|
+
lines, which fit the lowest area of a 128px column. */
|
|
23
|
+
.z-game__fallback { padding: var(--space-2); overflow: hidden; display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 2; line-clamp: 2; }
|
|
24
|
+
.z-game:hover .z-game__cover { outline-color: var(--border-control); }
|
|
25
|
+
.z-game[aria-pressed="true"] .z-game__cover { outline: 2px solid var(--accent-text); outline-offset: -2px; }
|
|
26
|
+
.z-game__title { font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
27
|
+
.z-game__price { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 12px; line-height: 16px; color: var(--text-muted); }
|
|
28
|
+
/* Addition to the reference: a[zGameTile]. The counter-rule every library
|
|
29
|
+
class on an anchor needs (_grundlage.css), so the link tile keeps the text
|
|
30
|
+
colour of the button tile and gets no underline on hover. */
|
|
31
|
+
.z-root a.z-game, .z-root a.z-game:hover { color: inherit; text-decoration: none; }
|
|
32
|
+
|
|
33
|
+
/* SpecList */
|
|
34
|
+
.z-spec { display: grid; grid-template-columns: minmax(120px, 200px) minmax(0, 1fr); margin: 0; }
|
|
35
|
+
.z-spec dt, .z-spec dd { margin: 0; padding: var(--space-3) 0; border-top: 1px solid var(--border); }
|
|
36
|
+
.z-spec dt { color: var(--text-muted); }
|
|
37
|
+
.z-spec dt:first-of-type, .z-spec dt:first-of-type + dd { border-top: 0; }
|
|
38
|
+
.z-spec dd small { font-size: 12px; color: var(--text-subtle); margin-left: var(--space-2); }
|
|
39
|
+
@media (max-width: 480px) {
|
|
40
|
+
.z-spec { grid-template-columns: 1fr; }
|
|
41
|
+
.z-spec dd { border-top: 0; padding-top: 0; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Hero */
|
|
45
|
+
.z-hero { display: grid; grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); gap: var(--space-7); align-items: center; padding-block: var(--space-9); }
|
|
46
|
+
.z-hero__title { margin: 0; font-family: var(--font-display); font-size: 56px; line-height: 60px; font-weight: 700; letter-spacing: -0.02em; text-wrap: balance; }
|
|
47
|
+
.z-hero__lead { margin: var(--space-5) 0 0; max-width: 52ch; font-size: 20px; line-height: 30px; color: var(--text-muted); }
|
|
48
|
+
.z-hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }
|
|
49
|
+
.z-hero__note { margin: var(--space-4) 0 0; font-size: 12px; line-height: 16px; color: var(--text-subtle); }
|
|
50
|
+
@media (max-width: 900px) { .z-hero { grid-template-columns: minmax(0, 1fr); padding-block: var(--space-8); } }
|
|
51
|
+
@media (max-width: 640px) { .z-hero__title { font-size: 40px; line-height: 44px; } .z-hero__lead { font-size: 16px; line-height: 24px; } }
|
|
52
|
+
/* Addition to the reference: Hero/README.md keeps display-xl for the start page
|
|
53
|
+
and /minecraft and gives sub-pages display-lg, which bundle.css only has
|
|
54
|
+
below 640px. Same two values, so a hero in size="lg" reads at every width as
|
|
55
|
+
an xl hero reads below 640px; the media query above keeps it there. */
|
|
56
|
+
.z-hero--lg .z-hero__title { font-size: 40px; line-height: 44px; }
|
|
57
|
+
/* Addition to the reference: Hero/README.md gives sub-pages a hero that "kommt
|
|
58
|
+
oft ohne rechte Spalte aus", while bundle.css fixes .z-hero at 7fr/5fr above
|
|
59
|
+
900px. Such a hero would keep an empty right column at every width above
|
|
60
|
+
900px, so without the slot the row is one column. The slot marker
|
|
61
|
+
[zHeroAside] adds no class of its own, so the attribute is what the rule
|
|
62
|
+
asks for. The lead keeps its own 52ch measure and does not stretch. */
|
|
63
|
+
.z-hero:not(:has(> [zHeroAside])) { grid-template-columns: minmax(0, 1fr); }
|
|
64
|
+
|
|
65
|
+
/* PriceSummary */
|
|
66
|
+
.z-summary { display: grid; gap: var(--space-4); padding: var(--space-5); border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--surface); }
|
|
67
|
+
.z-summary__label { font-size: 12px; line-height: 16px; color: var(--text-muted); }
|
|
68
|
+
.z-summary__price { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 28px; line-height: 34px; font-weight: 600; }
|
|
69
|
+
.z-summary__price small { font-size: 12px; font-weight: 400; color: var(--text-subtle); }
|
|
70
|
+
.z-summary__lines { display: grid; margin: 0; }
|
|
71
|
+
.z-summary__line { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-2) 0; border-top: 1px solid var(--border); color: var(--text-muted); }
|
|
72
|
+
.z-summary__line dd { margin: 0; color: var(--text); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
|
73
|
+
.z-summary__note { font-size: 12px; line-height: 16px; color: var(--text-subtle); }
|
|
74
|
+
|
|
75
|
+
/* Console */
|
|
76
|
+
.z-console { border: 1px solid var(--border); border-radius: var(--radius-md); background: var(--bg); overflow: hidden; }
|
|
77
|
+
.z-console__log { max-height: 240px; margin: 0; padding: var(--space-3) var(--space-4); overflow: auto; font-family: var(--font-mono); font-size: 12px; line-height: 20px; color: var(--text-muted); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
78
|
+
.z-log__time { color: var(--text-subtle); }
|
|
79
|
+
.z-log--warn { color: var(--warning); }
|
|
80
|
+
.z-log--error { color: var(--danger); }
|
|
81
|
+
.z-log--cmd { color: var(--text); }
|
|
82
|
+
.z-console__input { display: flex; align-items: center; gap: var(--space-2); padding: 0 var(--space-4); border-top: 1px solid var(--border); background: var(--surface); font-family: var(--font-mono); font-size: 12px; color: var(--text-subtle); }
|
|
83
|
+
.z-console__input input { flex: 1; min-width: 0; height: var(--control-md); border: 0; background: none; font-family: var(--font-mono); font-size: 12px; color: var(--text); }
|
|
84
|
+
.z-console__input input::placeholder { color: var(--text-subtle); }
|
|
85
|
+
|
|
86
|
+
/* Faq */
|
|
87
|
+
.z-faq { border-top: 1px solid var(--border); }
|
|
88
|
+
.z-faq:last-of-type { border-bottom: 1px solid var(--border); }
|
|
89
|
+
.z-faq summary { display: flex; justify-content: space-between; gap: var(--space-4); padding: var(--space-4) 0; font-size: 16px; line-height: 24px; font-weight: 600; cursor: pointer; list-style: none; }
|
|
90
|
+
.z-faq summary::-webkit-details-marker { display: none; }
|
|
91
|
+
.z-faq summary::after { content: "+"; font-family: var(--font-mono); color: var(--text-muted); }
|
|
92
|
+
.z-faq[open] summary::after { content: "\2212"; }
|
|
93
|
+
.z-faq__body { max-width: var(--measure); margin: 0; padding: 0 0 var(--space-5); font-size: 16px; line-height: 24px; color: var(--text-muted); }
|
|
94
|
+
|
|
95
|
+
/* ---------- Angular hosts and additions of the library ---------- */
|
|
96
|
+
|
|
97
|
+
.z-console, z-faq, z-spec-list, z-price-summary { display: block; }
|
|
98
|
+
|
|
99
|
+
/* Every question sits in a z-faq of its own. That makes every details the
|
|
100
|
+
:last-of-type of its host and it would get a second line. Only the last
|
|
101
|
+
question keeps it, otherwise there would be 2px between the questions. */
|
|
102
|
+
z-faq:not(:last-of-type) .z-faq { border-bottom: 0; }
|
|
103
|
+
|
|
104
|
+
/* The button "Zum Ende" does not appear in the preview, because the preview
|
|
105
|
+
does not scroll. It sits between log and input. Tokens only. */
|
|
106
|
+
.z-console__end { display: flex; justify-content: center; padding: var(--space-2); border-top: 1px solid var(--border); }
|
|
107
|
+
|
|
108
|
+
/* Disabled input like .z-btn:disabled: 45 % and not-allowed. */
|
|
109
|
+
.z-console__input input:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
110
|
+
|
|
111
|
+
/* Addition to the reference: the empty state in the log is running text, not a
|
|
112
|
+
log. The enclosing pre sets mono, 12px and white-space: pre-wrap; here
|
|
113
|
+
body-sm with normal wrapping applies again. */
|
|
114
|
+
.z-console__log .z-empty {
|
|
115
|
+
font-family: var(--font-body);
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
line-height: 20px;
|
|
118
|
+
white-space: normal;
|
|
119
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* Zenit themes / accents.
|
|
2
|
+
|
|
3
|
+
An accent replaces the five tokens that carry "you can click this" or "this
|
|
4
|
+
is active": accent, accent-hover, on-accent, accent-text, accent-subtle.
|
|
5
|
+
Nothing else moves. Surfaces, text, borders, status and the Minecraft
|
|
6
|
+
subtheme stay exactly where the scheme put them.
|
|
7
|
+
|
|
8
|
+
rot is the default of the design system and has no block here: it comes from
|
|
9
|
+
tokens.css for dark, from light.css and from contrast.css. The theme service
|
|
10
|
+
therefore removes data-accent entirely for rot instead of writing it out.
|
|
11
|
+
|
|
12
|
+
Two blocks per accent, because accent-text has to reach 4.5:1 on the bg AND
|
|
13
|
+
the surface of the scheme it is read on:
|
|
14
|
+
- [data-accent="X"] holds the values for the dark grounds (dark, contrast).
|
|
15
|
+
accent-text is a light tint there, the fill is a deep saturated colour.
|
|
16
|
+
- [data-theme="light"][data-accent="X"] replaces all five for the light
|
|
17
|
+
ground: accent-text becomes a deep colour, accent-subtle an opaque light
|
|
18
|
+
tint, matching how light.css handles rot and the status colours.
|
|
19
|
+
The second block has specificity (0,2,0) and wins over the first (0,1,0)
|
|
20
|
+
regardless of order; the first wins over [data-theme="light"] (0,1,0) only
|
|
21
|
+
because this file is imported after light.css. That is why themes.css fixes
|
|
22
|
+
the order base -> light -> contrast -> accents.
|
|
23
|
+
|
|
24
|
+
Derivation, per accent and per scheme:
|
|
25
|
+
1. The fill is deepened until white as on-accent clears 4.5:1 on accent AND
|
|
26
|
+
on accent-hover; accent-hover is one step deeper than accent, as in the
|
|
27
|
+
dark scheme.
|
|
28
|
+
2. accent also has to keep 3:1 against focus, which is white on the dark
|
|
29
|
+
grounds and near black on the light one. That is the binding limit on the
|
|
30
|
+
light ground and it is why the light fills are not deeper still.
|
|
31
|
+
accent-hover is exempt on the light ground (2.41:1 to 2.97:1): it has to
|
|
32
|
+
be deeper than accent, and the focus ring never touches the fill anyway,
|
|
33
|
+
because it is drawn with a 2px offset on the surface around the button.
|
|
34
|
+
3. accent-text is pushed away from the ground until it clears 4.5:1 on bg
|
|
35
|
+
and on surface of that scheme.
|
|
36
|
+
4. accent-subtle follows accent-text: 12 % of it on the dark grounds, an
|
|
37
|
+
opaque light tint of the same hue on the light ground.
|
|
38
|
+
|
|
39
|
+
Known overlap: with gruen, accent-text sits close to --success, and with
|
|
40
|
+
blau close to --info. The design system answers that itself, because a
|
|
41
|
+
status is always written out as a word as well (CLAUDE.md, "Farbe").
|
|
42
|
+
|
|
43
|
+
All values are verified by tools/check-theme-contrast.mjs. */
|
|
44
|
+
|
|
45
|
+
/* Blau */
|
|
46
|
+
[data-accent="blau"] {
|
|
47
|
+
--accent: #1d4ed8;
|
|
48
|
+
--accent-hover: #1a44ba;
|
|
49
|
+
--on-accent: #ffffff;
|
|
50
|
+
--accent-text: #7ab0ff;
|
|
51
|
+
--accent-subtle: rgba(122, 176, 255, 0.12);
|
|
52
|
+
}
|
|
53
|
+
[data-theme="light"][data-accent="blau"] {
|
|
54
|
+
--accent: #2563eb;
|
|
55
|
+
--accent-hover: #1d4ed8;
|
|
56
|
+
--on-accent: #ffffff;
|
|
57
|
+
--accent-text: #1d4ed8;
|
|
58
|
+
--accent-subtle: #e6edfd;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Gruen */
|
|
62
|
+
[data-accent="gruen"] {
|
|
63
|
+
--accent: #15803d;
|
|
64
|
+
--accent-hover: #116a32;
|
|
65
|
+
--on-accent: #ffffff;
|
|
66
|
+
--accent-text: #5fd07a;
|
|
67
|
+
--accent-subtle: rgba(95, 208, 122, 0.12);
|
|
68
|
+
}
|
|
69
|
+
[data-theme="light"][data-accent="gruen"] {
|
|
70
|
+
--accent: #15803d;
|
|
71
|
+
--accent-hover: #116a32;
|
|
72
|
+
--on-accent: #ffffff;
|
|
73
|
+
--accent-text: #116a32;
|
|
74
|
+
--accent-subtle: #e3f3e9;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/* Violett */
|
|
78
|
+
[data-accent="violett"] {
|
|
79
|
+
--accent: #7e22ce;
|
|
80
|
+
--accent-hover: #6b1fae;
|
|
81
|
+
--on-accent: #ffffff;
|
|
82
|
+
--accent-text: #c795f5;
|
|
83
|
+
--accent-subtle: rgba(199, 149, 245, 0.12);
|
|
84
|
+
}
|
|
85
|
+
[data-theme="light"][data-accent="violett"] {
|
|
86
|
+
--accent: #8b2ade;
|
|
87
|
+
--accent-hover: #7420bd;
|
|
88
|
+
--on-accent: #ffffff;
|
|
89
|
+
--accent-text: #6b1fae;
|
|
90
|
+
--accent-subtle: #f0e4fb;
|
|
91
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/* Zenit themes / base.
|
|
2
|
+
|
|
3
|
+
What belongs here: the declarations every scheme needs that tokens.css must
|
|
4
|
+
not carry. tokens.css is compiled from spec/tokens.json and has to stay byte
|
|
5
|
+
identical to spec/tokens.css, so nothing may be added there by hand.
|
|
6
|
+
|
|
7
|
+
color-scheme is the one such declaration today. It tells the engine which
|
|
8
|
+
palette to use for the parts a page cannot style itself: the drop-down of a
|
|
9
|
+
native <select>, scrollbars, the text caret, spin buttons, the default
|
|
10
|
+
background of a form control and the focus ring the platform draws before
|
|
11
|
+
CSS takes over. Without it a dark page opens a white drop-down list.
|
|
12
|
+
|
|
13
|
+
The value follows the chosen scheme, never the operating system: `dark` is
|
|
14
|
+
the default of the design system, and [data-theme="light"] overrides it in
|
|
15
|
+
light.css. Because :root and [data-theme=...] have the same specificity
|
|
16
|
+
(0,1,0), this file has to be imported BEFORE light.css and contrast.css.
|
|
17
|
+
|
|
18
|
+
The default for a page without data-theme no longer depends on this file:
|
|
19
|
+
_grundlage.css carries it as :where(:root) at specificity (0,0,0), so an
|
|
20
|
+
application that only includes tokens.css and zenit-ui.css gets dark native
|
|
21
|
+
UI as well. [data-theme="dark"] stays here and is the reason the file
|
|
22
|
+
remains: a dark subtree inside a light page inherits color-scheme: light
|
|
23
|
+
from the root, and only this selector puts it back. The :root here is
|
|
24
|
+
therefore a duplicate of the base styles' default, kept so that themes.css
|
|
25
|
+
reads completely on its own. */
|
|
26
|
+
:root, [data-theme="dark"] { color-scheme: dark; }
|