@astronox/core 0.5.7 → 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 +1 -1
- package/src/patterns/CircuitPuzzle/CircuitPuzzle.astro +22 -2
- package/src/patterns/CircuitPuzzle/CircuitPuzzle.css +24 -0
- package/src/patterns/LightMazePuzzle/LightMazePuzzle.astro +210 -0
- package/src/patterns/LightMazePuzzle/LightMazePuzzle.css +195 -0
- package/src/patterns/LightMazePuzzle/contracts.ts +75 -0
- package/src/patterns/LightMazePuzzle/lightMazePuzzleController.ts +684 -0
- package/src/patterns/LightMazePuzzle/presentation.ts +55 -0
- package/src/patterns/LightMazePuzzle/simulation.ts +164 -0
- package/src/patterns/MagneticFieldPuzzle/MagneticFieldPuzzle.astro +64 -3
- package/src/patterns/MagneticFieldPuzzle/MagneticFieldPuzzle.css +69 -0
- package/src/patterns/MagneticFieldPuzzle/contracts.ts +4 -0
- package/src/patterns/SlidingPuzzle/SlidingPuzzle.astro +8 -1
- package/src/patterns/SlidingPuzzle/SlidingPuzzle.css +13 -7
- package/src/patterns/index.ts +4 -0
- package/src/styles/style.css +1 -0
package/package.json
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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%;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { HTMLAttributes } from 'astro/types';
|
|
3
|
+
import Media from '../../primitives/media/Media/Media.astro';
|
|
4
|
+
import PuzzleGrid from '../PuzzleGrid/PuzzleGrid.astro';
|
|
5
|
+
import { presentLightMazeCell } from './presentation';
|
|
6
|
+
import type {
|
|
7
|
+
LightMazePuzzleLabels,
|
|
8
|
+
LightMazePuzzleMedia,
|
|
9
|
+
LightMazePuzzleModel,
|
|
10
|
+
} from './contracts';
|
|
11
|
+
import {
|
|
12
|
+
createLightMazePuzzleState,
|
|
13
|
+
evaluateLightMazeMechanism,
|
|
14
|
+
} from './simulation';
|
|
15
|
+
|
|
16
|
+
type Props = HTMLAttributes<'section'> & {
|
|
17
|
+
label: string;
|
|
18
|
+
labels: LightMazePuzzleLabels;
|
|
19
|
+
media: LightMazePuzzleMedia;
|
|
20
|
+
puzzles: readonly LightMazePuzzleModel[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
class: className,
|
|
25
|
+
label,
|
|
26
|
+
labels,
|
|
27
|
+
media,
|
|
28
|
+
puzzles,
|
|
29
|
+
...attrs
|
|
30
|
+
} = Astro.props;
|
|
31
|
+
|
|
32
|
+
if (puzzles.some((puzzle) => puzzle.activators?.length) && !media.mechanism)
|
|
33
|
+
throw new Error('Light maze mechanisms require display media.');
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
<section
|
|
37
|
+
{...attrs}
|
|
38
|
+
class:list={['light-maze-puzzle', className]}
|
|
39
|
+
aria-label={label}
|
|
40
|
+
data-light-maze-puzzle
|
|
41
|
+
>
|
|
42
|
+
{
|
|
43
|
+
puzzles.map((puzzle, index) => {
|
|
44
|
+
const initial = createLightMazePuzzleState(puzzle);
|
|
45
|
+
const mechanism = evaluateLightMazeMechanism(puzzle, initial);
|
|
46
|
+
return (
|
|
47
|
+
<div
|
|
48
|
+
class="light-maze-puzzle__instance"
|
|
49
|
+
data-puzzle-id={puzzle.id}
|
|
50
|
+
data-puzzle-status="incomplete"
|
|
51
|
+
data-light-maze-model={JSON.stringify(puzzle)}
|
|
52
|
+
data-light-maze-labels={JSON.stringify(labels)}
|
|
53
|
+
hidden={index !== 0}
|
|
54
|
+
style={`--piece-selection-filter: url(#light-maze-outline-${encodeURIComponent(puzzle.id)}); --piece-hover-filter: url(#light-maze-hover-${encodeURIComponent(puzzle.id)});`}
|
|
55
|
+
>
|
|
56
|
+
<svg
|
|
57
|
+
class="light-maze-puzzle__filters"
|
|
58
|
+
aria-hidden="true"
|
|
59
|
+
focusable="false"
|
|
60
|
+
>
|
|
61
|
+
<defs>
|
|
62
|
+
<filter
|
|
63
|
+
id={`light-maze-hover-${encodeURIComponent(puzzle.id)}`}
|
|
64
|
+
color-interpolation-filters="sRGB"
|
|
65
|
+
>
|
|
66
|
+
<feFlood
|
|
67
|
+
class="light-maze-puzzle__hover-color"
|
|
68
|
+
flood-opacity="0.1"
|
|
69
|
+
/>
|
|
70
|
+
<feComposite operator="in" in2="SourceAlpha" />
|
|
71
|
+
<feMerge>
|
|
72
|
+
<feMergeNode in="SourceGraphic" />
|
|
73
|
+
<feMergeNode />
|
|
74
|
+
</feMerge>
|
|
75
|
+
</filter>
|
|
76
|
+
<filter
|
|
77
|
+
id={`light-maze-outline-${encodeURIComponent(puzzle.id)}`}
|
|
78
|
+
x="-25%"
|
|
79
|
+
y="-25%"
|
|
80
|
+
width="150%"
|
|
81
|
+
height="150%"
|
|
82
|
+
color-interpolation-filters="sRGB"
|
|
83
|
+
>
|
|
84
|
+
<feMorphology in="SourceAlpha" operator="dilate" radius="2" />
|
|
85
|
+
<feGaussianBlur stdDeviation="0.5" result="outline" />
|
|
86
|
+
<feFlood class="light-maze-puzzle__ring-color" />
|
|
87
|
+
<feComposite operator="in" in2="outline" />
|
|
88
|
+
<feMerge>
|
|
89
|
+
<feMergeNode />
|
|
90
|
+
<feMergeNode in="SourceGraphic" />
|
|
91
|
+
</feMerge>
|
|
92
|
+
</filter>
|
|
93
|
+
</defs>
|
|
94
|
+
</svg>
|
|
95
|
+
<PuzzleGrid
|
|
96
|
+
class="light-maze-puzzle__board"
|
|
97
|
+
label={puzzle.label}
|
|
98
|
+
rows={puzzle.rows}
|
|
99
|
+
columns={puzzle.columns}
|
|
100
|
+
>
|
|
101
|
+
{Array.from(
|
|
102
|
+
{ length: puzzle.rows * puzzle.columns },
|
|
103
|
+
(_, cellIndex) => {
|
|
104
|
+
const row = Math.floor(cellIndex / puzzle.columns);
|
|
105
|
+
const column = cellIndex % puzzle.columns;
|
|
106
|
+
const {
|
|
107
|
+
disabled: blocked,
|
|
108
|
+
target,
|
|
109
|
+
lit,
|
|
110
|
+
activator,
|
|
111
|
+
consumed,
|
|
112
|
+
removableWall,
|
|
113
|
+
releasedWall,
|
|
114
|
+
label: cellLabel,
|
|
115
|
+
} = presentLightMazeCell(puzzle, mechanism, labels, {
|
|
116
|
+
row,
|
|
117
|
+
column,
|
|
118
|
+
});
|
|
119
|
+
return (
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
class="puzzle-grid__cell light-maze-puzzle__cell"
|
|
123
|
+
disabled={blocked}
|
|
124
|
+
tabindex={-1}
|
|
125
|
+
aria-label={cellLabel}
|
|
126
|
+
data-puzzle-cell
|
|
127
|
+
data-puzzle-cell-disabled={blocked ? '' : undefined}
|
|
128
|
+
data-light-maze-cell
|
|
129
|
+
data-light-maze-lit={lit ? '' : undefined}
|
|
130
|
+
data-light-maze-target={target ? '' : undefined}
|
|
131
|
+
data-light-maze-wall={removableWall ? '' : undefined}
|
|
132
|
+
data-light-maze-wall-released={
|
|
133
|
+
releasedWall ? '' : undefined
|
|
134
|
+
}
|
|
135
|
+
data-light-maze-activator={activator ? '' : undefined}
|
|
136
|
+
data-light-maze-activator-consumed={
|
|
137
|
+
consumed ? '' : undefined
|
|
138
|
+
}
|
|
139
|
+
data-cell-row={row}
|
|
140
|
+
data-cell-column={column}
|
|
141
|
+
data-cell-label={cellLabel}
|
|
142
|
+
style={`--cell-row: ${row + 1}; --cell-column: ${column + 1};`}
|
|
143
|
+
>
|
|
144
|
+
{activator && media.mechanism && (
|
|
145
|
+
<span
|
|
146
|
+
class="light-maze-puzzle__activator"
|
|
147
|
+
aria-hidden="true"
|
|
148
|
+
>
|
|
149
|
+
<span data-activator-inactive>
|
|
150
|
+
<Media
|
|
151
|
+
media={media.mechanism.activatorInactive}
|
|
152
|
+
size="fill"
|
|
153
|
+
/>
|
|
154
|
+
</span>
|
|
155
|
+
<span data-activator-active>
|
|
156
|
+
<Media
|
|
157
|
+
media={media.mechanism.activatorActive}
|
|
158
|
+
size="fill"
|
|
159
|
+
/>
|
|
160
|
+
</span>
|
|
161
|
+
</span>
|
|
162
|
+
)}
|
|
163
|
+
{removableWall && (
|
|
164
|
+
<span
|
|
165
|
+
class="light-maze-puzzle__wall-fill"
|
|
166
|
+
data-wall-fill
|
|
167
|
+
aria-hidden="true"
|
|
168
|
+
>
|
|
169
|
+
<svg
|
|
170
|
+
class="light-maze-puzzle__wall-cracks"
|
|
171
|
+
viewBox="0 0 100 100"
|
|
172
|
+
focusable="false"
|
|
173
|
+
>
|
|
174
|
+
<path d="M29 0L34 17L27 29L40 39L37 56M27 29L15 34L9 46" />
|
|
175
|
+
<path d="M100 31L82 37L78 49L63 55L58 69M78 49L85 59L83 70" />
|
|
176
|
+
<path d="M24 100L32 84L29 75L43 67M32 84L47 89" />
|
|
177
|
+
</svg>
|
|
178
|
+
</span>
|
|
179
|
+
)}
|
|
180
|
+
</button>
|
|
181
|
+
);
|
|
182
|
+
},
|
|
183
|
+
)}
|
|
184
|
+
{(['walker', 'light'] as const).map((piece) => (
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
class="light-maze-puzzle__piece"
|
|
188
|
+
aria-label={labels[piece]}
|
|
189
|
+
aria-pressed="false"
|
|
190
|
+
data-light-maze-piece={piece}
|
|
191
|
+
style={`--piece-row: ${initial[piece].row + 1}; --piece-column: ${initial[piece].column + 1};`}
|
|
192
|
+
>
|
|
193
|
+
<span class="light-maze-puzzle__piece-media" aria-hidden="true">
|
|
194
|
+
<Media media={media[piece]} size="fill" />
|
|
195
|
+
</span>
|
|
196
|
+
</button>
|
|
197
|
+
))}
|
|
198
|
+
</PuzzleGrid>
|
|
199
|
+
</div>
|
|
200
|
+
);
|
|
201
|
+
})
|
|
202
|
+
}
|
|
203
|
+
</section>
|
|
204
|
+
|
|
205
|
+
<script>
|
|
206
|
+
import { initLightMazePuzzle } from './lightMazePuzzleController';
|
|
207
|
+
for (const root of document.querySelectorAll('[data-light-maze-puzzle]')) {
|
|
208
|
+
if (root instanceof HTMLElement) initLightMazePuzzle(root);
|
|
209
|
+
}
|
|
210
|
+
</script>
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
.light-maze-puzzle,
|
|
2
|
+
.light-maze-puzzle__instance {
|
|
3
|
+
display: grid;
|
|
4
|
+
min-inline-size: 0;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.light-maze-puzzle__instance[hidden] {
|
|
8
|
+
display: none;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.light-maze-puzzle__filters {
|
|
12
|
+
position: absolute;
|
|
13
|
+
inline-size: 0;
|
|
14
|
+
block-size: 0;
|
|
15
|
+
pointer-events: none;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
.light-maze-puzzle__ring-color {
|
|
19
|
+
flood-color: var(--nox-source-tone-info);
|
|
20
|
+
}
|
|
21
|
+
.light-maze-puzzle__hover-color {
|
|
22
|
+
flood-color: var(--nox-tone-color);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.light-maze-puzzle__board {
|
|
26
|
+
isolation: isolate;
|
|
27
|
+
touch-action: manipulation;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.light-maze-puzzle__cell {
|
|
31
|
+
appearance: none;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.light-maze-puzzle__activator {
|
|
35
|
+
position: absolute;
|
|
36
|
+
inset: 8%;
|
|
37
|
+
display: grid;
|
|
38
|
+
pointer-events: none;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.light-maze-puzzle__activator > span {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-area: 1 / 1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
.light-maze-puzzle__cell:not([data-light-maze-activator-consumed])
|
|
47
|
+
[data-activator-active],
|
|
48
|
+
.light-maze-puzzle__cell[data-light-maze-activator-consumed]
|
|
49
|
+
[data-activator-inactive] {
|
|
50
|
+
display: none;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.light-maze-puzzle__wall-fill {
|
|
54
|
+
position: absolute;
|
|
55
|
+
inset: 0;
|
|
56
|
+
display: grid;
|
|
57
|
+
border-radius: inherit;
|
|
58
|
+
overflow: hidden;
|
|
59
|
+
background: var(--nox-surface-strong);
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.light-maze-puzzle__wall-cracks {
|
|
64
|
+
position: absolute;
|
|
65
|
+
inset: 0;
|
|
66
|
+
inline-size: 100%;
|
|
67
|
+
block-size: 100%;
|
|
68
|
+
fill: none;
|
|
69
|
+
stroke: var(--nox-text-muted);
|
|
70
|
+
stroke-width: 1.5;
|
|
71
|
+
stroke-linecap: round;
|
|
72
|
+
stroke-linejoin: round;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.light-maze-puzzle__cell[data-light-maze-wall-released]:not(
|
|
76
|
+
[data-wall-removing]
|
|
77
|
+
)
|
|
78
|
+
.light-maze-puzzle__wall-fill {
|
|
79
|
+
display: none;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.light-maze-puzzle__cell[data-light-maze-lit]:not([data-puzzle-cell-disabled]) {
|
|
83
|
+
--puzzle-cell-base-background: color-mix(
|
|
84
|
+
in srgb,
|
|
85
|
+
var(--nox-source-tone-warning) 12%,
|
|
86
|
+
var(--nox-surface)
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.light-maze-puzzle__cell[data-light-maze-target] {
|
|
91
|
+
--puzzle-cell-base-border: var(--nox-source-tone-success);
|
|
92
|
+
border-style: dashed;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.light-maze-puzzle__cell[data-light-maze-destination] {
|
|
96
|
+
--puzzle-cell-ring-width: var(--nox-focus-ring-width);
|
|
97
|
+
--puzzle-cell-ring-color: var(--nox-source-tone-info);
|
|
98
|
+
z-index: 3;
|
|
99
|
+
overflow: visible;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.light-maze-puzzle__piece {
|
|
103
|
+
position: relative;
|
|
104
|
+
z-index: 4;
|
|
105
|
+
display: grid;
|
|
106
|
+
grid-row: var(--piece-row);
|
|
107
|
+
grid-column: var(--piece-column);
|
|
108
|
+
min-inline-size: 0;
|
|
109
|
+
min-block-size: 0;
|
|
110
|
+
margin: var(--nox-space-1);
|
|
111
|
+
padding: 0;
|
|
112
|
+
place-items: center;
|
|
113
|
+
border: var(--nox-border-width) solid transparent;
|
|
114
|
+
border-radius: var(--nox-radius-sm);
|
|
115
|
+
background: transparent;
|
|
116
|
+
cursor: grab;
|
|
117
|
+
touch-action: none;
|
|
118
|
+
user-select: none;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
.light-maze-puzzle__piece-media {
|
|
122
|
+
display: grid;
|
|
123
|
+
inline-size: 84%;
|
|
124
|
+
aspect-ratio: 1;
|
|
125
|
+
pointer-events: none;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.light-maze-puzzle__piece:focus-visible {
|
|
129
|
+
outline: none;
|
|
130
|
+
}
|
|
131
|
+
.light-maze-puzzle__piece:is([data-piece-selected], :focus-visible)
|
|
132
|
+
.light-maze-puzzle__piece-media {
|
|
133
|
+
filter: var(--piece-selection-filter);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@media (hover: hover) {
|
|
137
|
+
.light-maze-puzzle__piece:hover .light-maze-puzzle__piece-media {
|
|
138
|
+
filter: var(--piece-hover-filter);
|
|
139
|
+
}
|
|
140
|
+
.light-maze-puzzle__piece:is([data-piece-selected], :focus-visible):hover
|
|
141
|
+
.light-maze-puzzle__piece-media {
|
|
142
|
+
filter: var(--piece-hover-filter) var(--piece-selection-filter);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.light-maze-puzzle__piece[data-piece-dragging] {
|
|
147
|
+
z-index: 6;
|
|
148
|
+
cursor: grabbing;
|
|
149
|
+
}
|
|
150
|
+
.light-maze-puzzle__piece[data-piece-dragging] .light-maze-puzzle__piece-media {
|
|
151
|
+
transform: translate3d(var(--piece-drag-x, 0px), var(--piece-drag-y, 0px), 0)
|
|
152
|
+
scale(1.04);
|
|
153
|
+
will-change: transform;
|
|
154
|
+
}
|
|
155
|
+
.light-maze-puzzle__instance[data-puzzle-status='valid']
|
|
156
|
+
.light-maze-puzzle__piece {
|
|
157
|
+
cursor: default;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.light-maze-puzzle__instance[data-light-maze-transitioning]
|
|
161
|
+
.light-maze-puzzle__piece {
|
|
162
|
+
cursor: default;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.light-maze-puzzle__instance[data-light-maze-transitioning]
|
|
166
|
+
.light-maze-puzzle__piece
|
|
167
|
+
.light-maze-puzzle__piece-media {
|
|
168
|
+
filter: none;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.light-maze-puzzle__instance[data-light-maze-transitioning]
|
|
172
|
+
.light-maze-puzzle__cell[data-puzzle-cell] {
|
|
173
|
+
--puzzle-cell-ring-width: 0px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@media (width < 34rem) {
|
|
177
|
+
.light-maze-puzzle__piece {
|
|
178
|
+
margin: 2px;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
183
|
+
.light-maze-puzzle__piece[data-piece-moved] {
|
|
184
|
+
animation: light-maze-puzzle-move 260ms var(--nox-motion-easing-standard);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
@keyframes light-maze-puzzle-move {
|
|
188
|
+
from {
|
|
189
|
+
transform: translate3d(
|
|
190
|
+
var(--piece-move-inline-distance),
|
|
191
|
+
var(--piece-move-block-distance),
|
|
192
|
+
0
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { DisplayMedia } from '../../display/contracts';
|
|
2
|
+
|
|
3
|
+
export type LightMazePosition = { row: number; column: number };
|
|
4
|
+
export type LightMazeDirection = 'north' | 'east' | 'south' | 'west';
|
|
5
|
+
export type LightMazePiece = 'walker' | 'light';
|
|
6
|
+
export type LightMazePuzzleStatus = 'incomplete' | 'valid';
|
|
7
|
+
|
|
8
|
+
export type LightMazePuzzleModel = {
|
|
9
|
+
id: string;
|
|
10
|
+
label: string;
|
|
11
|
+
rows: number;
|
|
12
|
+
columns: number;
|
|
13
|
+
start: LightMazePosition;
|
|
14
|
+
light: LightMazePosition;
|
|
15
|
+
target: LightMazePosition;
|
|
16
|
+
blockedCells: readonly LightMazePosition[];
|
|
17
|
+
activators?: readonly {
|
|
18
|
+
position: LightMazePosition;
|
|
19
|
+
walls: readonly LightMazePosition[];
|
|
20
|
+
}[];
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export type LightMazeMechanism = {
|
|
24
|
+
illuminatedCells: readonly LightMazePosition[];
|
|
25
|
+
blockedCells: readonly LightMazePosition[];
|
|
26
|
+
activatedCells: readonly LightMazePosition[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type LightMazePuzzleState = {
|
|
30
|
+
walker: LightMazePosition;
|
|
31
|
+
light: LightMazePosition;
|
|
32
|
+
activated: readonly number[];
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type LightMazeAction = {
|
|
36
|
+
piece: LightMazePiece;
|
|
37
|
+
direction: LightMazeDirection;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type LightMazeMovement = LightMazeAction & {
|
|
41
|
+
from: LightMazePosition;
|
|
42
|
+
to: LightMazePosition;
|
|
43
|
+
stoppedByWalker: boolean;
|
|
44
|
+
state: LightMazePuzzleState;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export type LightMazePuzzleLabels = {
|
|
48
|
+
walker: string;
|
|
49
|
+
light: string;
|
|
50
|
+
target: string;
|
|
51
|
+
blockedCell: string;
|
|
52
|
+
litCell: string;
|
|
53
|
+
darkCell: string;
|
|
54
|
+
directions: Readonly<Record<LightMazeDirection, string>>;
|
|
55
|
+
moveTemplate: string;
|
|
56
|
+
mechanism?: {
|
|
57
|
+
activatorAvailable: string;
|
|
58
|
+
activatorConsumed: string;
|
|
59
|
+
removableWall: string;
|
|
60
|
+
releasedWall: string;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export type LightMazePuzzleMedia = Record<LightMazePiece, DisplayMedia> & {
|
|
65
|
+
mechanism?: {
|
|
66
|
+
activatorInactive: DisplayMedia;
|
|
67
|
+
activatorActive: DisplayMedia;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type LightMazePuzzleChangeDetail = {
|
|
72
|
+
kind: 'light-maze';
|
|
73
|
+
puzzleId: string;
|
|
74
|
+
state: LightMazePuzzleState;
|
|
75
|
+
};
|