add vertical shake; apply shake animation when nav/push/pull fails

Hunter
Saturday, March 7th, 2026 at 6:52 PM

index.html+74-38

index.html
 216  		.shake {
 217  			animation: shake 0.25s ease-out;
 218  		}
 219 +		@keyframes shake-vertical {
 220 +			0% { transform: translateY(0); }
 221 +			20% { transform: translateY(3px); }
 222 +			40% { transform: translateY(-3px); }
 223 +			60% { transform: translateY(3px); }
 224 +			80% { transform: translateY(-3px); }
 225 +			100% { transform: translateY(0); }
 226 +		}
 227 +		.shake-vertical {
 228 +			animation: shake-vertical 0.25s ease-out;
 229 +		}
 230  		html {
 231  			scroll-behavior: smooth;
 232  			overflow-y: scroll;

 598  							keyHandler.enter.blocked = true;
 599  							addNewSubtask(currentTask, task);
 600  						}
     -					} else if (e.key === 'ArrowDown' && !e.shiftKey) {
 601 +					} else if (e.key === 'ArrowDown' && !e.shiftKey && !e.metaKey && !e.ctrlKey) {
 602  						if (keyHandler.arrowDown.blocked) {
 603  							e.preventDefault();
 604  							return;

 734  				}
 735  			}
 736  
     -			function applyShakeAnimation(taskId) {
 737 +			function applyShakeAnimation(taskId, direction = 'horizontal') {
 738  				const checkbox = document.querySelector(`.task-container[data-id="${taskId}"] .checkbox-label`);
 739  				if (checkbox) {
     -					checkbox.classList.add('shake');
 740 +					const className = direction === 'vertical' ? 'shake-vertical' : 'shake'
 741 +					checkbox.classList.add(className);
 742  					setTimeout(() => {
     -						checkbox.classList.remove('shake');
     -					}, 250); // Remove class after animation completes
 743 +						checkbox.classList.remove(className);
 744 +					}, 250);
 745  				}
 746  			}
 747  

 882  			}
 883  
 884  			function handleKeyDown(e, task) {
 885 +				const cmd = e.metaKey || e.ctrlKey;
 886 +
 887  				if (e.key === 'Enter' && e.shiftKey) {
 888  					e.preventDefault();
 889  					if (!keyHandler.shiftEnter.pressed) {
 890  						keyHandler.shiftEnter.pressed = true;
 891  						toggleTaskState(task);
 892  					}
     -				} else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
 893 +				} else if (e.key === 'ArrowUp' && cmd && !e.shiftKey) {
 894 +					e.preventDefault();
 895 +					if (!e.repeat && !pushSubtaskIntoTarget(task, 'up')) {
 896 +						applyShakeAnimation(task.id, 'vertical');
 897 +					}
 898 +				} else if (e.key === 'ArrowDown' && cmd && !e.shiftKey) {
 899  					e.preventDefault();
     -					if (!e.repeat) pushSubtaskIntoTarget(task, 'up');
     -				} else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
 900 +					if (!e.repeat && !pushSubtaskIntoTarget(task, 'down')) {
 901 +						applyShakeAnimation(task.id, 'vertical');
 902 +					}
 903 +				} else if (e.key === 'ArrowRight' && cmd && !e.shiftKey) {
 904 +					e.preventDefault();
 905 +					applyShakeAnimation(task.id);
 906 +				} else if (e.key === 'ArrowLeft' && cmd && !e.shiftKey) {
 907 +					e.preventDefault();
 908 +					if (!e.repeat && !pullSubtaskOutLayer(task)) {
 909 +						applyShakeAnimation(task.id);
 910 +					}
 911 +				} else if (e.key === 'ArrowUp' && cmd && e.shiftKey) {
 912 +					e.preventDefault();
 913 +					if (!e.repeat && !pushSubtaskIntoTarget(task, 'up', true)) {
 914 +						applyShakeAnimation(task.id, 'vertical');
 915 +					}
 916 +				} else if (e.key === 'ArrowDown' && cmd && e.shiftKey) {
 917 +					e.preventDefault();
 918 +					if (!e.repeat && !pushSubtaskIntoTarget(task, 'down', true)) {
 919 +						applyShakeAnimation(task.id, 'vertical');
 920 +					}
 921 +				} else if (e.key === 'ArrowLeft' && cmd && e.shiftKey) {
 922 +					e.preventDefault();
 923 +					if (!e.repeat && !pullSubtaskOutLayer(task, true)) {
 924 +						applyShakeAnimation(task.id);
 925 +					}
 926 +				} else if (e.key === 'ArrowRight' && cmd && e.shiftKey) {
 927  					e.preventDefault();
     -					if (!e.repeat) pushSubtaskIntoTarget(task, 'down');
 928 +					applyShakeAnimation(task.id);
 929  				} else if (e.key === 'ArrowUp' && !e.shiftKey) {
 930  					e.preventDefault();
 931  					navigateTasks('up');
 932  				} else if (e.key === 'ArrowDown' && !e.shiftKey) {
 933  					e.preventDefault();
 934  					navigateTasks('down');
     -				} else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
     -					e.preventDefault();
     -					if (!e.repeat) pullSubtaskOutLayer(task, true);
     -				} else if (e.key === 'ArrowUp' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
     -					e.preventDefault();
     -					if (!e.repeat) pushSubtaskIntoTarget(task, 'up', true);
     -				} else if (e.key === 'ArrowDown' && (e.metaKey || e.ctrlKey) && e.shiftKey) {
     -					e.preventDefault();
     -					if (!e.repeat) pushSubtaskIntoTarget(task, 'down', true);
 935  				} else if (e.key === 'ArrowUp' && e.shiftKey) {
 936  					e.preventDefault();
     -					moveSubtask(task, 'up');
 937 +					if (!moveSubtask(task, 'up')) {
 938 +						applyShakeAnimation(task.id, 'vertical');
 939 +					}
 940  				} else if (e.key === 'ArrowDown' && e.shiftKey) {
 941  					e.preventDefault();
     -					moveSubtask(task, 'down');
 942 +					if (!moveSubtask(task, 'down')) {
 943 +						applyShakeAnimation(task.id, 'vertical');
 944 +					}
 945  				} else if (e.key === 'ArrowRight' && e.shiftKey) {
 946  					e.preventDefault();
 947  					if (!keyHandler.shiftRight.pressed) {

 949  						if (task !== currentTask) {
 950  							navigateIntoSubtask(task);
 951  						} else {
     -							// Apply shake animation when trying to navigate into the parent task
 952  							applyShakeAnimation(task.id);
 953  						}
 954  					}
     -				} else if (e.key === 'ArrowLeft' && (e.metaKey || e.ctrlKey) && !e.shiftKey) {
     -					e.preventDefault();
     -					if (!e.repeat) pullSubtaskOutLayer(task);
 955  				} else if (e.key === 'ArrowLeft' && e.shiftKey) {
 956  					e.preventDefault();
 957  					if (!keyHandler.shiftLeft.pressed) {

 962  			}
 963  
 964  			function moveSubtask(subtask, direction) {
 965 +				if (subtask === currentTask) return false;
 966 +
 967  				const parentTask = findParentTask(subtask);
     -				if (!parentTask) return;
 968 +				if (!parentTask) return false;
 969  
 970  				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
     -				if (index === -1) return;
 971 +				if (index === -1) return false;
 972  
 973  				if (direction === 'up' && index > 0) {
 974  					[parentTask.subtasks[index - 1], parentTask.subtasks[index]] = [parentTask.subtasks[index], parentTask.subtasks[index - 1]];
 975  				} else if (direction === 'down' && index < parentTask.subtasks.length - 1) {
 976  					[parentTask.subtasks[index], parentTask.subtasks[index + 1]] = [parentTask.subtasks[index + 1], parentTask.subtasks[index]];
 977 +				} else {
 978 +					return false;
 979  				}
 980  
 981  				renderCurrentView();
 982  				selectAndFocusTask(subtask);
 983  				scheduleSave();
 984 +				return true;
 985  			}
 986  
 987  			function pushSubtaskIntoTarget(subtask, direction, navigate = false) {
 988  				// Don't allow pushing the current parent task
     -				if (subtask === currentTask) {
     -					return;
     -				}
 989 +				if (subtask === currentTask) return false;
 990  
 991  				if (navigate) {
 992  					document.documentElement.style.scrollBehavior = 'auto';
 993  				}
 994  
 995  				const parentTask = findParentTask(subtask);
     -				if (!parentTask) return;
 996 +				if (!parentTask) return false;
 997  
 998  				const index = parentTask.subtasks.findIndex(t => t.id === subtask.id);
     -				if (index === -1) return;
 999 +				if (index === -1) return false;
1000  
1001  				let targetTask = null;
1002  				if (direction === 'up' && index > 0) {

1005  					targetTask = parentTask.subtasks[index + 1];
1006  				}
1007  
     -				if (!targetTask) return;
1008 +				if (!targetTask) return false;
1009  
1010  				// Remove the subtask from its current parent
1011  				parentTask.subtasks.splice(index, 1);

1033  				}
1034  
1035  				scheduleSave();
1036 +				return true;
1037  			}
1038  
1039  			function pullSubtaskOutLayer(subtask, navigate = false) {
1040  				// Don't allow pulling the current parent task
     -				if (subtask === currentTask) {
     -					return;
     -				}
1041 +				if (subtask === currentTask) return false;
1042  
1043  				// Don't allow pulling when at root level
     -				if (taskPath.length <= 1) {
     -					return;
     -				}
1044 +				if (taskPath.length <= 1) return false;
1045  
1046  				if (navigate) {
1047  					document.documentElement.style.scrollBehavior = 'auto';

1091  				}
1092  
1093  				scheduleSave();
1094 +				return true;
1095  			}
1096  
1097