@astronox/core 0.5.13 → 0.5.14

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 (34) hide show
  1. package/package.json +1 -1
  2. package/src/components/actions/ThemeToggle/ThemeToggle.astro +11 -0
  3. package/src/components/actions/ThemeToggle/ThemeToggle.css +4 -1
  4. package/src/components/actions/ThemeToggle/themeToggleController.ts +1 -0
  5. package/src/components/content/CardLink/CardLink.astro +2 -0
  6. package/src/components/content/CardLink/CardLink.css +50 -5
  7. package/src/components/feedback/SeasonalDecorations/SeasonalDecorations.astro +46 -1
  8. package/src/components/feedback/SeasonalDecorations/SeasonalDecorations.css +51 -1
  9. package/src/components/feedback/SeasonalDecorations/snowfall.ts +38 -0
  10. package/src/components/feedback/ThemeBootstrap/ThemeBootstrap.astro +51 -26
  11. package/src/components/icons/CurrencyIcon.css +2 -2
  12. package/src/components/icons/FirTreeIcon.astro +32 -0
  13. package/src/components/icons/GiftIcon.astro +34 -0
  14. package/src/components/index.ts +2 -0
  15. package/src/components/navigation/Header.css +22 -3
  16. package/src/components/navigation/NavigationBrand.astro +38 -19
  17. package/src/components/navigation/NavigationBrand.css +12 -2
  18. package/src/config/siteConfig.ts +24 -8
  19. package/src/display/contracts.ts +2 -1
  20. package/src/layouts/BaseLayout.astro +6 -2
  21. package/src/patterns/Modal/modalController.ts +16 -0
  22. package/src/patterns/RewardCalendar/RewardCalendar.astro +174 -0
  23. package/src/patterns/RewardCalendar/RewardCalendar.css +406 -0
  24. package/src/patterns/RewardCalendar/RewardPreview.astro +18 -0
  25. package/src/patterns/RewardCalendar/contracts.ts +55 -0
  26. package/src/patterns/RewardCalendar/rewardCalendarController.ts +268 -0
  27. package/src/patterns/index.ts +3 -0
  28. package/src/styles/base.css +6 -3
  29. package/src/styles/style.css +2 -0
  30. package/src/styles/tokens.css +6 -0
  31. package/src/theme/christmas-garland.svg +28 -0
  32. package/src/theme/christmas.css +25 -0
  33. package/src/theme/contracts.ts +7 -3
  34. package/src/theme/halloween.css +1 -0
