diff --git a/src/App.css b/src/App.css index 09a07a0..cafb124 100644 --- a/src/App.css +++ b/src/App.css @@ -126,7 +126,7 @@ body { font-size: clamp(20px, 5.5vw, 32px); font-weight: 500; line-height: 1; - color: #1976d2; + color: hsl(var(--hue), 69%, 48%); } .cell.given .cell-value { @@ -140,23 +140,23 @@ body { /* Cell highlighting */ .cell.selected { - background: #bbdefb; + background: hsl(var(--hue), 89%, 86%); } .cell.highlighted-row, .cell.highlighted-col, .cell.highlighted-box { - background: #e8eef4; + background: hsl(var(--hue), 22%, 93%); } .cell.same-number { - background: #c8e6f9; + background: hsl(var(--hue), 84%, 88%); } .cell.same-number.highlighted-row, .cell.same-number.highlighted-col, .cell.same-number.highlighted-box { - background: #b3d9f5; + background: hsl(var(--hue), 81%, 83%); } .cell.error { @@ -278,7 +278,7 @@ body { } .num-btn:hover:not(:disabled) { - background: #e3f2fd; + background: hsl(var(--hue), 89%, 96%); transform: translateY(-1px); box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12); } @@ -292,10 +292,15 @@ body { cursor: default; } +.num-btn.highlighted { + background: hsl(var(--hue), 89%, 86%); + box-shadow: 0 0 0 2px hsl(var(--hue), 69%, 48%), 0 2px 6px rgba(0, 0, 0, 0.12); +} + .num-btn-value { font-size: clamp(16px, 4vw, 22px); font-weight: 600; - color: #1976d2; + color: hsl(var(--hue), 69%, 48%); line-height: 1; } @@ -333,9 +338,9 @@ body { } .action-btn.active { - background: #1976d2; + background: hsl(var(--hue), 69%, 48%); color: #fff; - box-shadow: 0 2px 8px rgba(25, 118, 210, 0.3); + box-shadow: 0 2px 8px hsla(var(--hue), 69%, 48%, 0.3); } /* === GameControls === */ @@ -370,8 +375,8 @@ body { .difficulty-select:focus { outline: none; - border-color: #1976d2; - box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15); + border-color: hsl(var(--hue), 69%, 48%); + box-shadow: 0 0 0 3px hsla(var(--hue), 69%, 48%, 0.15); } /* === Toggle Switch === */ @@ -424,7 +429,7 @@ body { } .checkbox-label input:checked + .toggle-track { - background: #1976d2; + background: hsl(var(--hue), 69%, 48%); } .checkbox-label input:checked + .toggle-track::after { @@ -454,14 +459,14 @@ body { .btn-primary { flex: 1; - background: #1976d2; + background: hsl(var(--hue), 69%, 48%); color: #fff; - box-shadow: 0 2px 6px rgba(25, 118, 210, 0.25); + box-shadow: 0 2px 6px hsla(var(--hue), 69%, 48%, 0.25); } .btn-primary:hover:not(:disabled) { - background: #1565c0; - box-shadow: 0 3px 8px rgba(25, 118, 210, 0.35); + background: hsl(var(--hue), 72%, 42%); + box-shadow: 0 3px 8px hsla(var(--hue), 69%, 48%, 0.35); } .btn-secondary { @@ -694,7 +699,7 @@ body { } .app.dark .cell-value { - color: #64b5f6; + color: hsl(var(--hue), 87%, 68%); } .app.dark .cell.given .cell-value { @@ -706,7 +711,7 @@ body { } .app.dark .cell.selected { - background: #1a3a5c; + background: hsl(var(--hue), 56%, 23%); } .app.dark .cell.highlighted-row, @@ -716,13 +721,13 @@ body { } .app.dark .cell.same-number { - background: #1a3050; + background: hsl(var(--hue), 50%, 21%); } .app.dark .cell.same-number.highlighted-row, .app.dark .cell.same-number.highlighted-col, .app.dark .cell.same-number.highlighted-box { - background: #1a2a45; + background: hsl(var(--hue), 45%, 18%); } .app.dark .cell.error { @@ -739,11 +744,16 @@ body { } .app.dark .num-btn:hover:not(:disabled) { - background: #1a3a5c; + background: hsl(var(--hue), 56%, 23%); +} + +.app.dark .num-btn.highlighted { + background: hsl(var(--hue), 72%, 17%); + box-shadow: 0 0 0 2px hsl(var(--hue), 87%, 68%), 0 2px 6px rgba(0, 0, 0, 0.3); } .app.dark .num-btn-value { - color: #64b5f6; + color: hsl(var(--hue), 87%, 68%); } .app.dark .num-btn-count { @@ -761,12 +771,12 @@ body { } .app.dark .btn-primary { - background: #1565c0; - box-shadow: 0 2px 6px rgba(21, 101, 192, 0.4); + background: hsl(var(--hue), 72%, 42%); + box-shadow: 0 2px 6px hsla(var(--hue), 72%, 42%, 0.4); } .app.dark .btn-primary:hover:not(:disabled) { - background: #1976d2; + background: hsl(var(--hue), 69%, 48%); } .app.dark .difficulty-select { @@ -776,8 +786,8 @@ body { } .app.dark .difficulty-select:focus { - border-color: #64b5f6; - box-shadow: 0 0 0 3px rgba(100, 181, 246, 0.15); + border-color: hsl(var(--hue), 87%, 68%); + box-shadow: 0 0 0 3px hsla(var(--hue), 87%, 68%, 0.15); } .app.dark .action-btn { @@ -791,7 +801,7 @@ body { } .app.dark .action-btn.active { - background: #1565c0; + background: hsl(var(--hue), 72%, 42%); color: #fff; } diff --git a/src/App.jsx b/src/App.jsx index e31ddcf..fd53d49 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -14,6 +14,7 @@ function App() { numberCounts, newGame, selectCell, + deselect, setNumber, clearCell, toggleNotes, @@ -25,13 +26,32 @@ function App() { } = useGame() const [showSettings, setShowSettings] = useState(false) + const [hue, setHue] = useState(() => { + const saved = parseInt(localStorage.getItem('sudoku-hue'), 10) + return isNaN(saved) ? 211 : saved + }) useEffect(() => { document.body.style.background = state.darkMode ? '#1a1a2e' : '#f8f9fa' }, [state.darkMode]) + useEffect(() => { + localStorage.setItem('sudoku-hue', hue) + }, [hue]) + + useEffect(() => { + function handleKeyDown(e) { + if (e.key === ' ' && !e.ctrlKey && !e.metaKey && !e.altKey) { + e.preventDefault() + setHue(Math.floor(Math.random() * 360)) + } + } + window.addEventListener('keydown', handleKeyDown) + return () => window.removeEventListener('keydown', handleKeyDown) + }, []) + return ( -
+

Sudoku

@@ -50,14 +70,17 @@ function App() { solution={state.solution} notes={state.notes} selected={state.selected} + highlightedNumber={state.highlightedNumber} showErrors={state.showErrors} celebrating={state.showEffects ? state.celebrating : new Set()} selectCell={selectCell} + deselect={deselect} /> @@ -20,9 +21,10 @@ function Board({ board, puzzle, solution, notes, selected, showErrors, celebrati Math.floor(selected.col / 3) === Math.floor(c / 3) const isSameNumber = !isSelected && - selectedValue !== 0 && value !== 0 && - value === selectedValue + activeNumber !== null && + activeNumber !== 0 && + value === activeNumber const isError = showErrors && !isGiven && @@ -43,7 +45,7 @@ function Board({ board, puzzle, solution, notes, selected, showErrors, celebrati isSameNumber={isSameNumber} isError={isError} isCelebrating={isCelebrating} - onClick={() => selectCell(r, c)} + onClick={(e) => e.ctrlKey || e.metaKey ? deselect() : selectCell(r, c)} /> ) }) diff --git a/src/components/NumberPad.jsx b/src/components/NumberPad.jsx index 6fcc756..7c5d4b0 100644 --- a/src/components/NumberPad.jsx +++ b/src/components/NumberPad.jsx @@ -1,17 +1,18 @@ import React from 'react' -function NumberPad({ onNumber, numberCounts }) { +function NumberPad({ onNumber, numberCounts, highlightedNumber }) { return (
{[1, 2, 3, 4, 5, 6, 7, 8, 9].map(n => { const remaining = 9 - numberCounts[n] const disabled = remaining <= 0 + const isHighlighted = highlightedNumber === n return (