@@ -32,6 +32,23 @@ function useGridSize() {
32
32
return gridSize ;
33
33
}
34
34
35
+ function useContainerSize ( ) {
36
+ const [ containerSize , setContainerSize ] = useState ( 0 ) ;
37
+
38
+ useEffect ( ( ) => {
39
+ function updateSize ( ) {
40
+ const maxWidth = window . innerWidth - 32 ;
41
+ const maxHeight = window . innerHeight - 64 ;
42
+ setContainerSize ( Math . min ( maxWidth , maxHeight ) ) ;
43
+ }
44
+ updateSize ( ) ;
45
+ window . addEventListener ( "resize" , updateSize ) ;
46
+ return ( ) => window . removeEventListener ( "resize" , updateSize ) ;
47
+ } , [ ] ) ;
48
+
49
+ return containerSize ;
50
+ }
51
+
35
52
const ALL_FACTS = [
36
53
// 1
37
54
`Zak King <a href="mailto:[email protected] ">[email protected] </a>` ,
@@ -120,10 +137,10 @@ function isPuzzleSolved(tiles: (number | null)[]) {
120
137
121
138
export default function PuzzleClient ( ) {
122
139
const gridSize = useGridSize ( ) ;
140
+ const containerSize = useContainerSize ( ) ;
123
141
const resumeFacts = useMemo ( ( ) => getFactsForGridSize ( gridSize ) , [ gridSize ] ) ;
124
142
const puzzleImages = useMemo ( ( ) => getPuzzleImages ( gridSize ) , [ gridSize ] ) ;
125
143
const [ tiles , setTiles ] = useState < ( number | null ) [ ] > ( [ ] ) ;
126
- const [ containerSize , setContainerSize ] = useState ( 0 ) ;
127
144
const [ tileOffsets , setTileOffsets ] = useState < { x : number ; y : number } [ ] > (
128
145
[ ]
129
146
) ;
@@ -155,17 +172,6 @@ export default function PuzzleClient() {
155
172
setIsWon ( false ) ; // reset if grid changes
156
173
} , [ puzzleImages , gridSize ] ) ;
157
174
158
- useEffect ( ( ) => {
159
- function updateSize ( ) {
160
- const maxWidth = window . innerWidth - 8 ;
161
- const maxHeight = window . innerHeight - 8 ;
162
- setContainerSize ( Math . min ( maxWidth , maxHeight ) ) ;
163
- }
164
- updateSize ( ) ;
165
- window . addEventListener ( "resize" , updateSize ) ;
166
- return ( ) => window . removeEventListener ( "resize" , updateSize ) ;
167
- } , [ ] ) ;
168
-
169
175
// Whenever tiles change, check if puzzle is solved
170
176
useEffect ( ( ) => {
171
177
if ( ! isWon && isPuzzleSolved ( tiles ) ) {
@@ -346,7 +352,7 @@ export default function PuzzleClient() {
346
352
newLeft = baseX ;
347
353
}
348
354
349
- // clamp so tile can’ t move beyond 1 tile distance
355
+ // clamp so tile can' t move beyond 1 tile distance
350
356
if ( axis === "x" ) {
351
357
if ( direction === 1 ) {
352
358
newLeft = Math . max ( newLeft , baseX ) ;
@@ -413,7 +419,7 @@ export default function PuzzleClient() {
413
419
// Render puzzle
414
420
// ------------------------------------------------------
415
421
return (
416
- < div className = "min-h-screen w-screen flex items-center justify-center relative" >
422
+ < div className = "w-screen min-h-[100dvh] flex items-center justify-center relative p-4 md:p-8 " >
417
423
{ isWon && (
418
424
< >
419
425
< Confetti
@@ -436,8 +442,8 @@ export default function PuzzleClient() {
436
442
className = "border"
437
443
style = { {
438
444
position : "relative" ,
439
- width : gridSize * tileSize ,
440
- height : gridSize * tileSize ,
445
+ width : containerSize ,
446
+ height : containerSize ,
441
447
} }
442
448
>
443
449
{ /* Bottom layer: NxN facts */ }
@@ -529,12 +535,15 @@ export default function PuzzleClient() {
529
535
}
530
536
html {
531
537
color-scheme: light dark;
538
+ height: 100%;
532
539
}
533
540
body {
534
541
margin: 0;
535
542
background-color: #fdfdfd;
536
543
color: #333;
537
544
padding: 0;
545
+ min-height: 100%;
546
+ overscroll-behavior: none; /* Prevent bounce on iOS */
538
547
}
539
548
@media (prefers-color-scheme: dark) {
540
549
body {
@@ -543,8 +552,8 @@ export default function PuzzleClient() {
543
552
}
544
553
}
545
554
.draggable {
546
- touch-action: none; /* prevent scrolling on mobile */
547
- user-select: none; /* prevent text highlighting */
555
+ touch-action: none;
556
+ user-select: none;
548
557
}
549
558
` } </ style >
550
559
</ div >
0 commit comments