@connectstudio/gastro-ui 0.3.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.
@@ -0,0 +1,359 @@
1
+ /* @connectstudio/gastro-ui — Default-Theme (docs/09, docs/10).
2
+ Ruhig, monochrom, kompakt: weiße Karte mit Hairline (Radius 16), Auswahl als
3
+ Ink-Fill statt Markenfarbe, enge Radien (10/12), Schatten fast flach.
4
+ Referenz: cal.com-Booker. Alles über --cg-*-Variablen überschreibbar;
5
+ die cg-*-Klassen sind der Styling-Vertrag. Dark nur auf Wunsch:
6
+ data-cg-theme="dark" oder "auto" (folgt prefers-color-scheme). */
7
+ .cg-root {
8
+ /* Flächen */
9
+ --cg-bg: #ffffff;
10
+ --cg-surface: #ffffff;
11
+ --cg-elevated: #ffffff; /* Karten-Innenflächen: Slots, Felder */
12
+ --cg-head: #ffffff; /* Kopfzeile der Karte */
13
+ --cg-tint: #f6f5f2; /* Skeleton, Segment-Schiene, leise Flächen */
14
+ --cg-line: #e7e5e0; /* Hairlines */
15
+ --cg-line-soft: #eeece7; /* deaktivierte Ränder */
16
+ --cg-line-strong: #d5d2cb; /* Hover-Ränder */
17
+
18
+ /* Text */
19
+ --cg-ink: #1b1a18;
20
+ --cg-ink-muted: #5b5851;
21
+ --cg-ink-soft: #7f7b74;
22
+ --cg-ink-disabled: #aaa69f;
23
+
24
+ /* Akzent = Auswahl. Default Ink (monochrom); Betriebe überschreiben --cg-accent. */
25
+ --cg-accent: #1b1a18;
26
+ --cg-accent-ink: #ffffff; /* Text auf Akzent-Fläche */
27
+ --cg-accent-text: #1b1a18; /* Akzent als Textfarbe (Links) */
28
+ --cg-accent-weak: #f3f2ee; /* Hinweisflächen */
29
+ --cg-accent-weak-text: #44423d;
30
+ --cg-cta: var(--cg-accent); /* Haupt-CTA folgt dem Akzent */
31
+ --cg-cta-ink: var(--cg-accent-ink);
32
+ --cg-status-bg: #f3f2ee;
33
+ --cg-status-ink: #44423d;
34
+ --cg-danger: #b91c1c;
35
+
36
+ /* Form */
37
+ --cg-radius-card: 16px;
38
+ --cg-radius-control: 10px; /* Buttons, Chips, Kalenderzellen */
39
+ --cg-radius-field: var(--cg-radius-control); /* Stepper, Inputs, Hinweise */
40
+ --cg-radius-slot: var(--cg-radius-control); /* Slot-Zellen */
41
+
42
+ /* Motion */
43
+ --cg-dur: 150ms;
44
+ --cg-ease: cubic-bezier(0.2, 0, 0, 1);
45
+ --cg-font: inherit;
46
+
47
+ /* Elevation: die Hairline trennt, nicht der Schatten. */
48
+ --cg-shadow: 0 1px 2px rgb(0 0 0 / 0.03), 0 1px 3px rgb(0 0 0 / 0.02);
49
+ --cg-shadow-raised: 0 2px 3px rgb(0 0 0 / 0.03), 0 2px 2px -1px rgb(0 0 0 / 0.03);
50
+
51
+ font-family: var(--cg-font);
52
+ color: var(--cg-ink);
53
+ font-size: 15px;
54
+ line-height: 1.45;
55
+ -webkit-font-smoothing: antialiased;
56
+ box-sizing: border-box;
57
+ }
58
+
59
+ /* Dark: gleiche Rollen, invertierte Werte. Auswahl wird Weiß auf Dunkel. */
60
+ .cg-root[data-cg-theme='dark'] { color-scheme: dark; }
61
+ .cg-root[data-cg-theme='dark'] {
62
+ --cg-bg: #0f0f0f;
63
+ --cg-surface: #171717;
64
+ --cg-elevated: #0f0f0f;
65
+ --cg-head: #171717;
66
+ --cg-tint: #1f1f1f;
67
+ --cg-line: #292929;
68
+ --cg-line-soft: #232323;
69
+ --cg-line-strong: #4d4d4d;
70
+ --cg-ink: #ededed;
71
+ --cg-ink-muted: #a3a3a3;
72
+ --cg-ink-soft: #8a8a8a;
73
+ --cg-ink-disabled: #5c5c5c;
74
+ --cg-accent: #ffffff;
75
+ --cg-accent-ink: #0a0a0a;
76
+ --cg-accent-text: #ededed;
77
+ --cg-accent-weak: #262626;
78
+ --cg-accent-weak-text: #d4d4d4;
79
+ --cg-status-bg: #262626;
80
+ --cg-status-ink: #d4d4d4;
81
+ --cg-danger: #f87171;
82
+ --cg-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
83
+ --cg-shadow-raised: 0 2px 3px rgb(0 0 0 / 0.35), 0 2px 2px -1px rgb(0 0 0 / 0.35);
84
+ }
85
+ @media (prefers-color-scheme: dark) {
86
+ .cg-root[data-cg-theme='auto'] { color-scheme: dark; }
87
+ .cg-root[data-cg-theme='auto'] {
88
+ --cg-bg: #0f0f0f;
89
+ --cg-surface: #171717;
90
+ --cg-elevated: #0f0f0f;
91
+ --cg-head: #171717;
92
+ --cg-tint: #1f1f1f;
93
+ --cg-line: #292929;
94
+ --cg-line-soft: #232323;
95
+ --cg-line-strong: #4d4d4d;
96
+ --cg-ink: #ededed;
97
+ --cg-ink-muted: #a3a3a3;
98
+ --cg-ink-soft: #8a8a8a;
99
+ --cg-ink-disabled: #5c5c5c;
100
+ --cg-accent: #ffffff;
101
+ --cg-accent-ink: #0a0a0a;
102
+ --cg-accent-text: #ededed;
103
+ --cg-accent-weak: #262626;
104
+ --cg-accent-weak-text: #d4d4d4;
105
+ --cg-status-bg: #262626;
106
+ --cg-status-ink: #d4d4d4;
107
+ --cg-danger: #f87171;
108
+ --cg-shadow: 0 1px 2px rgb(0 0 0 / 0.4);
109
+ --cg-shadow-raised: 0 2px 3px rgb(0 0 0 / 0.35), 0 2px 2px -1px rgb(0 0 0 / 0.35);
110
+ }
111
+ }
112
+
113
+ /* Reset nur innerhalb der Komponenten (.cg-widget, .cg-menu) — NICHT auf .cg-root: die Plattform nutzt
114
+ .cg-root als Token-Träger für ganze Seiten, und ungelayerte Regeln würden dort Tailwind-Utilities schlagen. */
115
+ :where(.cg-widget, .cg-menu) :where(*, *::before, *::after) { box-sizing: inherit; }
116
+ :where(.cg-widget, .cg-menu) :where(button, input, textarea) { font: inherit; color: inherit; }
117
+ .cg-root :focus-visible { outline: 2px solid var(--cg-ink); outline-offset: 2px; }
118
+
119
+ /* ---------- Widget-Karte ---------- */
120
+ .cg-widget {
121
+ max-width: 560px;
122
+ margin: 0 auto;
123
+ background: var(--cg-surface);
124
+ border: 1px solid var(--cg-line);
125
+ border-radius: var(--cg-radius-card);
126
+ box-shadow: var(--cg-shadow);
127
+ overflow: hidden;
128
+ }
129
+ .cg-widget__header {
130
+ display: flex; align-items: center; gap: 10px;
131
+ min-height: 48px; padding: 12px 20px;
132
+ background: var(--cg-head);
133
+ border-bottom: 1px solid var(--cg-line);
134
+ color: var(--cg-ink);
135
+ }
136
+ .cg-widget__title { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 15px; font-weight: 600; letter-spacing: -0.01em; }
137
+ .cg-widget__body { display: flex; flex-direction: column; gap: 20px; padding: 20px; }
138
+ @media (min-width: 560px) { .cg-widget__body { padding: 24px; } }
139
+ .cg-widget__muted { margin: 0; color: var(--cg-ink-soft); }
140
+ .cg-widget__small { font-size: 13px; }
141
+ .cg-widget__center { text-align: center; }
142
+ .cg-widget__hint,
143
+ .cg-widget__message {
144
+ margin: 0; padding: 10px 14px;
145
+ border: 1px solid var(--cg-line); border-radius: var(--cg-radius-field);
146
+ background: var(--cg-elevated); font-size: 14px; color: var(--cg-ink-muted);
147
+ }
148
+ .cg-widget__message--error { color: var(--cg-danger); }
149
+ .cg-widget__empty { grid-column: 1 / -1; margin: 0; color: var(--cg-ink-soft); font-size: 14px; }
150
+ .cg-widget__empty a { color: var(--cg-accent-text); text-decoration: underline; text-underline-offset: 2px; }
151
+
152
+ /* ---------- Sektionen: kleines Label über der Gruppe (Referenz-Rhythmus) ---------- */
153
+ .cg-section { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
154
+ .cg-label {
155
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
156
+ font-size: 13px; font-weight: 500; color: var(--cg-ink-soft);
157
+ letter-spacing: 0.01em;
158
+ }
159
+ .cg-label__value { color: var(--cg-ink); font-weight: 500; }
160
+
161
+ /* ---------- Datumsstreifen: Wochentag über Tageszahl, heute mit Punkt ---------- */
162
+ .cg-days {
163
+ display: flex; gap: 8px; overflow-x: auto; padding: 2px; margin: -2px;
164
+ scrollbar-width: none; scroll-snap-type: x proximity;
165
+ -webkit-mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
166
+ mask-image: linear-gradient(90deg, #000 calc(100% - 28px), transparent);
167
+ }
168
+ .cg-days::-webkit-scrollbar { display: none; }
169
+ .cg-day {
170
+ flex: 0 0 auto; scroll-snap-align: start;
171
+ display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px;
172
+ width: 52px; min-height: 60px; padding: 6px 0;
173
+ background: var(--cg-elevated); border: 1px solid var(--cg-line);
174
+ border-radius: var(--cg-radius-control); cursor: pointer;
175
+ transition: background-color var(--cg-dur) var(--cg-ease), border-color var(--cg-dur) var(--cg-ease), color var(--cg-dur) var(--cg-ease), transform var(--cg-dur) var(--cg-ease);
176
+ }
177
+ .cg-day:active { transform: scale(0.97); }
178
+ .cg-day__wd { font-size: 12px; font-weight: 400; color: var(--cg-ink-soft); text-transform: uppercase; letter-spacing: 0.04em; }
179
+ .cg-day__num { font-size: 16px; font-weight: 500; font-variant-numeric: tabular-nums; line-height: 1.1; }
180
+ .cg-day__dot { width: 3px; height: 3px; border-radius: 50%; background: var(--cg-ink-disabled); }
181
+ .cg-day--selected, .cg-day--selected:hover { background: var(--cg-accent); border-color: var(--cg-accent); color: var(--cg-accent-ink); }
182
+ .cg-day--selected .cg-day__wd { color: var(--cg-accent-ink); opacity: 0.7; }
183
+ .cg-day--selected .cg-day__dot { background: var(--cg-accent-ink); }
184
+ .cg-day:disabled { opacity: 0.5; cursor: default; }
185
+ @media (hover: hover) { .cg-day:not(.cg-day--selected):hover { border-color: var(--cg-line-strong); } }
186
+
187
+ /* ---------- Segment-Control (Mittag/Abend) — Schiene + aktive Karte ---------- */
188
+ .cg-seg { display: inline-flex; gap: 2px; padding: 3px; background: var(--cg-tint); border-radius: var(--cg-radius-control); }
189
+ .cg-seg__btn {
190
+ min-height: 30px; padding: 0 12px; border: 0; background: none; border-radius: 7px;
191
+ font-size: 13px; font-weight: 500; color: var(--cg-ink-soft); cursor: pointer;
192
+ transition: background-color var(--cg-dur) var(--cg-ease), color var(--cg-dur) var(--cg-ease), box-shadow var(--cg-dur) var(--cg-ease);
193
+ }
194
+ .cg-seg__btn--active { background: var(--cg-surface); color: var(--cg-ink); box-shadow: var(--cg-shadow-raised); }
195
+ @media (hover: hover) { .cg-seg__btn:not(.cg-seg__btn--active):hover { color: var(--cg-ink); } }
196
+ .cg-more { align-self: center; background: none; border: 0; padding: 6px 12px; min-height: 40px; font-size: 13px; font-weight: 500; color: var(--cg-ink-muted); cursor: pointer; border-radius: var(--cg-radius-control); }
197
+ @media (hover: hover) { .cg-more:hover { color: var(--cg-ink); background: var(--cg-tint); } }
198
+
199
+ /* ---------- Personen-Stepper ---------- */
200
+ .cg-stepper {
201
+ display: flex; align-items: center; justify-content: space-between; gap: 12px;
202
+ min-height: 52px; padding: 6px 8px 6px 14px;
203
+ border: 1px solid var(--cg-line); border-radius: var(--cg-radius-field);
204
+ background: var(--cg-elevated);
205
+ }
206
+ .cg-stepper__label { font-size: 15px; color: var(--cg-ink-muted); }
207
+ .cg-stepper__controls { display: flex; align-items: center; gap: 6px; }
208
+ .cg-stepper__btn {
209
+ width: 38px; height: 38px; border-radius: 8px;
210
+ background: var(--cg-surface); border: 1px solid var(--cg-line); cursor: pointer;
211
+ font-size: 17px; font-weight: 400; line-height: 1; color: var(--cg-ink-muted);
212
+ transition: border-color var(--cg-dur) var(--cg-ease), color var(--cg-dur) var(--cg-ease), transform var(--cg-dur) var(--cg-ease);
213
+ }
214
+ .cg-stepper__btn:active { transform: scale(0.95); }
215
+ .cg-stepper__btn:disabled { opacity: 0.4; cursor: default; }
216
+ @media (hover: hover) { .cg-stepper__btn:not(:disabled):hover { border-color: var(--cg-line-strong); color: var(--cg-ink); } }
217
+ .cg-stepper__value { min-width: 30px; text-align: center; font-size: 16px; font-weight: 600; color: var(--cg-ink); font-variant-numeric: tabular-nums; }
218
+
219
+ /* ---------- Bereichs-Wahl (tables-Modus) ---------- */
220
+ .cg-zones {
221
+ display: grid; grid-auto-flow: column; grid-auto-columns: minmax(104px, 1fr);
222
+ gap: 8px; overflow-x: auto; padding: 2px; margin: -2px; scrollbar-width: none;
223
+ }
224
+ .cg-zones::-webkit-scrollbar { display: none; }
225
+ .cg-zone {
226
+ display: flex; flex-direction: column; padding: 0; overflow: hidden; text-align: center;
227
+ background: var(--cg-elevated); border: 1px solid var(--cg-line); border-radius: var(--cg-radius-control);
228
+ cursor: pointer;
229
+ transition: border-color var(--cg-dur) var(--cg-ease), background-color var(--cg-dur) var(--cg-ease), transform var(--cg-dur) var(--cg-ease);
230
+ }
231
+ .cg-zone:active { transform: scale(0.98); }
232
+ @media (hover: hover) { .cg-zone:not(.cg-zone--selected):hover { border-color: var(--cg-line-strong); } }
233
+ .cg-zone__img { display: block; width: 100%; height: 56px; object-fit: cover; background: var(--cg-tint); }
234
+ .cg-zone__img--empty { display: grid; place-items: center; color: var(--cg-ink-disabled); font-size: 18px; font-weight: 500; }
235
+ .cg-zone__name { display: block; padding: 8px 10px; font-size: 13px; font-weight: 500; color: var(--cg-ink); overflow-wrap: anywhere; }
236
+ .cg-zone--any { justify-content: center; min-height: 92px; }
237
+ .cg-zone--selected { border-color: var(--cg-accent); }
238
+ .cg-zone--selected .cg-zone__name { background: var(--cg-accent); color: var(--cg-accent-ink); }
239
+ .cg-zone--any.cg-zone--selected { background: var(--cg-tint); }
240
+ .cg-zone--any.cg-zone--selected .cg-zone__name { background: none; color: var(--cg-ink); }
241
+
242
+ /* ---------- Slots ---------- */
243
+ .cg-slotarea { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
244
+ .cg-slots { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
245
+ @media (max-width: 350px) { .cg-slots { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
246
+ .cg-slot {
247
+ min-height: 44px; border-radius: var(--cg-radius-slot);
248
+ background: var(--cg-elevated); border: 1px solid var(--cg-line);
249
+ box-shadow: var(--cg-shadow-raised);
250
+ font-size: 14px; font-weight: 500; font-variant-numeric: tabular-nums; cursor: pointer;
251
+ transition: background-color var(--cg-dur) var(--cg-ease), border-color var(--cg-dur) var(--cg-ease), color var(--cg-dur) var(--cg-ease), transform var(--cg-dur) var(--cg-ease);
252
+ }
253
+ .cg-slot:active { transform: scale(0.97); }
254
+ .cg-slot--selected, .cg-slot--selected:hover { background: var(--cg-accent); border-color: var(--cg-accent); color: var(--cg-accent-ink); }
255
+ .cg-slot--full, .cg-slot--full:hover { background: var(--cg-surface); border-color: var(--cg-line-soft); color: var(--cg-ink-disabled); box-shadow: none; text-decoration: line-through; cursor: default; }
256
+ @media (hover: hover) { .cg-slot:not(.cg-slot--selected):not(.cg-slot--full):hover { border-color: var(--cg-line-strong); } }
257
+ .cg-skeleton { background: var(--cg-tint); border: 0; box-shadow: none; border-radius: var(--cg-radius-slot); min-height: 44px; animation: cg-pulse 1.2s var(--cg-ease) infinite; }
258
+ .cg-menu .cg-skeleton { min-height: 18px; margin: 8px 0; border-radius: 6px; }
259
+ @keyframes cg-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.55; } }
260
+
261
+ /* ---------- Formular ---------- */
262
+ .cg-form-wrap { animation: cg-enter var(--cg-dur) var(--cg-ease) both; }
263
+ @keyframes cg-enter { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
264
+ .cg-form { display: flex; flex-direction: column; gap: 12px; }
265
+ .cg-field { display: flex; flex-direction: column; gap: 5px; }
266
+ .cg-field label { font-size: 13px; font-weight: 500; color: var(--cg-ink-soft); }
267
+ .cg-field input, .cg-field textarea {
268
+ box-sizing: border-box; width: 100%; min-height: 44px; padding: 11px 14px;
269
+ background: var(--cg-elevated); border: 1px solid var(--cg-line); border-radius: var(--cg-radius-field);
270
+ font-size: 16px; color: var(--cg-ink);
271
+ transition: border-color var(--cg-dur) var(--cg-ease), box-shadow var(--cg-dur) var(--cg-ease);
272
+ }
273
+ @media (min-width: 560px) { .cg-field input, .cg-field textarea { font-size: 15px; } }
274
+ .cg-field input::placeholder, .cg-field textarea::placeholder { color: var(--cg-ink-disabled); }
275
+ .cg-field textarea { resize: vertical; }
276
+ .cg-field input:focus, .cg-field textarea:focus { outline: none; border-color: var(--cg-ink); box-shadow: 0 0 0 3px color-mix(in srgb, var(--cg-ink) 12%, transparent); }
277
+ /* Ernährung (docs/15): Chips als Checkboxen, Zustand rein per CSS. Rhythmus wie das Formular:
278
+ 12px zwischen Gruppen, Label + 5px zum Inhalt (.cg-field). */
279
+ .cg-dietary { display: flex; flex-direction: column; gap: 12px; min-width: 0; }
280
+ .cg-field__label { font-size: 13px; font-weight: 500; color: var(--cg-ink-soft); }
281
+ .cg-chips { display: flex; flex-wrap: wrap; gap: 8px; }
282
+ .cg-chip { position: relative; display: inline-flex; align-items: center; min-height: 36px; padding: 0 14px; border: 1px solid var(--cg-line); border-radius: 999px; background: var(--cg-elevated); font-size: 14px; color: var(--cg-ink); cursor: pointer; user-select: none; transition: background var(--cg-dur) var(--cg-ease), border-color var(--cg-dur) var(--cg-ease), color var(--cg-dur) var(--cg-ease); }
283
+ .cg-chip input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
284
+ .cg-chip span { color: inherit; }
285
+ .cg-chip:has(input:checked) { background: var(--cg-accent); border-color: var(--cg-accent); color: var(--cg-accent-ink, #fff); }
286
+ .cg-chip:has(input:focus-visible) { outline: 2px solid var(--cg-ink); outline-offset: 2px; }
287
+ /* <details> stellt seine Kinder in einen internen Slot — Abstände deshalb über einen eigenen Body,
288
+ und box-sizing explizit, weil der Slot content-box vererbt. */
289
+ .cg-details { display: block; min-width: 0; }
290
+ .cg-details__body { display: flex; flex-direction: column; gap: 12px; min-width: 0; margin-top: 12px; }
291
+ .cg-details__body input { box-sizing: border-box; }
292
+ .cg-details summary { display: inline-flex; align-items: center; gap: 6px; width: fit-content; min-height: 24px; cursor: pointer; font-size: 13px; font-weight: 500; color: var(--cg-ink-soft); list-style: none; }
293
+ .cg-details summary::-webkit-details-marker { display: none; }
294
+ .cg-details summary:hover, .cg-details[open] summary { color: var(--cg-ink); }
295
+ .cg-details__chevron { display: inline-block; width: 7px; height: 7px; border-right: 1.5px solid currentColor; border-bottom: 1.5px solid currentColor; transform: rotate(-45deg); transition: transform var(--cg-dur) var(--cg-ease); }
296
+ .cg-details[open] .cg-details__chevron { transform: rotate(45deg); }
297
+ .cg-consent { display: flex; gap: 10px; align-items: flex-start; font-size: 13px; line-height: 1.5; color: var(--cg-ink-soft); cursor: pointer; padding-top: 2px; }
298
+ .cg-consent input { width: 18px; height: 18px; margin: 1px 0 0; accent-color: var(--cg-accent); flex: 0 0 auto; }
299
+ .cg-summary {
300
+ display: flex; align-items: center; justify-content: center; gap: 8px;
301
+ margin: 0; padding: 10px 14px; border-radius: var(--cg-radius-field);
302
+ background: var(--cg-tint); font-size: 14px; font-weight: 500; color: var(--cg-ink);
303
+ font-variant-numeric: tabular-nums; overflow-wrap: anywhere;
304
+ }
305
+
306
+ .cg-widget__actions { display: flex; align-items: stretch; gap: 8px; width: 100%; }
307
+ .cg-widget__actions .cg-btn--primary { flex: 1 1 auto; width: auto; min-height: 48px; }
308
+ .cg-btn--icon { flex: 0 0 48px; width: 48px; min-height: 48px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
309
+
310
+ /* ---------- Buttons ---------- */
311
+ .cg-btn {
312
+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;
313
+ min-height: 44px; min-width: 0; padding: 0 18px; border-radius: var(--cg-radius-control);
314
+ font-size: 15px; font-weight: 500; cursor: pointer; text-decoration: none; white-space: nowrap;
315
+ transition: opacity var(--cg-dur) var(--cg-ease), background-color var(--cg-dur) var(--cg-ease), border-color var(--cg-dur) var(--cg-ease), transform var(--cg-dur) var(--cg-ease);
316
+ }
317
+ .cg-btn:active { transform: scale(0.98); }
318
+ .cg-btn--primary { width: 100%; min-height: 48px; background: var(--cg-cta); border: 1px solid var(--cg-cta); color: var(--cg-cta-ink); }
319
+ .cg-btn--primary:hover { opacity: 0.9; }
320
+ .cg-btn--primary:disabled { background: var(--cg-tint); border-color: var(--cg-line); color: var(--cg-ink-disabled); opacity: 1; cursor: default; }
321
+ .cg-btn--secondary { background: var(--cg-elevated); border: 1px solid var(--cg-line); color: var(--cg-ink); box-shadow: var(--cg-shadow-raised); }
322
+ @media (hover: hover) { .cg-btn--secondary:hover { border-color: var(--cg-line-strong); } }
323
+
324
+ /* ---------- Status-/Tag-Pillen ---------- */
325
+ .cg-pill { display: inline-flex; align-items: center; min-height: 26px; padding: 0 10px; border-radius: 6px; font-size: 13px; font-weight: 500; white-space: nowrap; }
326
+ .cg-pill--status { background: var(--cg-status-bg); color: var(--cg-status-ink); }
327
+ .cg-pill--tag { background: var(--cg-accent-weak); color: var(--cg-accent-weak-text); }
328
+ .cg-pill--outline { background: var(--cg-elevated); border: 1px solid var(--cg-line); color: var(--cg-ink-soft); }
329
+
330
+ /* ---------- Bestätigung ---------- */
331
+ .cg-widget__success { align-items: center; text-align: center; gap: 12px; padding-top: 8px; padding-bottom: 8px; }
332
+ .cg-widget__success-icon { width: 44px; height: 44px; border-radius: 50%; display: grid; place-items: center; background: var(--cg-accent); color: var(--cg-accent-ink); font-size: 20px; }
333
+ .cg-widget__success-title { margin: 0; font-size: 19px; font-weight: 600; letter-spacing: -0.01em; }
334
+ .cg-widget__success-when { margin: 0; font-size: 15px; color: var(--cg-ink-muted); font-variant-numeric: tabular-nums; }
335
+
336
+ /* ---------- Speisekarte ---------- */
337
+ .cg-menu { display: flex; flex-direction: column; gap: 28px; }
338
+ .cg-menu__category-title { margin: 0 0 10px; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
339
+ .cg-menu__items { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; }
340
+ .cg-menu__item { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; padding: 12px 0; border-bottom: 1px solid var(--cg-line); }
341
+ .cg-menu__item:last-child { border-bottom: 0; }
342
+ .cg-menu__item-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
343
+ .cg-menu__item-name { font-size: 15px; font-weight: 500; }
344
+ .cg-menu__item-desc { font-size: 14px; color: var(--cg-ink-soft); }
345
+ .cg-menu__item-price { font-size: 15px; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--cg-ink-muted); }
346
+ .cg-menu__item--unavailable .cg-menu__item-name { text-decoration: line-through; color: var(--cg-ink-disabled); }
347
+ .cg-menu__footnote { margin-left: 4px; font-size: 11px; color: var(--cg-ink-soft); }
348
+ .cg-menu__badge { display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 6px; background: var(--cg-tint); color: var(--cg-ink-muted); font-size: 12px; font-weight: 500; vertical-align: middle; }
349
+ .cg-menu__badge--muted { background: var(--cg-tint); color: var(--cg-ink-soft); }
350
+ .cg-menu__legend { padding-top: 16px; border-top: 1px solid var(--cg-line); font-size: 13px; color: var(--cg-ink-soft); }
351
+ .cg-menu__legend-title { margin: 0 0 8px; font-weight: 500; color: var(--cg-ink-muted); }
352
+ .cg-menu__legend-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 4px 16px; margin: 0; }
353
+ .cg-menu__legend-list div { display: flex; gap: 8px; }
354
+ .cg-menu__legend-list dt { width: 18px; color: var(--cg-ink-muted); font-weight: 500; }
355
+ .cg-menu__legend-list dd { margin: 0; }
356
+
357
+ @media (prefers-reduced-motion: reduce) {
358
+ .cg-root *, .cg-root *::before, .cg-root *::after { animation: none !important; transition: none !important; }
359
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * CSS-Variablen-Vertrag (headless-first, docs/09) — Namen und Defaults aus docs/10.
3
+ * Kunden-Websites überschreiben diese Variablen; die Komponenten bringen nur das
4
+ * Default-Theme (Connect-Gastro-Identität) mit. Nichts ist hart codiert.
5
+ */
6
+ export declare const CSS_VARIABLES: {
7
+ readonly '--cg-bg': "App-/Seitenhintergrund (Default Weiß)";
8
+ readonly '--cg-surface': "Karten / Felder (Default Weiß)";
9
+ readonly '--cg-ink': "Text und die eine Hauptaktion";
10
+ readonly '--cg-ink-muted': "Sekundärtext";
11
+ readonly '--cg-accent': "Akzent: Auswahl, aktiv, Marke (Default Ink — monochrom)";
12
+ readonly '--cg-accent-weak': "Akzent-Tint: Badges, Hover, Skeleton";
13
+ readonly '--cg-line': "Hairlines, Borders";
14
+ readonly '--cg-success': "gedeckter Erfolg (nie allein, immer mit Text)";
15
+ readonly '--cg-danger': "destruktiv: nur Text/Border im Ruhezustand";
16
+ readonly '--cg-radius-card': "Eckenradius Karten";
17
+ readonly '--cg-radius-control': "Eckenradius Buttons/Chips/Inputs (Default 10px)";
18
+ readonly '--cg-dur': "Transition-Dauer";
19
+ readonly '--cg-ease': "Easing (ease-out)";
20
+ readonly '--cg-font': "Schriftfamilie (Default: erbt von der Website)";
21
+ };
22
+ export type CssVariable = keyof typeof CSS_VARIABLES;
23
+ export declare const DEFAULT_THEME: Record<CssVariable, string>;
24
+ /** Preisformat (de-DE), Cents → "12,90 €" */
25
+ export declare function formatPrice(cents: number, locale?: string, currency?: string): string;
package/dist/theme.js ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * CSS-Variablen-Vertrag (headless-first, docs/09) — Namen und Defaults aus docs/10.
3
+ * Kunden-Websites überschreiben diese Variablen; die Komponenten bringen nur das
4
+ * Default-Theme (Connect-Gastro-Identität) mit. Nichts ist hart codiert.
5
+ */
6
+ export const CSS_VARIABLES = {
7
+ '--cg-bg': 'App-/Seitenhintergrund (Default Weiß)',
8
+ '--cg-surface': 'Karten / Felder (Default Weiß)',
9
+ '--cg-ink': 'Text und die eine Hauptaktion',
10
+ '--cg-ink-muted': 'Sekundärtext',
11
+ '--cg-accent': 'Akzent: Auswahl, aktiv, Marke (Default Ink — monochrom)',
12
+ '--cg-accent-weak': 'Akzent-Tint: Badges, Hover, Skeleton',
13
+ '--cg-line': 'Hairlines, Borders',
14
+ '--cg-success': 'gedeckter Erfolg (nie allein, immer mit Text)',
15
+ '--cg-danger': 'destruktiv: nur Text/Border im Ruhezustand',
16
+ '--cg-radius-card': 'Eckenradius Karten',
17
+ '--cg-radius-control': 'Eckenradius Buttons/Chips/Inputs (Default 10px)',
18
+ '--cg-dur': 'Transition-Dauer',
19
+ '--cg-ease': 'Easing (ease-out)',
20
+ '--cg-font': 'Schriftfamilie (Default: erbt von der Website)',
21
+ };
22
+ export const DEFAULT_THEME = {
23
+ '--cg-bg': '#FFFFFF',
24
+ '--cg-surface': '#FFFFFF',
25
+ '--cg-ink': '#0A0A0A',
26
+ '--cg-ink-muted': '#525252',
27
+ '--cg-accent': '#0A0A0A',
28
+ '--cg-accent-weak': '#F5F5F5',
29
+ '--cg-line': '#E5E5E5',
30
+ '--cg-success': '#15803D',
31
+ '--cg-danger': '#B91C1C',
32
+ '--cg-radius-card': '16px',
33
+ '--cg-radius-control': '10px',
34
+ '--cg-dur': '150ms',
35
+ '--cg-ease': 'cubic-bezier(0.2, 0, 0, 1)',
36
+ '--cg-font': 'inherit',
37
+ };
38
+ /** Preisformat (de-DE), Cents → "12,90 €" */
39
+ export function formatPrice(cents, locale = 'de-DE', currency = 'EUR') {
40
+ return new Intl.NumberFormat(locale, { style: 'currency', currency }).format(cents / 100);
41
+ }
42
+ //# sourceMappingURL=theme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"theme.js","sourceRoot":"","sources":["../src/theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS,EAAE,uCAAuC;IAClD,cAAc,EAAE,gCAAgC;IAChD,UAAU,EAAE,+BAA+B;IAC3C,gBAAgB,EAAE,cAAc;IAChC,aAAa,EAAE,yDAAyD;IACxE,kBAAkB,EAAE,sCAAsC;IAC1D,WAAW,EAAE,oBAAoB;IACjC,cAAc,EAAE,+CAA+C;IAC/D,aAAa,EAAE,4CAA4C;IAC3D,kBAAkB,EAAE,oBAAoB;IACxC,qBAAqB,EAAE,iDAAiD;IACxE,UAAU,EAAE,kBAAkB;IAC9B,WAAW,EAAE,mBAAmB;IAChC,WAAW,EAAE,gDAAgD;CACrD,CAAA;AAIV,MAAM,CAAC,MAAM,aAAa,GAAgC;IACxD,SAAS,EAAE,SAAS;IACpB,cAAc,EAAE,SAAS;IACzB,UAAU,EAAE,SAAS;IACrB,gBAAgB,EAAE,SAAS;IAC3B,aAAa,EAAE,SAAS;IACxB,kBAAkB,EAAE,SAAS;IAC7B,WAAW,EAAE,SAAS;IACtB,cAAc,EAAE,SAAS;IACzB,aAAa,EAAE,SAAS;IACxB,kBAAkB,EAAE,MAAM;IAC1B,qBAAqB,EAAE,MAAM;IAC7B,UAAU,EAAE,OAAO;IACnB,WAAW,EAAE,4BAA4B;IACzC,WAAW,EAAE,SAAS;CACvB,CAAA;AAED,6CAA6C;AAC7C,MAAM,UAAU,WAAW,CAAC,KAAa,EAAE,MAAM,GAAG,OAAO,EAAE,QAAQ,GAAG,KAAK;IAC3E,OAAO,IAAI,IAAI,CAAC,YAAY,CAAC,MAAM,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,GAAG,GAAG,CAAC,CAAA;AAC3F,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Öffentliche Datentypen — Spiegel der Public API (/api/public/v1, docs/09).
3
+ * Logik (Verfügbarkeit, Validierung, Consent) bleibt serverseitig.
4
+ */
5
+ export interface PublicMenuPriceOption {
6
+ name: string;
7
+ priceCents: number;
8
+ }
9
+ export interface PublicMenuItem {
10
+ id: string;
11
+ name: string;
12
+ description: string | null;
13
+ priceCents: number;
14
+ allergens: string[];
15
+ additives: string[];
16
+ isAvailable: boolean;
17
+ isSpecial: boolean;
18
+ /** seit Speisekarte v2 (docs/14) — optional, ältere Payloads haben die Felder nicht */
19
+ imageUrl?: string | null;
20
+ tags?: string[];
21
+ /** Größen/Varianten mit absolutem Preis */
22
+ variants?: PublicMenuPriceOption[];
23
+ /** Aufpreis-Optionen */
24
+ extras?: PublicMenuPriceOption[];
25
+ }
26
+ export interface PublicMenuCategory {
27
+ id: string;
28
+ name: string;
29
+ items: PublicMenuItem[];
30
+ /** seit v2: Untergruppe einer Kategorie (eine Ebene); flache Liste, Kinder folgen dem Elternteil */
31
+ parentId?: string | null;
32
+ description?: string | null;
33
+ icon?: string | null;
34
+ imageUrl?: string | null;
35
+ }
36
+ export interface PublicMenuOffer {
37
+ id: string;
38
+ title: string;
39
+ description: string | null;
40
+ icon: string | null;
41
+ /** 0 = Sonntag … 6 = Samstag; leer = täglich */
42
+ weekdays: number[];
43
+ startLocal: string | null;
44
+ endLocal: string | null;
45
+ items: {
46
+ itemId: string;
47
+ offerPriceCents: number | null;
48
+ }[];
49
+ }
50
+ export interface PublicAvailabilitySlot {
51
+ /** ISO 8601 mit Zeitzone */
52
+ slotStart: string;
53
+ remainingCovers: number;
54
+ maxPartySize: number;
55
+ }
56
+ export type PublicBookingError = 'SLOT_FULL' | 'OUTSIDE_HOURS' | 'VALIDATION' | 'NOT_FOUND' | 'RATE_LIMITED' | 'UNKNOWN';
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@connectstudio/gastro-ui",
3
+ "version": "0.3.0",
4
+ "description": "Buchungs-Widget und Speisekarte für Connect-Gastro-Kunden-Websites (headless-first, CSS-Variablen).",
5
+ "license": "UNLICENSED",
6
+ "private": false,
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/tomheyden/connect-gastro.git",
10
+ "directory": "packages/gastro-ui"
11
+ },
12
+ "homepage": "https://gastro.connectstudio.io",
13
+ "keywords": [
14
+ "restaurant",
15
+ "reservierung",
16
+ "booking-widget",
17
+ "speisekarte",
18
+ "react"
19
+ ],
20
+ "publishConfig": {
21
+ "access": "public"
22
+ },
23
+ "type": "module",
24
+ "sideEffects": [
25
+ "./dist/styles.css"
26
+ ],
27
+ "main": "./dist/index.js",
28
+ "module": "./dist/index.js",
29
+ "types": "./dist/index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./dist/index.d.ts",
33
+ "import": "./dist/index.js",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./styles.css": "./dist/styles.css",
37
+ "./package.json": "./package.json"
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "CHANGELOG.md"
43
+ ],
44
+ "scripts": {
45
+ "build": "rm -rf dist && tsc -p tsconfig.build.json && cp src/styles.css dist/styles.css",
46
+ "dev": "tsc -p tsconfig.build.json --watch",
47
+ "typecheck": "tsc --noEmit",
48
+ "lint": "echo \"(gastro-ui) Lint über Plattform-ESLint in Phase 4\"",
49
+ "test": "vitest run",
50
+ "prepublishOnly": "pnpm build && pnpm test"
51
+ },
52
+ "peerDependencies": {
53
+ "react": "^18.2.0 || ^19.0.0",
54
+ "react-dom": "^18.2.0 || ^19.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/react": "^19.2.18",
58
+ "@types/react-dom": "^19.2.4",
59
+ "react": "19.2.8",
60
+ "react-dom": "19.2.8",
61
+ "typescript": "^5.9.3",
62
+ "vitest": "^4.1.11"
63
+ }
64
+ }