@astronox/core 0.5.8 → 0.5.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@astronox/core",
3
- "version": "0.5.8",
3
+ "version": "0.5.9",
4
4
  "description": "Reusable Astro components, patterns, styles, and infrastructure for AstroNox sites.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -1,11 +1,14 @@
1
1
  ---
2
2
  import type { HTMLAttributes } from 'astro/types';
3
+ import type { DisplayMedia } from '../../display/contracts';
4
+ import Media from '../../primitives/media/Media/Media.astro';
3
5
  import PuzzleGrid from '../PuzzleGrid/PuzzleGrid.astro';
4
6
  import type { CircuitPuzzleLabels, CircuitPuzzleModel } from './contracts';
5
7
 
6
8
  type Props = HTMLAttributes<'section'> & {
7
9
  label: string;
8
10
  labels: CircuitPuzzleLabels;
11
+ coreMedia?: DisplayMedia;
9
12
  puzzles: readonly CircuitPuzzleModel[];
10
13
  };
11
14
 
@@ -13,6 +16,7 @@ const {
13
16
  class: className,
14
17
  label,
15
18
  labels,
19
+ coreMedia,
16
20
  puzzles,
17
21
  ...attrs
18
22
  }: Props = Astro.props;
@@ -64,7 +68,16 @@ const {
64
68
  disabled={cell.fixed ? true : undefined}
65
69
  style={`--cell-row: ${cell.row + 1}; --cell-column: ${cell.column + 1}; --cell-rotation: ${cell.initialRotation}deg; --cell-index: ${cellIndex};`}
66
70
  >
67
- <span class="circuit-puzzle__piece" aria-hidden="true">
71
+ <span
72
+ class:list={[
73
+ 'circuit-puzzle__piece',
74
+ {
75
+ 'circuit-puzzle__piece--with-media':
76
+ cell.kind === 'core' && coreMedia,
77
+ },
78
+ ]}
79
+ aria-hidden="true"
80
+ >
68
81
  {cell.connections.map((direction) => (
69
82
  <span
70
83
  class:list={[
@@ -73,8 +86,15 @@ const {
73
86
  ]}
74
87
  />
75
88
  ))}
76
- <span class="circuit-puzzle__node" />
89
+ {!(cell.kind === 'core' && coreMedia) && (
90
+ <span class="circuit-puzzle__node" />
91
+ )}
77
92
  </span>
93
+ {cell.kind === 'core' && coreMedia && (
94
+ <span class="circuit-puzzle__core-media" aria-hidden="true">
95
+ <Media media={coreMedia} size="fill" />
96
+ </span>
97
+ )}
78
98
  </button>
79
99
  );
80
100
  })}
@@ -36,6 +36,15 @@
36
36
  --circuit-puzzle-symbol-color: var(--nox-source-tone-danger);
37
37
  }
38
38
 
39
+ .circuit-puzzle__instance[data-puzzle-status='valid']
40
+ .circuit-puzzle__cell[data-cell-kind='core'] {
41
+ --puzzle-cell-state-background: color-mix(
42
+ in srgb,
43
+ var(--nox-source-tone-success) 14%,
44
+ var(--nox-surface)
45
+ );
46
+ }
47
+
39
48
  .circuit-puzzle__piece {
40
49
  position: relative;
41
50
  display: grid;
@@ -46,6 +55,21 @@
46
55
  color: var(--circuit-puzzle-symbol-color);
47
56
  }
48
57
 
