@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.
Files changed (36) hide show
  1. package/README.md +400 -0
  2. package/fesm2022/zenit-hosting-zenit-ui.mjs +11289 -0
  3. package/fesm2022/zenit-hosting-zenit-ui.mjs.map +1 -0
  4. package/llms-full.txt +5120 -0
  5. package/llms.txt +179 -0
  6. package/package.json +44 -0
  7. package/schematics/collection.json +15 -0
  8. package/schematics/migrate-material/component.js +241 -0
  9. package/schematics/migrate-material/index.js +463 -0
  10. package/schematics/migrate-material/report.js +125 -0
  11. package/schematics/migrate-material/schema.js +2 -0
  12. package/schematics/migrate-material/schema.json +32 -0
  13. package/schematics/migrate-material/tables.js +138 -0
  14. package/schematics/migrate-material/template.js +871 -0
  15. package/schematics/ng-add/html.js +151 -0
  16. package/schematics/ng-add/index.js +609 -0
  17. package/schematics/ng-add/init-script.js +108 -0
  18. package/schematics/ng-add/schema.js +2 -0
  19. package/schematics/ng-add/schema.json +34 -0
  20. package/schematics/package.json +3 -0
  21. package/styles/_daten.css +138 -0
  22. package/styles/_formulare.css +97 -0
  23. package/styles/_grundlage.css +424 -0
  24. package/styles/_konfigurator.css +216 -0
  25. package/styles/_navigation.css +179 -0
  26. package/styles/_overlays.css +86 -0
  27. package/styles/_rueckmeldung.css +100 -0
  28. package/styles/_werkzeuge.css +119 -0
  29. package/styles/themes/accents.css +91 -0
  30. package/styles/themes/base.css +26 -0
  31. package/styles/themes/contrast.css +67 -0
  32. package/styles/themes/light.css +74 -0
  33. package/styles/themes.css +21 -0
  34. package/styles/tokens.css +76 -0
  35. package/styles/zenit-ui.css +12 -0
  36. 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; }