@@ -0,0 +1,406 @@
1
+ .reward-calendar {
2
+ --reward-ribbon: var(--nox-reward-ribbon);
3
+
4
+ min-inline-size: 0;
5
+ }
6
+
7
+ .reward-calendar__status {
8
+ margin-block-end: var(--nox-space-4);
9
+ color: var(--nox-text-muted);
10
+ }
11
+
12
+ .reward-calendar__days {
13
+ display: grid;
14
+ grid-template-columns: repeat(6, minmax(0, 1fr));
15
+ gap: var(--nox-space-3);
16
+ padding: 0;
17
+ margin: 0;
18
+ list-style: none;
19
+ }
20
+
21
+ .reward-calendar__days > li {
22
+ min-inline-size: 0;
23
+ }
24
+
25
+ .reward-calendar__door,
26
+ .reward-calendar__reward {
27
+ --reward-color: var(--nox-reward-green);
28
+ --reward-paper: var(--reward-color);
29
+ --reward-ink: color-mix(
30
+ in srgb,
31
+ var(--reward-color) 75%,
32
+ var(--nox-theme-text-base)
33
+ );
34
+ }
35
+
36
+ .reward-calendar__door[data-reward-color='red'],
37
+ .reward-calendar__reward[data-reward-color='red'] {
38
+ --reward-color: var(--nox-reward-red);
39
+ }
40
+
41
+ .reward-calendar__door[data-reward-color='gold'],
42
+ .reward-calendar__reward[data-reward-color='gold'] {
43
+ --reward-color: var(--nox-reward-gold);
44
+ }
45
+
46
+ .reward-calendar__door {
47
+ position: relative;
48
+ isolation: isolate;
49
+ display: grid;
50
+ inline-size: 100%;
51
+ min-block-size: 10rem;
52
+ height: 100%;
53
+ overflow: hidden;
54
+ border: var(--nox-border-width) solid var(--nox-border);
55
+ border-radius: var(--nox-radius-md);
56
+ background: var(--nox-surface-strong);
57
+ color: var(--nox-text);
58
+ font: inherit;
59
+ text-align: center;
60
+ cursor: pointer;
61
+ padding: 0;
62
+ }
63
+
64
+ .reward-calendar__door:focus-visible {
65
+ outline: var(--nox-focus-ring-width) solid var(--nox-focus-ring);
66
+ outline-offset: var(--nox-focus-ring-offset);
67
+ }
68
+
69
+ .reward-calendar__door[aria-disabled='true'] {
70
+ cursor: default;
71
+ }
72
+
73
+ .reward-calendar__door[data-state='locked'] {
74
+ --reward-paper: color-mix(
75
+ in srgb,
76
+ var(--nox-text-muted) 22%,
77
+ var(--nox-surface)
78
+ );
79
+ --reward-ribbon: color-mix(
80
+ in srgb,
81
+ var(--nox-text-muted) 60%,
82
+ var(--nox-surface)
83
+ );
84
+ }
85
+
86
+ .reward-calendar__contents {
87
+ display: grid;
88
+ align-content: center;
89
+ justify-items: center;
90
+ gap: var(--nox-space-2);
91
+ padding: 2.5rem var(--nox-space-2) var(--nox-space-3);
92
+ opacity: 0;
93
+ }
94
+
95
+ .reward-calendar__preview {
96
+ --nox-currency-icon-color: var(--reward-ink);
97
+
98
+ display: grid;
99
+ place-items: center;
100
+ inline-size: 3.75rem;
101
+ aspect-ratio: 1;
102
+ padding: var(--nox-space-2);
103
+ border-radius: 50%;
104
+ border: var(--nox-border-width) solid
105
+ color-mix(in srgb, var(--reward-color) 65%, var(--nox-border));
106
+ background: var(--nox-surface-muted);
107
+ color: var(--reward-ink);
108
+ font-size: 2rem;
109
+ font-weight: var(--nox-font-weight-strong);
110
+ line-height: 1;
111
+ }
112
+
113
+ .reward-calendar__preview > [data-widget='media'] {
114
+ inline-size: 100%;
115
+ block-size: 100%;
116
+ min-inline-size: 0;
117
+ min-block-size: 0;
118
+ }
119
+
120
+ .reward-calendar__preview-icon {
121
+ inline-size: 100%;
122
+ block-size: 100%;
123
+ }
124
+
125
+ .reward-calendar__reward-name {
126
+ font-size: var(--nox-font-size-xs);
127
+ overflow-wrap: anywhere;
128
+ }
129
+
130
+ .reward-calendar__paper {
131
+ position: absolute;
132
+ inset-block: 0;
133
+ inline-size: 51%;
134
+ background: linear-gradient(
135
+ 135deg,
136
+ color-mix(in srgb, var(--reward-paper) 82%, white),
137
+ var(--reward-paper) 60%,
138
+ color-mix(in srgb, var(--reward-paper) 85%, black)
139
+ );
140
+ box-shadow: inset 0 1px color-mix(in srgb, white 25%, transparent);
141
+ pointer-events: none;
142
+ }
143
+
144
+ .reward-calendar__paper--left {
145
+ inset-inline-start: 0;
146
+ --paper-shift: -115%;
147
+ --paper-turn: -12deg;
148
+ }
149
+
150
+ .reward-calendar__paper--right {
151
+ inset-inline-end: 0;
152
+ --paper-shift: 115%;
153
+ --paper-turn: 12deg;
154
+ }
155
+
156
+ .reward-calendar__ribbon {
157
+ position: absolute;
158
+ background: linear-gradient(
159
+ 90deg,
160
+ color-mix(in srgb, var(--reward-ribbon) 80%, black),
161
+ var(--reward-ribbon) 20% 75%,
162
+ color-mix(in srgb, var(--reward-ribbon) 85%, white)
163
+ );
164
+ box-shadow: 0 2px 3px color-mix(in srgb, black 18%, transparent);
165
+ pointer-events: none;
166
+ }
167
+
168
+ .reward-calendar__ribbon--vertical {
169
+ inset-block: 0;
170
+ inset-inline-start: 43%;
171
+ inline-size: 14%;
172
+ }
173
+
174
+ .reward-calendar__ribbon--horizontal {
175
+ inset-inline: 0;
176
+ inset-block-start: 46%;
177
+ block-size: 13%;
178
+ }
179
+
180
+ .reward-calendar__bow {
181
+ position: absolute;
182
+ inset-inline-start: 50%;
183
+ inset-block-start: 50%;
184
+ inline-size: 1rem;
185
+ block-size: 1rem;
186
+ transform: translate(-50%, -50%);
187
+ border-radius: 30%;
188
+ background: var(--reward-ribbon);
189
+ box-shadow: 0 2px 3px color-mix(in srgb, black 22%, transparent);
190
+ pointer-events: none;
191
+ }
192
+
193
+ .reward-calendar__bow::before,
194
+ .reward-calendar__bow::after {
195
+ content: '';
196
+ position: absolute;
197
+ inset-block-end: 45%;
198
+ inline-size: 1.7rem;
199
+ block-size: 1.15rem;
200
+ border: 0.32rem solid var(--reward-ribbon);
201
+ box-shadow:
202
+ inset 0 1px color-mix(in srgb, white 40%, transparent),
203
+ 0 2px 2px color-mix(in srgb, black 18%, transparent);
204
+ }
205
+
206
+ .reward-calendar__bow::before {
207
+ inset-inline-end: 55%;
208
+ border-radius: 60% 65% 15% 60%;
209
+ transform: rotate(20deg);
210
+ }
211
+
212
+ .reward-calendar__bow::after {
213
+ inset-inline-start: 55%;
214
+ border-radius: 65% 60% 60% 15%;
215
+ transform: rotate(-20deg);
216
+ }
217
+
218
+ .reward-calendar__number {
219
+ position: absolute;
220
+ inset-block-start: var(--nox-space-2);
221
+ inset-inline-start: var(--nox-space-2);
222
+ display: grid;
223
+ place-items: center;
224
+ min-inline-size: 2.25rem;
225
+ block-size: 2.25rem;
226
+ padding-inline: var(--nox-space-1);
227
+ border-radius: var(--nox-radius-sm);
228
+ background: var(--nox-surface);
229
+ color: var(--nox-text);
230
+ font-size: 1.5rem;
231
+ font-weight: var(--nox-font-weight-strong);
232
+ font-variant-numeric: lining-nums tabular-nums;
233
+ line-height: 1;
234
+ }
235
+
236
+ .reward-calendar__number > span {
237
+ /* Center the visible digits, excluding the font's ascender/descender space. */
238
+ text-box-trim: trim-both;
239
+ text-box-edge: cap alphabetic;
240
+ }
241
+
242
+ @supports not (text-box-trim: trim-both) {
243
+ .reward-calendar__number > span {
244
+ transform: translateY(-0.05em);
245
+ }
246
+ }
247
+
248
+ .reward-calendar__glow {
249
+ position: absolute;
250
+ inset: -30%;
251
+ border-radius: 50%;
252
+ background: radial-gradient(
253
+ circle,
254
+ white 0%,
255
+ color-mix(in srgb, var(--reward-ribbon) 85%, white) 20%,
256
+ transparent 65%
257
+ );
258
+ opacity: 0;
259
+ pointer-events: none;
260
+ }
261
+
262
+ .reward-calendar__door:is([data-state='revealed'], [data-state='generated'])
263
+ :is(
264
+ .reward-calendar__paper,
265
+ .reward-calendar__ribbon,
266
+ .reward-calendar__bow
267
+ ) {
268
+ display: none;
269
+ }
270
+
271
+ .reward-calendar__door:is([data-state='revealed'], [data-state='generated'])
272
+ .reward-calendar__contents {
273
+ opacity: 1;
274
+ }
275
+
276
+ .reward-calendar__reward {
277
+ display: grid;
278
+ justify-items: center;
279
+ gap: var(--nox-space-3);
280
+ padding: var(--nox-space-5);
281
+ border-radius: var(--nox-radius-md);
282
+ background: var(--nox-surface-strong);
283
+ text-align: center;
284
+ }
285
+
286
+ .reward-calendar__reward .reward-calendar__preview {
287
+ inline-size: 6rem;
288
+ font-size: 3rem;
289
+ }
290
+
291
+ .reward-calendar__reward h3 {
292
+ margin: 0;
293
+ font-size: var(--nox-font-size-md);
294
+ }
295
+
296
+ .reward-calendar__feedback:empty {
297
+ display: none;
298
+ }
299
+
300
+ @media (hover: hover) {
301
+ .reward-calendar__door:not(:disabled):not([aria-disabled='true']):hover {
302
+ opacity: 0.85;
303
+ }
304
+ }
305
+
306
+ @media (width < 64rem) {
307
+ .reward-calendar__days {
308
+ grid-template-columns: repeat(4, minmax(0, 1fr));
309
+ }
310
+ }
311
+
312
+ @media (width < 36rem) {
313
+ .reward-calendar__days {
314
+ grid-template-columns: repeat(3, minmax(0, 1fr));
315
+ gap: var(--nox-space-2);
316
+ }
317
+ }
318
+
319
+ @media (width < 24rem) {
320
+ .reward-calendar__days {
321
+ grid-template-columns: repeat(2, minmax(0, 1fr));
322
+ }
323
+ }
324
+
325
+ @media (prefers-reduced-motion: no-preference) {
326
+ .reward-calendar__door {
327
+ transition: opacity var(--nox-motion-duration-sm)
328
+ var(--nox-motion-easing-standard);
329
+ }
330
+
331
+ .reward-calendar__door[data-opening] .reward-calendar__paper {
332
+ animation: reward-unwrap var(--reward-opening-duration) ease-in-out both;
333
+ }
334
+
335
+ .reward-calendar__door[data-opening]
336
+ :is(.reward-calendar__ribbon, .reward-calendar__bow) {
337
+ animation: reward-untie var(--reward-opening-duration) ease-in-out both;
338
+ }
339
+
340
+ .reward-calendar__door[data-opening] .reward-calendar__contents {
341
+ animation: reward-reveal var(--reward-opening-duration) ease-out both;
342
+ }
343
+
344
+ .reward-calendar__door[data-opening] .reward-calendar__glow {
345
+ animation: reward-glow var(--reward-opening-duration) ease-out both;
346
+ }
347
+ }
348
+
349
+ @keyframes reward-unwrap {
350
+ 0%,
351
+ 20% {
352
+ transform: translateX(0) rotate(0);
353
+ opacity: 1;
354
+ }
355
+
356
+ 65%,
357
+ 100% {
358
+ transform: translateX(var(--paper-shift)) rotate(var(--paper-turn));
359
+ opacity: 0;
360
+ }
361
+ }
362
+
363
+ @keyframes reward-untie {
364
+ 0%,
365
+ 10% {
366
+ opacity: 1;
367
+ }
368
+
369
+ 35%,
370
+ 100% {
371
+ opacity: 0;
372
+ }
373
+ }
374
+
375
+ @keyframes reward-reveal {
376
+ 0%,
377
+ 40% {
378
+ opacity: 0;
379
+ transform: scale(0.85);
380
+ }
381
+
382
+ 85%,
383
+ 100% {
384
+ opacity: 1;
385
+ transform: scale(1);
386
+ }
387
+ }
388
+
389
+ @keyframes reward-glow {
390
+ 0%,
391
+ 30%,
392
+ 100% {
393
+ opacity: 0;
394
+ transform: scale(0.6);
395
+ }
396
+
397
+ 55% {
398
+ opacity: 0.95;
399
+ transform: scale(1);
400
+ }
401
+
402
+ 80% {
403
+ opacity: 0;
404
+ transform: scale(1.3);
405
+ }
406
+ }
@@ -0,0 +1,18 @@
1
+ ---
2
+ import type { RewardCalendarVisual } from './contracts';
3
+ import Media from '../../primitives/media/Media/Media.astro';
4
+
5
+ type Props = { visual: RewardCalendarVisual };
6
+ const { visual } = Astro.props;
7
+ const Icon = visual.kind === 'icon' ? visual.component : null;
8
+ ---
9
+
10
+ <span class="reward-calendar__preview" aria-hidden="true">
11
+ {
12
+ visual.kind === 'image' ? (
13
+ <Media media={visual.media} fit="contain" size="fill" />
14
+ ) : Icon ? (
15
+ <Icon class="reward-calendar__preview-icon" />
16
+ ) : null
17
+ }
18
+ </span>
@@ -0,0 +1,55 @@
1
+ import type { DisplayMedia } from '../../display/contracts';
2
+ import type { AstroComponent } from '../../components/widgetTypes';
3
+
4
+ export type RewardCalendarColor = 'green' | 'red' | 'gold';
5
+
6
+ export type RewardCalendarVisual =
7
+ | { kind: 'icon'; component: AstroComponent }
8
+ | { kind: 'image'; media: DisplayMedia };
9
+
10
+ export type RewardCalendarEntry = {
11
+ id: string;
12
+ number: number;
13
+ label: string;
14
+ title: string;
15
+ color?: RewardCalendarColor;
16
+ visual: RewardCalendarVisual;
17
+ };
18
+
19
+ export type RewardCalendarEntryState =
20
+ 'locked' | 'available' | 'revealed' | 'generated';
21
+
22
+ export type RewardCalendarSnapshot = {
23
+ message: string;
24
+ identity: string;
25
+ entries: Readonly<Record<string, RewardCalendarEntryState>>;
26
+ };
27
+
28
+ export type RewardCalendarLabels = {
29
+ calendar: string;
30
+ close: string;
31
+ identity: string;
32
+ identityPlaceholder: string;
33
+ generate: string;
34
+ generating: string;
35
+ copy: string;
36
+ copied: string;
37
+ copyFailed: string;
38
+ invalidIdentity: string;
39
+ unavailable: string;
40
+ failed: string;
41
+ states: Record<RewardCalendarEntryState, string>;
42
+ };
43
+
44
+ export type RewardCalendarResult =
45
+ | { ok: true; code: string }
46
+ | { ok: false; reason: 'invalidIdentity' | 'unavailable' | 'failed' };
47
+
48
+ /** The consumer owns availability, identity, persistence and reward delivery. */
49
+ export type RewardCalendarAdapter = {
50
+ getSnapshot(): RewardCalendarSnapshot;
51
+ setIdentity(value: string): void;
52
+ reveal(id: string): boolean;
53
+ generate(id: string): Promise<RewardCalendarResult>;
54
+ subscribe(listener: () => void): () => void;
55
+ };