58
+ .circuit-puzzle__piece--with-media {
59
+ grid-area: 1 / 1;
60
+ inline-size: 100%;
61
+ block-size: 100%;
62
+ }
63
+
64
+ .circuit-puzzle__core-media {
65
+ z-index: 1;
66
+ grid-area: 1 / 1;
67
+ display: grid;
68
+ inline-size: 84%;
69
+ aspect-ratio: 1;
70
+ pointer-events: none;
71
+ }
72
+
49
73
  .circuit-puzzle__node {
50
74
  z-index: 1;
51
75
  inline-size: 32%;
@@ -33,7 +33,7 @@
33
33
 
34
34
  .light-maze-puzzle__activator {
35
35
  position: absolute;
36
- inset: 18%;
36
+ inset: 8%;
37
37
  display: grid;
38
38
  pointer-events: none;
39
39
  }
@@ -110,7 +110,7 @@
110
110
  margin: var(--nox-space-1);
111
111
  padding: 0;
112
112
  place-items: center;
113
- border: 0;
113
+ border: var(--nox-border-width) solid transparent;
114
114
  border-radius: var(--nox-radius-sm);
115
115
  background: transparent;
116
116
  cursor: grab;
@@ -120,7 +120,7 @@
120
120
 
121
121
  .light-maze-puzzle__piece-media {
122
122
  display: grid;
123
- inline-size: 80%;
123
+ inline-size: 84%;
124
124
  aspect-ratio: 1;
125
125
  pointer-events: none;
126
126
  }
@@ -173,6 +173,12 @@
173
173
  --puzzle-cell-ring-width: 0px;
174
174
  }
175
175
 
