SudokuInput.cs
using Sandbox;

public sealed class SudokuInput : Component
{
	public static bool Up;
	public static bool Down;
	public static bool Left;
	public static bool Right;
	public static bool Escape; // latched: stays true until SudokuGamePanel handles it
	public static bool Erase;
	public static int  NumberPressed; // 0 = none, 1–9 = digit

	float _lastEscape;

	protected override void OnUpdate()
	{
		Up    = Input.Keyboard.Pressed( "UPARROW" )    || Input.Keyboard.Pressed( "w" );
		Down  = Input.Keyboard.Pressed( "DOWNARROW" )  || Input.Keyboard.Pressed( "s" );
		Left  = Input.Keyboard.Pressed( "LEFTARROW" )  || Input.Keyboard.Pressed( "a" );
		Right = Input.Keyboard.Pressed( "RIGHTARROW" ) || Input.Keyboard.Pressed( "d" );

		// Number row 1–9  OR  numpad KP_1–KP_9
		NumberPressed = 0;
		for ( int i = 0; i < 9; i++ )
		{
			string row = (i + 1).ToString();
			string kp  = $"KP_{i + 1}";
			if ( Input.Keyboard.Pressed( row ) || Input.Keyboard.Pressed( kp ) )
			{
				NumberPressed = i + 1;
				break;
			}
		}

		// DEL or BACKSPACE to erase
		Erase = Input.Keyboard.Pressed( "DEL" ) || Input.Keyboard.Pressed( "BACKSPACE" );

		// Swallow Escape so the engine's pause menu stays shut, and latch it for the panel's own
		// pause menu. Latching (instead of a one-frame flag) means component/panel update order
		// can't make the press vanish. The short debounce stops both sources firing one press twice.
		bool esc = Input.EscapePressed || Input.Keyboard.Pressed( "escape" );
		if ( Input.EscapePressed ) Input.EscapePressed = false;
		if ( esc && RealTime.Now - _lastEscape > 0.15f )
		{
			_lastEscape = RealTime.Now;
			Escape = true;
		}
	}
}