Add a visual preview of the next pixel placement

Implement a pixel preview feature on hover, showing the selected color before committing the pixel placement.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 0385ea33-cde8-4bbd-8fce-8d192d30eb41
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/870d08ce-da3b-4822-9874-c2fe2b7628b1/0385ea33-cde8-4bbd-8fce-8d192d30eb41/ZegP3fv
This commit is contained in:
freesemar93 2025-08-18 12:41:59 +00:00
parent 83fc03b313
commit f017853c09
4 changed files with 33 additions and 4 deletions

View File

@ -25,6 +25,7 @@ export function Canvas({
const [zoom, setZoom] = useState(1); const [zoom, setZoom] = useState(1);
const [pixelSize, setPixelSize] = useState(8); const [pixelSize, setPixelSize] = useState(8);
const [mouseCoords, setMouseCoords] = useState<{x: number, y: number} | null>(null); const [mouseCoords, setMouseCoords] = useState<{x: number, y: number} | null>(null);
const [previewPixel, setPreviewPixel] = useState<{x: number, y: number} | null>(null);
// Create pixel map for O(1) lookup // Create pixel map for O(1) lookup
const pixelMap = new Map<string, string>(); const pixelMap = new Map<string, string>();
@ -39,10 +40,14 @@ export function Canvas({
const handlePixelMouseEnter = (x: number, y: number) => { const handlePixelMouseEnter = (x: number, y: number) => {
setMouseCoords({ x, y }); setMouseCoords({ x, y });
if (!cooldownActive) {
setPreviewPixel({ x, y });
}
}; };
const handlePixelMouseLeave = () => { const handlePixelMouseLeave = () => {
setMouseCoords(null); setMouseCoords(null);
setPreviewPixel(null);
}; };
const handleZoomIn = () => { const handleZoomIn = () => {
@ -136,19 +141,24 @@ export function Canvas({
{Array.from({ length: canvasHeight }, (_, y) => {Array.from({ length: canvasHeight }, (_, y) =>
Array.from({ length: canvasWidth }, (_, x) => { Array.from({ length: canvasWidth }, (_, x) => {
const pixelColor = pixelMap.get(`${x},${y}`) || "#FFFFFF"; const pixelColor = pixelMap.get(`${x},${y}`) || "#FFFFFF";
const isPreview = previewPixel && previewPixel.x === x && previewPixel.y === y;
const previewColor = isPreview && !cooldownActive ? selectedColor : pixelColor;
return ( return (
<div <div
key={`${x}-${y}`} key={`${x}-${y}`}
className={cn( className={cn(
"pixel cursor-pointer hover:scale-110 hover:z-10 absolute", "pixel cursor-pointer hover:scale-110 hover:z-10 absolute",
cooldownActive && "cursor-not-allowed" cooldownActive && "cursor-not-allowed",
isPreview && !cooldownActive && "pixel-preview"
)} )}
style={{ style={{
backgroundColor: pixelColor, backgroundColor: previewColor,
width: `${pixelSize}px`, width: `${pixelSize}px`,
height: `${pixelSize}px`, height: `${pixelSize}px`,
left: `${x * pixelSize}px`, left: `${x * pixelSize}px`,
top: `${y * pixelSize}px` top: `${y * pixelSize}px`,
opacity: isPreview && !cooldownActive ? 0.7 : 1
}} }}
onClick={() => handlePixelClick(x, y)} onClick={() => handlePixelClick(x, y)}
onMouseEnter={() => handlePixelMouseEnter(x, y)} onMouseEnter={() => handlePixelMouseEnter(x, y)}
@ -178,6 +188,11 @@ export function Canvas({
Mouse: ({mouseCoords.x}, {mouseCoords.y}) Mouse: ({mouseCoords.x}, {mouseCoords.y})
</div> </div>
)} )}
{previewPixel && !cooldownActive && (
<div className="text-xs text-blue-400 mt-1">
Vorschau: {selectedColor}
</div>
)}
</div> </div>
{/* Zoom Controls */} {/* Zoom Controls */}

View File

@ -148,7 +148,7 @@
/* Optimierte Pixel-Hover-Effekte */ /* Optimierte Pixel-Hover-Effekte */
.pixel { .pixel {
transition: transform 0.15s ease-out, box-shadow 0.15s ease-out; transition: transform 0.15s ease-out, box-shadow 0.15s ease-out, opacity 0.1s ease-out;
} }
.pixel:hover { .pixel:hover {
@ -158,6 +158,18 @@
box-shadow: 0 0 8px rgba(255, 255, 255, 0.3); box-shadow: 0 0 8px rgba(255, 255, 255, 0.3);
} }
/* Pixel-Vorschau */
.pixel-preview {
border: 2px solid rgba(255, 255, 255, 0.8);
box-shadow: 0 0 12px rgba(255, 255, 255, 0.5);
animation: previewPulse 1s ease-in-out infinite alternate;
}
@keyframes previewPulse {
0% { box-shadow: 0 0 12px rgba(255, 255, 255, 0.5); }
100% { box-shadow: 0 0 20px rgba(255, 255, 255, 0.8); }
}
/* Toast animations */ /* Toast animations */
.toast-enter { .toast-enter {
animation: slideInRight 0.3s ease-out; animation: slideInRight 0.3s ease-out;

View File

@ -0,0 +1 @@
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/></svg>

After

Width:  |  Height:  |  Size: 120 B

View File

@ -0,0 +1 @@
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg"><rect width="100%" height="100%" fill="#FFFFFF"/></svg>

After

Width:  |  Height:  |  Size: 120 B