176
+ @media (width < 34rem) {
177
+ .light-maze-puzzle__piece {
178
+ margin: 2px;
179
+ }
180
+ }
181
+
176
182
  @media (prefers-reduced-motion: no-preference) {
177
183
  .light-maze-puzzle__piece[data-piece-moved] {
178
184
  animation: light-maze-puzzle-move 260ms var(--nox-motion-easing-standard);
@@ -4,14 +4,17 @@ import MinusIcon from '../../components/icons/MinusIcon.astro';
4
4
  import PlusIcon from '../../components/icons/PlusIcon.astro';
5
5
  import SlashIcon from '../../components/icons/SlashIcon.astro';
6
6
  import PuzzleGrid from '../PuzzleGrid/PuzzleGrid.astro';
7
+ import Media from '../../primitives/media/Media/Media.astro';
7
8
  import type {
8
9
  MagneticFieldPuzzleLabels,
9
10
  MagneticFieldPuzzleModel,
11
+ MagneticFieldPuzzleMedia,
10
12
  } from './contracts';
11
13
 
12
14
  type Props = HTMLAttributes<'section'> & {
13
15
  label: string;
14
16
  labels: MagneticFieldPuzzleLabels;
17
+ media?: MagneticFieldPuzzleMedia;
15
18
  puzzles: readonly MagneticFieldPuzzleModel[];
16
19
  };
17
20
 
@@ -19,9 +22,16 @@ const {
19
22
  class: className,
20
23
  label,
21
24
  labels,
25
+ media,
22
26
  puzzles,
23
27
  ...attrs
24
28
  }: Props = Astro.props;
29
+
30
+ if (
31
+ media &&
32
+ puzzles.some((puzzle) => puzzle.pieces.some((piece) => !media[piece.id]))
33
+ )
34
+ throw new Error('Magnetic field puzzle media must cover every piece.');
25
35
  ---
26
36
 
27
37
  <section
@@ -46,7 +56,53 @@ const {
46
56
  data-puzzle-id={puzzle.id}
47
57
  data-puzzle-status="incomplete"
48
58
  hidden={puzzleIndex !== 0}
59
+ style={
60
+ media
61
+ ? `--piece-selection-filter: url(#magnetic-outline-${encodeURIComponent(puzzle.id)}); --piece-hover-filter: url(#magnetic-hover-${encodeURIComponent(puzzle.id)});`
62
+ : undefined
63
+ }
49
64
  >
65
+ {media && (
66
+ <svg
67
+ class="magnetic-field-puzzle__filters"
68
+ aria-hidden="true"
69
+ focusable="false"
70
+ >
71
+ <defs>
72
+ <filter
73
+ id={`magnetic-hover-${encodeURIComponent(puzzle.id)}`}
74
+ color-interpolation-filters="sRGB"
75
+ >
76
+ <feFlood
77
+ class="magnetic-field-puzzle__hover-color"
78
+ flood-opacity="0.1"
79
+ />
80
+ <feComposite operator="in" in2="SourceAlpha" />
81
+ <feMerge>
82
+ <feMergeNode in="SourceGraphic" />
83
+ <feMergeNode />
84
+ </feMerge>
85
+ </filter>
86
+ <filter
87
+ id={`magnetic-outline-${encodeURIComponent(puzzle.id)}`}
88
+ x="-25%"
89
+ y="-25%"
90
+ width="150%"
91
+ height="150%"
92
+ color-interpolation-filters="sRGB"
93
+ >
94
+ <feMorphology in="SourceAlpha" operator="dilate" radius="2" />
95
+ <feGaussianBlur stdDeviation="0.5" result="outline" />
96
+ <feFlood class="magnetic-field-puzzle__ring-color" />
97
+ <feComposite operator="in" in2="outline" />
98
+ <feMerge>
99
+ <feMergeNode />
100
+ <feMergeNode in="SourceGraphic" />
101
+ </feMerge>
102
+ </filter>
103
+ </defs>
104
+ </svg>
105
+ )}
50
106
  <PuzzleGrid
51
107
  class="magnetic-field-puzzle__board"
52
108
  label={puzzle.label}
@@ -158,6 +214,7 @@ const {
158
214
  aria-label={`${piece.label} — ${labels.polarities[piece.polarity]}`}
159
215
  aria-pressed="false"
160
216
  data-field-piece={piece.id}
217
+ data-field-piece-media={media ? '' : undefined}
161
218
  data-piece-label={piece.label}
162
219
  data-piece-short-label={piece.shortLabel}
163
220
  data-piece-row={piece.row}
@@ -175,9 +232,13 @@ const {
175
232
  class="magnetic-field-puzzle__piece-core"
176
233
  aria-hidden="true"
177
234
  >
178
- <span class="magnetic-field-puzzle__piece-iris">
179
- <span class="magnetic-field-puzzle__piece-pupil" />
180
- </span>
235
+ {media ? (
236
+ <Media media={media[piece.id]!} size="fill" />
237
+ ) : (
238
+ <span class="magnetic-field-puzzle__piece-iris">
239
+ <span class="magnetic-field-puzzle__piece-pupil" />
240
+ </span>
241
+ )}
181
242
  <span class="magnetic-field-puzzle__piece-identity-region">
182
243
  <span
183
244
  class="magnetic-field-puzzle__identity"
@@ -19,6 +19,21 @@
19
19
  display: none;
20
20
  }
21
21
 
22
+ .magnetic-field-puzzle__filters {
23
+ position: absolute;
24
+ inline-size: 0;
25
+ block-size: 0;
26
+ pointer-events: none;
27
+ }
28
+
29
+ .magnetic-field-puzzle__ring-color {
30
+ flood-color: var(--nox-source-tone-info);
31
+ }
32
+
33
+ .magnetic-field-puzzle__hover-color {
34
+ flood-color: var(--nox-tone-color);
35
+ }
36
+
22
37
  .magnetic-field-puzzle__board {
23
38
  isolation: isolate;
24
39
  touch-action: manipulation;
@@ -287,6 +302,60 @@
287
302
  );
288
303
  }
289
304
 
305
+ .magnetic-field-puzzle__piece[data-field-piece-media] {
306
+ border: var(--nox-border-width) solid transparent;
307
+ }
308
+
309
+ .magnetic-field-puzzle__piece[data-field-piece-media]
310
+ .magnetic-field-puzzle__piece-core {
311
+ inline-size: 84%;
312
+ border: 0;
313
+ border-radius: 0;
314
+ background: none;
315
+ box-shadow: none;
316
+ container-type: inline-size;
317
+ pointer-events: none;
318
+ }
319
+
320
+ .magnetic-field-puzzle__piece[data-field-piece-media]
321
+ .magnetic-field-puzzle__piece-identity-region {
322
+ inset-block: 68% 14%;
323
+ }
324
+
325
+ .magnetic-field-puzzle__piece[data-field-piece-media]
326
+ .magnetic-field-puzzle__identity {
327
+ font-size: clamp(0.625rem, 22cqi, var(--nox-font-size-md));
328
+ }
329
+
330
+ .magnetic-field-puzzle__piece[data-field-piece-media]:is(
331
+ [data-piece-selected],
332
+ :focus-visible
333
+ )
334
+ .magnetic-field-puzzle__piece-core {
335
+ filter: var(--piece-selection-filter);
336
+ }
337
+
338
+ @media (hover: hover) {
339
+ .magnetic-field-puzzle__piece[data-field-piece-media]:hover
340
+ .magnetic-field-puzzle__piece-core {
341
+ filter: var(--piece-hover-filter);
342
+ }
343
+
344
+ .magnetic-field-puzzle__piece[data-field-piece-media]:is(
345
+ [data-piece-selected],
346
+ :focus-visible
347
+ ):hover
348
+ .magnetic-field-puzzle__piece-core {
349
+ filter: var(--piece-hover-filter) var(--piece-selection-filter);
350
+ }
351
+ }
352
+
353
+ @media (width < 34rem) {
354
+ .magnetic-field-puzzle__piece[data-field-piece-media] {
355
+ margin: 2px;
356
+ }
357
+ }
358
+
290
359
  @media (prefers-reduced-motion: no-preference) {
291
360
  .magnetic-field-puzzle__piece[data-piece-moved] {
292
361
  animation: magnetic-field-slide 260ms var(--nox-motion-easing-standard);
@@ -1,3 +1,7 @@
1
+ import type { DisplayMedia } from '../../display/contracts';
2
+
3
+ export type MagneticFieldPuzzleMedia = Readonly<Record<string, DisplayMedia>>;
4
+
1
5
  export type MagneticFieldDirection = 'north' | 'east' | 'south' | 'west';
2
6
  export type MagneticFieldPolarity = 'north' | 'south';
3
7
  export type MagneticFieldPupilPosition = 'upper-end' | 'center' | 'lower-start';
@@ -147,7 +147,14 @@ const {
147
147
  </svg>
148
148
  )}
149
149
  {hole && (
150
- <span class="sliding-puzzle__hole" aria-hidden="true" />
150
+ <svg
151
+ class="sliding-puzzle__hole"
152
+ viewBox="0 0 100 100"
153
+ aria-hidden="true"
154
+ focusable="false"
155
+ >
156
+ <circle cx="50" cy="50" r="46.5" />
157
+ </svg>
151
158
  )}
152
159
  {target && (
153
160
  <span class="sliding-puzzle__target" aria-hidden="true">
@@ -69,11 +69,11 @@
69
69
 
70
70
  .sliding-puzzle__hole {
71
71
  display: none;
72
- inline-size: 72%;
72
+ inline-size: 84%;
73
73
  aspect-ratio: 1;
74
- border: calc(var(--nox-border-width) * 2) solid var(--sliding-transfer-color);
75
- border-radius: var(--nox-radius-pill);
76
- background: color-mix(
74
+ stroke: var(--sliding-transfer-color);
75
+ stroke-width: 2.5;
76
+ fill: color-mix(
77
77
  in srgb,
78
78
  var(--nox-source-tone-neutral) 20%,
79
79
  var(--nox-mix-dark)
@@ -99,7 +99,7 @@
99
99
 
100
100
  .sliding-puzzle__target {
101
101
  display: grid;
102
- inline-size: 90%;
102
+ inline-size: 84%;
103
103
  aspect-ratio: 1;
104
104
  pointer-events: none;
105
105
  }
@@ -115,7 +115,7 @@
115
115
  margin: var(--nox-space-1);
116
116
  padding: 0;
117
117
  place-items: center;
118
- border: 0;
118
+ border: var(--nox-border-width) solid transparent;
119
119
  border-radius: var(--nox-radius-sm);
120
120
  background: transparent;
121
121
  cursor: grab;
@@ -125,7 +125,7 @@
125
125
 
126
126
  .sliding-puzzle__piece-media {
127
127
  display: grid;
128
- inline-size: 80%;
128
+ inline-size: 84%;
129
129
  aspect-ratio: 1;
130
130
  pointer-events: none;
131
131
  }
@@ -169,6 +169,12 @@
169
169
  cursor: default;
170
170
  }
171
171
 
172
+ @media (width < 34rem) {
173
+ .sliding-puzzle__piece {
174
+ margin: 2px;
175
+ }
176
+ }
177
+
172
178
  @media (prefers-reduced-motion: no-preference) {
173
179
  .sliding-puzzle__piece[data-piece-moved] {
174
180
  animation: sliding-puzzle-move 260ms var(--nox-motion-easing-standard);