diff --git a/app/globals.css b/app/globals.css index 7b13957..188d5fc 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,8 +1,8 @@ @import "tailwindcss"; :root { - --background: #fafaf9; - --foreground: #1c1917; + --background: #f8fafc; + --foreground: #1e293b; } @theme inline { @@ -14,8 +14,8 @@ @media (prefers-color-scheme: dark) { :root { - --background: #0c0a09; - --foreground: #e7e5e4; + --background: #0f172a; + --foreground: #e2e8f0; } } @@ -25,19 +25,22 @@ body { font-family: var(--font-sans), Arial, Helvetica, sans-serif; } -/* Crossed cell diagonal line */ -.cell-crossed { - position: relative; +/* Equal-width grid cells */ +.loto-grid { + display: grid; + grid-template-columns: repeat(9, 1fr); + gap: 0; } +/* Crossed cell diagonal line */ .cell-crossed::after { content: ""; position: absolute; - inset: 4px; + inset: 6px; background-image: linear-gradient( to bottom right, transparent calc(50% - 1.5px), - currentColor, + #ef4444, transparent calc(50% + 1.5px) ); pointer-events: none; diff --git a/app/page.tsx b/app/page.tsx index 917cf75..75cd30d 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -14,62 +14,53 @@ export default function Home() { const [crossed, setCrossed] = useState([]); const [showInstructions, setShowInstructions] = useState(false); - // Load saved state on mount useEffect(() => { const savedGrid = loadGrid(); if (savedGrid) { setGrid(savedGrid); - const savedCrossed = loadCrossedState(); setCrossed( - savedCrossed ?? - savedGrid.map((row) => row.map(() => false)) + loadCrossedState() ?? savedGrid.map((row) => row.map(() => false)) ); } }, []); - // Persist crossed state on change useEffect(() => { - if (crossed.length > 0) { - saveCrossedState(crossed); - } + if (crossed.length > 0) saveCrossedState(crossed); }, [crossed]); const handleGenerate = useCallback(() => { if (grid && !confirm("Bạn có muốn tạo lại bảng không?")) return; const newGrid = generateGrid(); - setGrid(newGrid); const newCrossed = newGrid.map((row) => row.map(() => false)); + setGrid(newGrid); setCrossed(newCrossed); saveGrid(newGrid); saveCrossedState(newCrossed); }, [grid]); - const handleCellClick = useCallback( - (row: number, col: number) => { - setCrossed((prev) => { - const next = prev.map((r) => [...r]); - next[row][col] = !next[row][col]; - return next; - }); - }, - [] - ); + const handleCellClick = useCallback((row: number, col: number) => { + setCrossed((prev) => { + const next = prev.map((r) => [...r]); + next[row][col] = !next[row][col]; + return next; + }); + }, []); return ( -
-
+
+
{/* Header */} -
-

+
+

Lô tô

-

- Tạo bảng chơi lô tô, lấy cảm hứng từ những buổi họp lớp thiếu - giấy chơi lô tô của TN1 (2014-2017) +

+ Lấy cảm hứng từ những buổi họp lớp thiếu giấy chơi lô tô +
của TN1 (2014–2017)

@@ -77,85 +68,81 @@ export default function Home() { {/* Instructions */} {showInstructions && ( -
-

Hướng dẫn

-
    +
    +
    • - Nhấn Tạo bảng mới để tạo bảng mới + Nhấn Tạo bảng mới để tạo bảng
    • Nhấn vào ô số để đánh dấu khi số được xổ
    • -
    • Nhấn lại ô đã đánh dấu để bỏ đánh dấu
    • -
    • Bảng và trạng thái được lưu tự động
    • +
    • Nhấn lại để bỏ đánh dấu
    • +
    • Bảng được lưu tự động
    )} {/* Generate button */} -
    +
    {/* Grid */} - {grid && ( -
    - - - {grid.map((row, i) => ( - - {row.map((num, j) => { - const hasNumber = num > 0; - const isCrossed = - hasNumber && crossed[i]?.[j]; + {grid ? ( +
    +
    + {grid.flat().map((num, idx) => { + const row = Math.floor(idx / 9); + const col = idx % 9; + const hasNumber = num > 0; + const isCrossed = hasNumber && crossed[row]?.[col]; - return ( -
    - ); - })} - - ))} - -
    handleCellClick(i, j) - : undefined - } - className={` - relative text-center border border-stone-200 dark:border-stone-700 - h-10 sm:h-12 text-sm sm:text-lg font-medium - transition-colors select-none - ${ - hasNumber - ? isCrossed - ? "cell-crossed bg-amber-100 dark:bg-amber-900/40 text-amber-800 dark:text-amber-300 cursor-pointer" - : "bg-amber-50 dark:bg-amber-950/30 text-stone-800 dark:text-stone-200 cursor-pointer hover:bg-amber-100 dark:hover:bg-amber-900/30" - : "bg-stone-50 dark:bg-stone-900/50" - } - `} - > - {hasNumber ? num : ""} -
    + return ( +
    handleCellClick(row, col) : undefined} + className={` + relative flex items-center justify-center + aspect-square text-base sm:text-xl font-bold + border-r border-b border-slate-200/80 dark:border-slate-700/60 + transition-all select-none + ${hasNumber + ? isCrossed + ? "cell-crossed bg-red-50 dark:bg-red-950/30 text-red-400 dark:text-red-500 cursor-pointer" + : "bg-white dark:bg-slate-800 text-slate-800 dark:text-slate-100 cursor-pointer hover:bg-indigo-50 dark:hover:bg-indigo-950/30 hover:text-indigo-600 dark:hover:text-indigo-400" + : "bg-slate-50 dark:bg-slate-900/60" + } + `} + > + {hasNumber ? num : ""} +
    + ); + })} +
    - )} - - {/* Empty state */} - {!grid && ( -
    + ) : ( +
    Nhấn “Tạo bảng mới” để bắt đầu chơi
    )} {/* Footer */} -
    - made by miti99 +
diff --git a/next-env.d.ts b/next-env.d.ts index c4b7818..9edff1c 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/dev/types/routes.d.ts"; +import "./.next/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.