This commit is contained in:
parent
3d4e9c3a25
commit
1cd38b6cba
9
bash.exe.stackdump
Normal file
9
bash.exe.stackdump
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
Stack trace:
|
||||||
|
Frame Function Args
|
||||||
|
000FFFF9F70 00210062B0E (00210297178, 00210275E3E, 000FFFF9F70, 000FFFF8E70)
|
||||||
|
000FFFF9F70 0021004846A (00000000000, 00000000000, 00000000000, 00000000004)
|
||||||
|
000FFFF9F70 002100484A2 (00210297229, 000FFFF9E28, 000FFFF9F70, 00000000000)
|
||||||
|
000FFFF9F70 002100D2FFE (00000000000, 00000000000, 00000000000, 00000000000)
|
||||||
|
000FFFF9F70 002100D3125 (000FFFF9F80, 00000000000, 00000000000, 00000000000)
|
||||||
|
001004F84B7 002100D46E5 (000FFFF9F80, 00000000000, 00000000000, 00000000000)
|
||||||
|
End of stack trace
|
||||||
File diff suppressed because one or more lines are too long
67
dist/assets/index-BmIETw7i.js
vendored
67
dist/assets/index-BmIETw7i.js
vendored
File diff suppressed because one or more lines are too long
67
dist/assets/index-DavgIoPG.js
vendored
Normal file
67
dist/assets/index-DavgIoPG.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4
dist/index.html
vendored
4
dist/index.html
vendored
@ -5,8 +5,8 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="theme-color" content="#080c14" />
|
<meta name="theme-color" content="#080c14" />
|
||||||
<title>ErrorLab</title>
|
<title>ErrorLab</title>
|
||||||
<script type="module" crossorigin src="/assets/index-BmIETw7i.js"></script>
|
<script type="module" crossorigin src="/assets/index-DavgIoPG.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/assets/index-DmKezAnz.css">
|
<link rel="stylesheet" crossorigin href="/assets/index-BMF9Bv1T.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
149
src/components/TetrisGame/TetrisGame.css
Normal file
149
src/components/TetrisGame/TetrisGame.css
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
.tetris-shell {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 700px;
|
||||||
|
background: linear-gradient(180deg, rgba(10, 20, 32, 0.85) 0%, rgba(6, 12, 18, 0.92) 100%);
|
||||||
|
border: 1px solid rgba(0, 200, 255, 0.2);
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 1rem;
|
||||||
|
position: relative;
|
||||||
|
box-shadow: 0 0 28px rgba(0, 200, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.8rem;
|
||||||
|
margin-bottom: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
color: var(--accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-metrics {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.8rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-layout {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-board {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 300px;
|
||||||
|
aspect-ratio: 1 / 2;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(10, 1fr);
|
||||||
|
grid-template-rows: repeat(20, 1fr);
|
||||||
|
gap: 2px;
|
||||||
|
background: rgba(0, 0, 0, 0.4);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
||||||
|
padding: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-cell {
|
||||||
|
background: rgba(255, 255, 255, 0.025);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-cell[style] {
|
||||||
|
background: var(--cell-color);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-cell--ghost {
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-sidebar {
|
||||||
|
min-width: 130px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-label {
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
margin-bottom: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-next-grid {
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
grid-template-rows: repeat(4, 1fr);
|
||||||
|
gap: 2px;
|
||||||
|
background: rgba(0, 0, 0, 0.28);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.09);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-next-cell {
|
||||||
|
background: rgba(255, 255, 255, 0.03);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-next-cell--on {
|
||||||
|
background: var(--cell-color);
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-btn {
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
background: rgba(0, 255, 204, 0.1);
|
||||||
|
color: var(--accent);
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.45rem 0.6rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-btn:hover {
|
||||||
|
background: rgba(0, 255, 204, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-help {
|
||||||
|
margin-top: 0.8rem;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(6, 10, 16, 0.55);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-overlay p {
|
||||||
|
color: #ffffff;
|
||||||
|
font-size: 1rem;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.tetris-layout {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tetris-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
356
src/components/TetrisGame/TetrisGame.jsx
Normal file
356
src/components/TetrisGame/TetrisGame.jsx
Normal file
@ -0,0 +1,356 @@
|
|||||||
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||||
|
import './TetrisGame.css'
|
||||||
|
|
||||||
|
const BOARD_W = 10
|
||||||
|
const BOARD_H = 20
|
||||||
|
const TICK_MS = 500
|
||||||
|
|
||||||
|
const SHAPES = {
|
||||||
|
I: {
|
||||||
|
color: '#00ccff',
|
||||||
|
matrix: [
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[1, 1, 1, 1],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
[0, 0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
O: {
|
||||||
|
color: '#ffee44',
|
||||||
|
matrix: [
|
||||||
|
[1, 1],
|
||||||
|
[1, 1],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
T: {
|
||||||
|
color: '#b266ff',
|
||||||
|
matrix: [
|
||||||
|
[0, 1, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
L: {
|
||||||
|
color: '#ffaa33',
|
||||||
|
matrix: [
|
||||||
|
[0, 0, 1],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
J: {
|
||||||
|
color: '#4488ff',
|
||||||
|
matrix: [
|
||||||
|
[1, 0, 0],
|
||||||
|
[1, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
S: {
|
||||||
|
color: '#33dd66',
|
||||||
|
matrix: [
|
||||||
|
[0, 1, 1],
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
Z: {
|
||||||
|
color: '#ff5566',
|
||||||
|
matrix: [
|
||||||
|
[1, 1, 0],
|
||||||
|
[0, 1, 1],
|
||||||
|
[0, 0, 0],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const PIECES = Object.keys(SHAPES)
|
||||||
|
|
||||||
|
function newBoard() {
|
||||||
|
return Array.from({ length: BOARD_H }, () => Array.from({ length: BOARD_W }, () => null))
|
||||||
|
}
|
||||||
|
|
||||||
|
function cloneBoard(board) {
|
||||||
|
return board.map((row) => [...row])
|
||||||
|
}
|
||||||
|
|
||||||
|
function rotateMatrix(matrix) {
|
||||||
|
const n = matrix.length
|
||||||
|
return Array.from({ length: n }, (_, y) =>
|
||||||
|
Array.from({ length: n }, (_, x) => matrix[n - 1 - x][y])
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function randomPiece() {
|
||||||
|
const type = PIECES[Math.floor(Math.random() * PIECES.length)]
|
||||||
|
const shape = SHAPES[type]
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
color: shape.color,
|
||||||
|
matrix: shape.matrix.map((r) => [...r]),
|
||||||
|
x: Math.floor((BOARD_W - shape.matrix[0].length) / 2),
|
||||||
|
y: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function collides(board, piece) {
|
||||||
|
const { matrix, x: px, y: py } = piece
|
||||||
|
for (let y = 0; y < matrix.length; y++) {
|
||||||
|
for (let x = 0; x < matrix[y].length; x++) {
|
||||||
|
if (!matrix[y][x]) continue
|
||||||
|
const bx = px + x
|
||||||
|
const by = py + y
|
||||||
|
if (bx < 0 || bx >= BOARD_W || by >= BOARD_H) return true
|
||||||
|
if (by >= 0 && board[by][bx]) return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePiece(board, piece) {
|
||||||
|
const next = cloneBoard(board)
|
||||||
|
const { matrix, x: px, y: py, color } = piece
|
||||||
|
for (let y = 0; y < matrix.length; y++) {
|
||||||
|
for (let x = 0; x < matrix[y].length; x++) {
|
||||||
|
if (!matrix[y][x]) continue
|
||||||
|
const by = py + y
|
||||||
|
const bx = px + x
|
||||||
|
if (by >= 0 && by < BOARD_H && bx >= 0 && bx < BOARD_W) {
|
||||||
|
next[by][bx] = color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLines(board) {
|
||||||
|
const kept = board.filter((row) => row.some((cell) => !cell))
|
||||||
|
const removed = BOARD_H - kept.length
|
||||||
|
const cleared = [
|
||||||
|
...Array.from({ length: removed }, () => Array.from({ length: BOARD_W }, () => null)),
|
||||||
|
...kept,
|
||||||
|
]
|
||||||
|
return { board: cleared, removed }
|
||||||
|
}
|
||||||
|
|
||||||
|
function ghostDropY(board, piece) {
|
||||||
|
let y = piece.y
|
||||||
|
while (!collides(board, { ...piece, y: y + 1 })) y += 1
|
||||||
|
return y
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TetrisGame() {
|
||||||
|
const [board, setBoard] = useState(() => newBoard())
|
||||||
|
const [piece, setPiece] = useState(() => randomPiece())
|
||||||
|
const [nextPiece, setNextPiece] = useState(() => randomPiece())
|
||||||
|
const [score, setScore] = useState(0)
|
||||||
|
const [lines, setLines] = useState(0)
|
||||||
|
const [status, setStatus] = useState('idle')
|
||||||
|
|
||||||
|
const statusRef = useRef(status)
|
||||||
|
useEffect(() => {
|
||||||
|
statusRef.current = status
|
||||||
|
}, [status])
|
||||||
|
|
||||||
|
const resetGame = useCallback(() => {
|
||||||
|
setBoard(newBoard())
|
||||||
|
setPiece(randomPiece())
|
||||||
|
setNextPiece(randomPiece())
|
||||||
|
setScore(0)
|
||||||
|
setLines(0)
|
||||||
|
setStatus('playing')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const lockAndSpawn = useCallback((fallingPiece) => {
|
||||||
|
setBoard((prevBoard) => {
|
||||||
|
const merged = mergePiece(prevBoard, fallingPiece)
|
||||||
|
const { board: clearedBoard, removed } = clearLines(merged)
|
||||||
|
if (removed > 0) {
|
||||||
|
setLines((v) => v + removed)
|
||||||
|
setScore((v) => v + removed * 100)
|
||||||
|
}
|
||||||
|
return clearedBoard
|
||||||
|
})
|
||||||
|
|
||||||
|
setPiece((_) => {
|
||||||
|
const incoming = {
|
||||||
|
...nextPiece,
|
||||||
|
matrix: nextPiece.matrix.map((row) => [...row]),
|
||||||
|
x: Math.floor((BOARD_W - nextPiece.matrix[0].length) / 2),
|
||||||
|
y: -1,
|
||||||
|
}
|
||||||
|
|
||||||
|
setNextPiece(randomPiece())
|
||||||
|
|
||||||
|
setBoard((latestBoard) => {
|
||||||
|
if (collides(latestBoard, incoming)) {
|
||||||
|
setStatus('gameover')
|
||||||
|
return latestBoard
|
||||||
|
}
|
||||||
|
return latestBoard
|
||||||
|
})
|
||||||
|
|
||||||
|
return incoming
|
||||||
|
})
|
||||||
|
}, [nextPiece])
|
||||||
|
|
||||||
|
const tryMove = useCallback((dx, dy) => {
|
||||||
|
if (statusRef.current !== 'playing') return
|
||||||
|
setPiece((prev) => {
|
||||||
|
const moved = { ...prev, x: prev.x + dx, y: prev.y + dy }
|
||||||
|
if (!collides(board, moved)) return moved
|
||||||
|
|
||||||
|
if (dy > 0 && dx === 0) {
|
||||||
|
lockAndSpawn(prev)
|
||||||
|
}
|
||||||
|
return prev
|
||||||
|
})
|
||||||
|
}, [board, lockAndSpawn])
|
||||||
|
|
||||||
|
const hardDrop = useCallback(() => {
|
||||||
|
if (statusRef.current !== 'playing') return
|
||||||
|
const dropY = ghostDropY(board, piece)
|
||||||
|
const landed = { ...piece, y: dropY }
|
||||||
|
setPiece(landed)
|
||||||
|
lockAndSpawn(landed)
|
||||||
|
}, [board, piece, lockAndSpawn])
|
||||||
|
|
||||||
|
const rotatePiece = useCallback(() => {
|
||||||
|
if (statusRef.current !== 'playing') return
|
||||||
|
setPiece((prev) => {
|
||||||
|
const rotated = { ...prev, matrix: rotateMatrix(prev.matrix) }
|
||||||
|
if (!collides(board, rotated)) return rotated
|
||||||
|
const kickLeft = { ...rotated, x: rotated.x - 1 }
|
||||||
|
if (!collides(board, kickLeft)) return kickLeft
|
||||||
|
const kickRight = { ...rotated, x: rotated.x + 1 }
|
||||||
|
if (!collides(board, kickRight)) return kickRight
|
||||||
|
return prev
|
||||||
|
})
|
||||||
|
}, [board])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (status !== 'playing') return undefined
|
||||||
|
const id = setInterval(() => {
|
||||||
|
tryMove(0, 1)
|
||||||
|
}, TICK_MS)
|
||||||
|
return () => clearInterval(id)
|
||||||
|
}, [status, tryMove])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onKey = (e) => {
|
||||||
|
if (e.code === 'Space' && statusRef.current !== 'playing') {
|
||||||
|
e.preventDefault()
|
||||||
|
resetGame()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (statusRef.current !== 'playing') return
|
||||||
|
|
||||||
|
if (e.code === 'ArrowLeft') {
|
||||||
|
e.preventDefault()
|
||||||
|
tryMove(-1, 0)
|
||||||
|
} else if (e.code === 'ArrowRight') {
|
||||||
|
e.preventDefault()
|
||||||
|
tryMove(1, 0)
|
||||||
|
} else if (e.code === 'ArrowDown') {
|
||||||
|
e.preventDefault()
|
||||||
|
tryMove(0, 1)
|
||||||
|
} else if (e.code === 'ArrowUp') {
|
||||||
|
e.preventDefault()
|
||||||
|
rotatePiece()
|
||||||
|
} else if (e.code === 'Space') {
|
||||||
|
e.preventDefault()
|
||||||
|
hardDrop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', onKey)
|
||||||
|
return () => window.removeEventListener('keydown', onKey)
|
||||||
|
}, [hardDrop, resetGame, rotatePiece, tryMove])
|
||||||
|
|
||||||
|
const ghostY = useMemo(() => ghostDropY(board, piece), [board, piece])
|
||||||
|
|
||||||
|
const renderGrid = useMemo(() => {
|
||||||
|
const grid = cloneBoard(board)
|
||||||
|
|
||||||
|
for (let y = 0; y < piece.matrix.length; y++) {
|
||||||
|
for (let x = 0; x < piece.matrix[y].length; x++) {
|
||||||
|
if (!piece.matrix[y][x]) continue
|
||||||
|
const by = ghostY + y
|
||||||
|
const bx = piece.x + x
|
||||||
|
if (by >= 0 && by < BOARD_H && bx >= 0 && bx < BOARD_W && !grid[by][bx]) {
|
||||||
|
grid[by][bx] = 'ghost'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let y = 0; y < piece.matrix.length; y++) {
|
||||||
|
for (let x = 0; x < piece.matrix[y].length; x++) {
|
||||||
|
if (!piece.matrix[y][x]) continue
|
||||||
|
const by = piece.y + y
|
||||||
|
const bx = piece.x + x
|
||||||
|
if (by >= 0 && by < BOARD_H && bx >= 0 && bx < BOARD_W) {
|
||||||
|
grid[by][bx] = piece.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid
|
||||||
|
}, [board, ghostY, piece])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section className="tetris-shell" aria-label="Tetris mini game">
|
||||||
|
<header className="tetris-header">
|
||||||
|
<h3 className="tetris-title">Tetris</h3>
|
||||||
|
<div className="tetris-metrics">
|
||||||
|
<span>SCORE: {String(score).padStart(4, '0')}</span>
|
||||||
|
<span>LINES: {lines}</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div className="tetris-layout">
|
||||||
|
<div className="tetris-board" role="img" aria-label="Tetris board">
|
||||||
|
{renderGrid.map((row, y) =>
|
||||||
|
row.map((cell, x) => {
|
||||||
|
const key = `${y}-${x}`
|
||||||
|
const className = cell === 'ghost' ? 'tetris-cell tetris-cell--ghost' : 'tetris-cell'
|
||||||
|
const style = cell && cell !== 'ghost' ? { '--cell-color': cell } : undefined
|
||||||
|
return <div key={key} className={className} style={style} />
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<aside className="tetris-sidebar">
|
||||||
|
<div className="tetris-next">
|
||||||
|
<p className="tetris-label">NEXT</p>
|
||||||
|
<div className="tetris-next-grid">
|
||||||
|
{nextPiece.matrix.map((row, y) =>
|
||||||
|
row.map((filled, x) => (
|
||||||
|
<div
|
||||||
|
key={`n-${y}-${x}`}
|
||||||
|
className={filled ? 'tetris-next-cell tetris-next-cell--on' : 'tetris-next-cell'}
|
||||||
|
style={filled ? { '--cell-color': nextPiece.color } : undefined}
|
||||||
|
/>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button className="tetris-btn" type="button" onClick={resetGame}>
|
||||||
|
{status === 'playing' ? 'Restart' : 'Start'}
|
||||||
|
</button>
|
||||||
|
</aside>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="tetris-help">
|
||||||
|
controls: left/right move, up rotate, down soft drop, space hard drop/start
|
||||||
|
</p>
|
||||||
|
|
||||||
|
{status !== 'playing' && (
|
||||||
|
<div className="tetris-overlay" aria-hidden="true">
|
||||||
|
<p>{status === 'gameover' ? 'GAME OVER' : 'PRESS START'}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -76,3 +76,18 @@
|
|||||||
.home-footer-text { font-size: 0.82rem; color: var(--text-muted); }
|
.home-footer-text { font-size: 0.82rem; color: var(--text-muted); }
|
||||||
.home-link { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
|
.home-link { color: var(--accent); text-decoration: underline; text-underline-offset: 3px; }
|
||||||
.home-link:hover { color: var(--cyan); }
|
.home-link:hover { color: var(--cyan); }
|
||||||
|
|
||||||
|
.home-tetris {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.7rem;
|
||||||
|
margin-top: 0.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-tetris-title {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
color: var(--cyan);
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import PageLayout from '../../components/PageLayout/PageLayout.jsx'
|
import PageLayout from '../../components/PageLayout/PageLayout.jsx'
|
||||||
|
import TetrisGame from '../../components/TetrisGame/TetrisGame.jsx'
|
||||||
import './HomePage.css'
|
import './HomePage.css'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -37,6 +38,11 @@ export default function HomePage() {
|
|||||||
<a href="/404" className="home-link">See the 404 game</a>
|
<a href="/404" className="home-link">See the 404 game</a>
|
||||||
</span>
|
</span>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
<section className="home-tetris" aria-label="Tetris mini game">
|
||||||
|
<h2 className="home-tetris-title">Mini Game: Tetris</h2>
|
||||||
|
<TetrisGame />
|
||||||
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</PageLayout>
|
</PageLayout>
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user