feat: proper mobile zoom
This commit is contained in:
		@ -8,6 +8,7 @@
 | 
			
		||||
		y?: number;
 | 
			
		||||
		zIndex?: number;
 | 
			
		||||
		canvasOffset?: { x: number; y: number };
 | 
			
		||||
		canvasZoom?: number;
 | 
			
		||||
		onMove?: (id: number, x: number, y: number) => void;
 | 
			
		||||
		onClick?: (id: number) => void;
 | 
			
		||||
		onDrop?: (id: number, x: number, y: number) => void;
 | 
			
		||||
@ -22,6 +23,7 @@
 | 
			
		||||
		y = $bindable(0),
 | 
			
		||||
		zIndex = $bindable(0),
 | 
			
		||||
		canvasOffset = { x: 0, y: 0 },
 | 
			
		||||
		canvasZoom = 1,
 | 
			
		||||
		onMove,
 | 
			
		||||
		onClick,
 | 
			
		||||
		onDrop
 | 
			
		||||
@ -38,10 +40,11 @@
 | 
			
		||||
		
 | 
			
		||||
		isDragging = true;
 | 
			
		||||
		hasMoved = false;
 | 
			
		||||
		const rect = cardElement.getBoundingClientRect();
 | 
			
		||||
		
 | 
			
		||||
		// Calculate offset relative to card position in canvas coordinates
 | 
			
		||||
		dragOffset = {
 | 
			
		||||
			x: event.clientX - rect.left,
 | 
			
		||||
			y: event.clientY - rect.top
 | 
			
		||||
			x: (event.clientX - canvasOffset.x) / canvasZoom - x,
 | 
			
		||||
			y: (event.clientY - canvasOffset.y) / canvasZoom - y
 | 
			
		||||
		};
 | 
			
		||||
		dragStartPos = {
 | 
			
		||||
			x: event.clientX,
 | 
			
		||||
@ -61,8 +64,8 @@
 | 
			
		||||
			hasMoved = true;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		const newX = event.clientX - dragOffset.x - canvasOffset.x;
 | 
			
		||||
		const newY = event.clientY - dragOffset.y - canvasOffset.y;
 | 
			
		||||
		const newX = (event.clientX - canvasOffset.x) / canvasZoom - dragOffset.x;
 | 
			
		||||
		const newY = (event.clientY - canvasOffset.y) / canvasZoom - dragOffset.y;
 | 
			
		||||
		
 | 
			
		||||
		x = newX;
 | 
			
		||||
		y = newY;
 | 
			
		||||
@ -84,6 +87,60 @@
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchStart(event: TouchEvent) {
 | 
			
		||||
		if (event.touches.length !== 1) return;
 | 
			
		||||
		event.stopPropagation(); // Prevent canvas panning
 | 
			
		||||
		
 | 
			
		||||
		const touch = event.touches[0];
 | 
			
		||||
		isDragging = true;
 | 
			
		||||
		hasMoved = false;
 | 
			
		||||
		
 | 
			
		||||
		// Calculate offset relative to card position in canvas coordinates
 | 
			
		||||
		dragOffset = {
 | 
			
		||||
			x: (touch.clientX - canvasOffset.x) / canvasZoom - x,
 | 
			
		||||
			y: (touch.clientY - canvasOffset.y) / canvasZoom - y
 | 
			
		||||
		};
 | 
			
		||||
		dragStartPos = {
 | 
			
		||||
			x: touch.clientX,
 | 
			
		||||
			y: touch.clientY
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchMove(event: TouchEvent) {
 | 
			
		||||
		if (!isDragging || event.touches.length !== 1) return;
 | 
			
		||||
		
 | 
			
		||||
		const touch = event.touches[0];
 | 
			
		||||
		const deltaX = Math.abs(touch.clientX - dragStartPos.x);
 | 
			
		||||
		const deltaY = Math.abs(touch.clientY - dragStartPos.y);
 | 
			
		||||
		
 | 
			
		||||
		if (deltaX > 10 || deltaY > 10) {
 | 
			
		||||
			hasMoved = true;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		const newX = (touch.clientX - canvasOffset.x) / canvasZoom - dragOffset.x;
 | 
			
		||||
		const newY = (touch.clientY - canvasOffset.y) / canvasZoom - dragOffset.y;
 | 
			
		||||
		
 | 
			
		||||
		x = newX;
 | 
			
		||||
		y = newY;
 | 
			
		||||
		
 | 
			
		||||
		onMove?.(id, newX, newY);
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchEnd(event: TouchEvent) {
 | 
			
		||||
		if (isDragging) {
 | 
			
		||||
			onDrop?.(id, x, y);
 | 
			
		||||
			if (!hasMoved) {
 | 
			
		||||
				flipped = !flipped;
 | 
			
		||||
				onClick?.(id);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		isDragging = false;
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	$effect(() => {
 | 
			
		||||
		if (isDragging) {
 | 
			
		||||
			document.addEventListener('mousemove', handleMouseMove);
 | 
			
		||||
@ -104,6 +161,9 @@
 | 
			
		||||
	style="left: {x}px; top: {y}px; z-index: {zIndex};"
 | 
			
		||||
	onmousedown={handleMouseDown}
 | 
			
		||||
	onclick={handleClick}
 | 
			
		||||
	ontouchstart={handleTouchStart}
 | 
			
		||||
	ontouchmove={handleTouchMove}
 | 
			
		||||
	ontouchend={handleTouchEnd}
 | 
			
		||||
	role="button"
 | 
			
		||||
	tabindex="0"
 | 
			
		||||
>
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@
 | 
			
		||||
		zIndex: number;
 | 
			
		||||
		color: string;
 | 
			
		||||
		canvasOffset?: { x: number; y: number };
 | 
			
		||||
		canvasZoom?: number;
 | 
			
		||||
		onMove?: (id: number, x: number, y: number) => void;
 | 
			
		||||
		onUpdate?: (id: number, text: string) => void;
 | 
			
		||||
		onDelete?: (id: number) => void;
 | 
			
		||||
@ -20,6 +21,7 @@
 | 
			
		||||
		zIndex = $bindable(0),
 | 
			
		||||
		color,
 | 
			
		||||
		canvasOffset = { x: 0, y: 0 },
 | 
			
		||||
		canvasZoom = 1,
 | 
			
		||||
		onMove,
 | 
			
		||||
		onUpdate,
 | 
			
		||||
		onDelete
 | 
			
		||||
@ -38,10 +40,11 @@
 | 
			
		||||
		
 | 
			
		||||
		isDragging = true;
 | 
			
		||||
		hasMoved = false;
 | 
			
		||||
		const rect = noteElement.getBoundingClientRect();
 | 
			
		||||
		
 | 
			
		||||
		// Calculate offset relative to note position in canvas coordinates
 | 
			
		||||
		dragOffset = {
 | 
			
		||||
			x: event.clientX - rect.left,
 | 
			
		||||
			y: event.clientY - rect.top
 | 
			
		||||
			x: (event.clientX - canvasOffset.x) / canvasZoom - x,
 | 
			
		||||
			y: (event.clientY - canvasOffset.y) / canvasZoom - y
 | 
			
		||||
		};
 | 
			
		||||
		dragStartPos = {
 | 
			
		||||
			x: event.clientX,
 | 
			
		||||
@ -61,8 +64,8 @@
 | 
			
		||||
			hasMoved = true;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		const newX = event.clientX - dragOffset.x - canvasOffset.x;
 | 
			
		||||
		const newY = event.clientY - dragOffset.y - canvasOffset.y;
 | 
			
		||||
		const newX = (event.clientX - canvasOffset.x) / canvasZoom - dragOffset.x;
 | 
			
		||||
		const newY = (event.clientY - canvasOffset.y) / canvasZoom - dragOffset.y;
 | 
			
		||||
		
 | 
			
		||||
		x = newX;
 | 
			
		||||
		y = newY;
 | 
			
		||||
@ -102,6 +105,59 @@
 | 
			
		||||
		onDelete?.(id);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchStart(event: TouchEvent) {
 | 
			
		||||
		if (event.touches.length !== 1) return;
 | 
			
		||||
		event.stopPropagation(); // Prevent canvas panning
 | 
			
		||||
		
 | 
			
		||||
		const touch = event.touches[0];
 | 
			
		||||
		isDragging = true;
 | 
			
		||||
		hasMoved = false;
 | 
			
		||||
		
 | 
			
		||||
		// Calculate offset relative to note position in canvas coordinates
 | 
			
		||||
		dragOffset = {
 | 
			
		||||
			x: (touch.clientX - canvasOffset.x) / canvasZoom - x,
 | 
			
		||||
			y: (touch.clientY - canvasOffset.y) / canvasZoom - y
 | 
			
		||||
		};
 | 
			
		||||
		dragStartPos = {
 | 
			
		||||
			x: touch.clientX,
 | 
			
		||||
			y: touch.clientY
 | 
			
		||||
		};
 | 
			
		||||
		
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchMove(event: TouchEvent) {
 | 
			
		||||
		if (!isDragging || event.touches.length !== 1) return;
 | 
			
		||||
		
 | 
			
		||||
		const touch = event.touches[0];
 | 
			
		||||
		const deltaX = Math.abs(touch.clientX - dragStartPos.x);
 | 
			
		||||
		const deltaY = Math.abs(touch.clientY - dragStartPos.y);
 | 
			
		||||
		
 | 
			
		||||
		if (deltaX > 5 || deltaY > 5) {
 | 
			
		||||
			hasMoved = true;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		const newX = (touch.clientX - canvasOffset.x) / canvasZoom - dragOffset.x;
 | 
			
		||||
		const newY = (touch.clientY - canvasOffset.y) / canvasZoom - dragOffset.y;
 | 
			
		||||
		
 | 
			
		||||
		x = newX;
 | 
			
		||||
		y = newY;
 | 
			
		||||
		
 | 
			
		||||
		onMove?.(id, newX, newY);
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleTouchEnd(event: TouchEvent) {
 | 
			
		||||
		if (isDragging) {
 | 
			
		||||
			if (!hasMoved) {
 | 
			
		||||
				isEditing = true;
 | 
			
		||||
				setTimeout(() => textareaElement?.focus(), 0);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		isDragging = false;
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	$effect(() => {
 | 
			
		||||
		if (isDragging) {
 | 
			
		||||
			document.addEventListener('mousemove', handleMouseMove);
 | 
			
		||||
@ -122,6 +178,9 @@
 | 
			
		||||
	style="left: {x}px; top: {y}px; z-index: {zIndex}; background-color: {color};"
 | 
			
		||||
	onmousedown={handleMouseDown}
 | 
			
		||||
	onclick={handleClick}
 | 
			
		||||
	ontouchstart={handleTouchStart}
 | 
			
		||||
	ontouchmove={handleTouchMove}
 | 
			
		||||
	ontouchend={handleTouchEnd}
 | 
			
		||||
	role="button"
 | 
			
		||||
	tabindex="0"
 | 
			
		||||
>
 | 
			
		||||
 | 
			
		||||
@ -101,9 +101,13 @@
 | 
			
		||||
 | 
			
		||||
	let deckPosition = $state({ x: 50, y: 300 });
 | 
			
		||||
	let canvasOffset = $state({ x: 0, y: 0 });
 | 
			
		||||
	let canvasZoom = $state(1);
 | 
			
		||||
	let isDragging = $state(false);
 | 
			
		||||
	let dragStart = $state({ x: 0, y: 0 });
 | 
			
		||||
	let dragStartOffset = $state({ x: 0, y: 0 });
 | 
			
		||||
	let isPinching = $state(false);
 | 
			
		||||
	let lastPinchDistance = $state(0);
 | 
			
		||||
	let pinchCenter = $state({ x: 0, y: 0 });
 | 
			
		||||
 | 
			
		||||
	let cards = $state<CardData[]>([]);
 | 
			
		||||
	let maxZIndex = $state(0);
 | 
			
		||||
@ -304,12 +308,36 @@
 | 
			
		||||
 | 
			
		||||
	function handleCanvasWheel(event: WheelEvent) {
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
		const scrollSpeed = 1;
 | 
			
		||||
		canvasOffset.x -= event.deltaX * scrollSpeed;
 | 
			
		||||
		canvasOffset.y -= event.deltaY * scrollSpeed;
 | 
			
		||||
		
 | 
			
		||||
		canvasOffset.x = Math.max(-2000, Math.min(2000, canvasOffset.x));
 | 
			
		||||
		canvasOffset.y = Math.max(-2000, Math.min(2000, canvasOffset.y));
 | 
			
		||||
		if (event.ctrlKey || event.metaKey) {
 | 
			
		||||
			// Zoom with mouse wheel + ctrl/cmd towards viewport center
 | 
			
		||||
			const zoomFactor = event.deltaY > 0 ? 0.9 : 1.1;
 | 
			
		||||
			const newZoom = Math.max(0.1, Math.min(3, canvasZoom * zoomFactor));
 | 
			
		||||
			
 | 
			
		||||
			// Get viewport center
 | 
			
		||||
			const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
 | 
			
		||||
			const centerX = rect.width / 2;
 | 
			
		||||
			const centerY = rect.height / 2;
 | 
			
		||||
			
 | 
			
		||||
			// Convert viewport center to canvas coordinates before zoom
 | 
			
		||||
			const canvasCenterX = (centerX - canvasOffset.x) / canvasZoom;
 | 
			
		||||
			const canvasCenterY = (centerY - canvasOffset.y) / canvasZoom;
 | 
			
		||||
			
 | 
			
		||||
			// Update zoom
 | 
			
		||||
			canvasZoom = newZoom;
 | 
			
		||||
			
 | 
			
		||||
			// Adjust offset to keep the viewport center fixed
 | 
			
		||||
			canvasOffset.x = centerX - canvasCenterX * canvasZoom;
 | 
			
		||||
			canvasOffset.y = centerY - canvasCenterY * canvasZoom;
 | 
			
		||||
		} else {
 | 
			
		||||
			// Pan with mouse wheel
 | 
			
		||||
			const scrollSpeed = 1 / canvasZoom; // Adjust pan speed for zoom level
 | 
			
		||||
			canvasOffset.x -= event.deltaX * scrollSpeed;
 | 
			
		||||
			canvasOffset.y -= event.deltaY * scrollSpeed;
 | 
			
		||||
			
 | 
			
		||||
			canvasOffset.x = Math.max(-2000, Math.min(2000, canvasOffset.x));
 | 
			
		||||
			canvasOffset.y = Math.max(-2000, Math.min(2000, canvasOffset.y));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleCanvasMouseDown(event: MouseEvent) {
 | 
			
		||||
@ -344,6 +372,96 @@
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function getTouchDistance(touches: TouchList) {
 | 
			
		||||
		if (touches.length < 2) return 0;
 | 
			
		||||
		const touch1 = touches[0];
 | 
			
		||||
		const touch2 = touches[1];
 | 
			
		||||
		return Math.sqrt(
 | 
			
		||||
			Math.pow(touch2.clientX - touch1.clientX, 2) + 
 | 
			
		||||
			Math.pow(touch2.clientY - touch1.clientY, 2)
 | 
			
		||||
		);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function getTouchCenter(touches: TouchList) {
 | 
			
		||||
		if (touches.length < 2) return { x: 0, y: 0 };
 | 
			
		||||
		const touch1 = touches[0];
 | 
			
		||||
		const touch2 = touches[1];
 | 
			
		||||
		return {
 | 
			
		||||
			x: (touch1.clientX + touch2.clientX) / 2,
 | 
			
		||||
			y: (touch1.clientY + touch2.clientY) / 2
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleCanvasTouchStart(event: TouchEvent) {
 | 
			
		||||
		isDragging = true;
 | 
			
		||||
		if (event.touches.length >= 2) {
 | 
			
		||||
			// Two or more touches - pinch zoom
 | 
			
		||||
			isPinching = true;
 | 
			
		||||
			lastPinchDistance = getTouchDistance(event.touches);
 | 
			
		||||
			pinchCenter = getTouchCenter(event.touches);
 | 
			
		||||
			
 | 
			
		||||
			// Convert pinch center to canvas coordinates
 | 
			
		||||
			const rect = (event.currentTarget as HTMLElement).getBoundingClientRect();
 | 
			
		||||
			pinchCenter.x -= rect.left;
 | 
			
		||||
			pinchCenter.y -= rect.top;
 | 
			
		||||
		} else if (event.touches.length === 1) {
 | 
			
		||||
			// Single touch - pan
 | 
			
		||||
			const touch = event.touches[0];
 | 
			
		||||
			dragStart.x = touch.clientX;
 | 
			
		||||
			dragStart.y = touch.clientY;
 | 
			
		||||
			dragStartOffset.x = canvasOffset.x;
 | 
			
		||||
			dragStartOffset.y = canvasOffset.y;
 | 
			
		||||
		}
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleCanvasTouchMove(event: TouchEvent) {
 | 
			
		||||
		if (event.touches.length === 1 && isDragging && !isPinching) {
 | 
			
		||||
			// Single touch pan
 | 
			
		||||
			const touch = event.touches[0];
 | 
			
		||||
			const deltaX = touch.clientX - dragStart.x;
 | 
			
		||||
			const deltaY = touch.clientY - dragStart.y;
 | 
			
		||||
			
 | 
			
		||||
			canvasOffset.x = Math.max(-2000, Math.min(2000, dragStartOffset.x + deltaX));
 | 
			
		||||
			canvasOffset.y = Math.max(-2000, Math.min(2000, dragStartOffset.y + deltaY));
 | 
			
		||||
		} else if (event.touches.length === 2 && isPinching) {
 | 
			
		||||
			// Pinch zoom
 | 
			
		||||
			const currentDistance = getTouchDistance(event.touches);
 | 
			
		||||
			const zoomFactor = currentDistance / lastPinchDistance;
 | 
			
		||||
			const newZoom = Math.max(0.1, Math.min(3, canvasZoom * zoomFactor));
 | 
			
		||||
			
 | 
			
		||||
			// Convert pinch center to canvas coordinates before zoom
 | 
			
		||||
			const canvasCenterX = (pinchCenter.x - canvasOffset.x) / canvasZoom;
 | 
			
		||||
			const canvasCenterY = (pinchCenter.y - canvasOffset.y) / canvasZoom;
 | 
			
		||||
			
 | 
			
		||||
			// Update zoom
 | 
			
		||||
			canvasZoom = newZoom;
 | 
			
		||||
			
 | 
			
		||||
			// Adjust offset to keep the pinch center fixed
 | 
			
		||||
			canvasOffset.x = pinchCenter.x - canvasCenterX * canvasZoom;
 | 
			
		||||
			canvasOffset.y = pinchCenter.y - canvasCenterY * canvasZoom;
 | 
			
		||||
			
 | 
			
		||||
			lastPinchDistance = currentDistance;
 | 
			
		||||
		}
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function handleCanvasTouchEnd(event: TouchEvent) {
 | 
			
		||||
		if (event.touches.length === 0) {
 | 
			
		||||
			isDragging = false;
 | 
			
		||||
			isPinching = false;
 | 
			
		||||
		} else if (event.touches.length === 1) {
 | 
			
		||||
			isPinching = false;
 | 
			
		||||
			// A pinch ended. Reset pan state for the remaining finger to prevent a jump.
 | 
			
		||||
			const touch = event.touches[0];
 | 
			
		||||
			dragStart.x = touch.clientX;
 | 
			
		||||
			dragStart.y = touch.clientY;
 | 
			
		||||
			dragStartOffset.x = canvasOffset.x;
 | 
			
		||||
			dragStartOffset.y = canvasOffset.y;
 | 
			
		||||
		}
 | 
			
		||||
		event.preventDefault();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	function addStickyNote() {
 | 
			
		||||
		const randomColor = noteColors[Math.floor(Math.random() * noteColors.length)];
 | 
			
		||||
		const newNote: StickyNote = {
 | 
			
		||||
@ -387,6 +505,7 @@
 | 
			
		||||
			cards: cards.map(card => ({...card})),
 | 
			
		||||
			stickyNotes: stickyNotes.map(note => ({...note})),
 | 
			
		||||
			canvasOffset,
 | 
			
		||||
			canvasZoom,
 | 
			
		||||
			deckPosition,
 | 
			
		||||
			maxZIndex,
 | 
			
		||||
			nextNoteId,
 | 
			
		||||
@ -431,6 +550,7 @@
 | 
			
		||||
					cards = canvasState.cards || [];
 | 
			
		||||
					stickyNotes = canvasState.stickyNotes || [];
 | 
			
		||||
					canvasOffset = canvasState.canvasOffset || { x: 0, y: 0 };
 | 
			
		||||
					canvasZoom = canvasState.canvasZoom || 1;
 | 
			
		||||
					deckPosition = canvasState.deckPosition || { x: 50, y: 300 };
 | 
			
		||||
					maxZIndex = canvasState.maxZIndex || cards.length;
 | 
			
		||||
					nextNoteId = canvasState.nextNoteId || 1;
 | 
			
		||||
@ -485,16 +605,20 @@
 | 
			
		||||
		onmousemove={handleCanvasMouseMove}
 | 
			
		||||
		onmouseup={handleCanvasMouseUp}
 | 
			
		||||
		oncontextmenu={handleCanvasContextMenu}
 | 
			
		||||
		ontouchstart={handleCanvasTouchStart}
 | 
			
		||||
		ontouchmove={handleCanvasTouchMove}
 | 
			
		||||
		ontouchend={handleCanvasTouchEnd}
 | 
			
		||||
	>
 | 
			
		||||
		<div 
 | 
			
		||||
			class="canvas-content" 
 | 
			
		||||
			style="transform: translate({canvasOffset.x}px, {canvasOffset.y}px);"
 | 
			
		||||
			style="transform: translate({canvasOffset.x}px, {canvasOffset.y}px) scale({canvasZoom});"
 | 
			
		||||
		>
 | 
			
		||||
			{#each cards as card (card.id)}
 | 
			
		||||
				<Card
 | 
			
		||||
					{...card}
 | 
			
		||||
					backImage={card.isRiskCategory ? 'risks/risikocat_back.png' : backImage}
 | 
			
		||||
					canvasOffset={canvasOffset}
 | 
			
		||||
					canvasZoom={canvasZoom}
 | 
			
		||||
					bind:flipped={card.flipped}
 | 
			
		||||
					bind:x={card.x}
 | 
			
		||||
					bind:y={card.y}
 | 
			
		||||
@ -509,6 +633,7 @@
 | 
			
		||||
				<StickyNote
 | 
			
		||||
					{...note}
 | 
			
		||||
					canvasOffset={canvasOffset}
 | 
			
		||||
					canvasZoom={canvasZoom}
 | 
			
		||||
					bind:text={note.text}
 | 
			
		||||
					bind:x={note.x}
 | 
			
		||||
					bind:y={note.y}
 | 
			
		||||
@ -597,18 +722,40 @@
 | 
			
		||||
		height: 100%;
 | 
			
		||||
		overflow: hidden;
 | 
			
		||||
		cursor: grab;
 | 
			
		||||
		touch-action: none;
 | 
			
		||||
		user-select: none;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.game-area:active {
 | 
			
		||||
		cursor: grabbing;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@media (max-width: 768px) {
 | 
			
		||||
		.controls {
 | 
			
		||||
			top: 10px;
 | 
			
		||||
			right: 10px;
 | 
			
		||||
			gap: 5px;
 | 
			
		||||
			flex-direction: column;
 | 
			
		||||
			align-items: flex-end;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		.control-group {
 | 
			
		||||
			padding: 6px 10px;
 | 
			
		||||
		}
 | 
			
		||||
		
 | 
			
		||||
		.controls button {
 | 
			
		||||
			padding: 8px 12px;
 | 
			
		||||
			font-size: 14px;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.canvas-content {
 | 
			
		||||
		position: relative;
 | 
			
		||||
		width: 500vw;
 | 
			
		||||
		height: 500vh;
 | 
			
		||||
		min-width: 500vw;
 | 
			
		||||
		min-height: 500vh;
 | 
			
		||||
		transform-origin: 0 0;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	.deck-area {
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user