diff --git a/keyboards/tssouthpaw/README.md b/keyboards/tssouthpaw/README.md
new file mode 100644
index 00000000000..b216f358a90
--- /dev/null
+++ b/keyboards/tssouthpaw/README.md
@@ -0,0 +1,31 @@
+# TS Southpaw
+
+A southpaw numpad keyboard with RGB matrix and work timer functionality.
+
+* Keyboard Maintainer: [TS Design Works LLC](https://github.com/tsdesignworks)
+* Hardware Supported: TS Southpaw Rev 1.6, RP2040 MCU
+* Hardware Availability: [GitHub](https://github.com/tsdesignworks/tssouthpaw)
+
+Make example for this keyboard (after setting up your build environment):
+
+ make tssouthpaw:default
+
+Flashing example for this keyboard:
+
+ make tssouthpaw:default:flash
+
+See the [build environment setup](https://docs.qmk.fm/#/getting_started_build_tools) and the [make instructions](https://docs.qmk.fm/#/getting_started_make_guide) for more information. Brand new to QMK? Start with our [Complete Newbs Guide](https://docs.qmk.fm/#/newbs).
+
+## Bootloader
+
+Enter the bootloader in 2 ways:
+
+* **Physical reset button**: Briefly press the button on the back of the PCB
+* **Bootmagic reset**: Hold down the ESC key and plug in the keyboard
+
+## Features
+
+* RGB Matrix with 104 LEDs
+* Rotary encoder for volume control
+* Work timer with visual notifications
+* Southpaw numpad layout (numpad on the left side)
\ No newline at end of file
diff --git a/keyboards/tssouthpaw/config.h b/keyboards/tssouthpaw/config.h
new file mode 100644
index 00000000000..d1fbde9e0b5
--- /dev/null
+++ b/keyboards/tssouthpaw/config.h
@@ -0,0 +1,50 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ #pragma once
+
+ // LED indices for indicator LEDs
+ #define CAPS_LOCK_LED 62 // LED index for Caps Lock indicator
+ #define NUM_LOCK_LED 2 // LED index for Num Lock indicator
+ #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE
+
+ // Work Timer LED indices
+ #define WORK_TIMER_LED_START 4 // F1 key LED
+ #define WORK_TIMER_LED_END 15 // F12 key LED
+
+ // Work Timer EEPROM addresses
+ // Using built-in wear leveling for RP2040
+ #define EEPROM_WORK_TIMER_ACTIVE 32 // 1 byte - active state
+ #define EEPROM_WORK_TIMER_START 33 // 4 bytes - start time
+ #define EEPROM_WORK_TIMER_ELAPSED 37 // 4 bytes - elapsed work time
+ // 4 bytes - total break time (37+4)
+ // 1 byte - timer type (37+8)
+ // 1 byte - break state (37+9)
+ // 4 bytes - break start time (37+10)
+ // Total: 15 bytes
+
+ // Work Timer Notification Configuration
+ #define WORK_TIMER_NOTIFICATION_BRIGHTNESS 250 // Brightness for work timer notifications
+
+ // Minimum brightness for timer notifications to ensure visibility
+ #define RGB_MATRIX_MINIMUM_BRIGHTNESS 64 // Ensures timer notifications remain visible
+
+ // Dynamic macro configuration
+ #define DYNAMIC_MACRO_SIZE 128 // More efficient macro size
+
+ // RP2040 Settings
+ #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET // Enable double-tap reset
+ #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET_TIMEOUT 500U // Timeout window in ms
diff --git a/keyboards/tssouthpaw/keyboard.json b/keyboards/tssouthpaw/keyboard.json
new file mode 100644
index 00000000000..e6c2b73135a
--- /dev/null
+++ b/keyboards/tssouthpaw/keyboard.json
@@ -0,0 +1,310 @@
+{
+ "manufacturer": "TS Design Works LLC",
+ "keyboard_name": "TS-Southpaw-Rev-1.6",
+ "maintainer": "ts_design_works_llc",
+ "bootloader": "rp2040",
+ "processor": "RP2040",
+ "diode_direction": "COL2ROW",
+ "url": "https://github.com/tsdesignworks/tssouthpaw",
+ "build": {
+ "lto": true
+ },
+ "matrix_pins": {
+ "cols": ["GP26", "GP25", "GP24", "GP23", "GP22", "GP16", "GP17", "GP15", "GP14", "GP13", "GP12", "GP11", "GP10", "GP9", "GP8", "GP7", "GP6", "GP5", "GP4", "GP3", "GP2"],
+ "rows": ["GP29", "GP28", "GP21", "GP20", "GP19", "GP18"]
+ },
+ "usb": {
+ "device_version": "0.0.2",
+ "pid": "0x6163",
+ "vid": "0x7856",
+ "suspend_wakeup_delay": 200
+ },
+ "ws2812": {
+ "driver": "vendor",
+ "pin": "GP27"
+ },
+ "encoder": {
+ "rotary": [
+ {"pin_a": "GP1", "pin_b": "GP0", "resolution": 4}
+ ]
+ },
+ "features": {
+ "bootmagic": true,
+ "caps_word": true,
+ "extrakey": true,
+ "nkro": true,
+ "rgb_matrix": true,
+ "dynamic_macro": true,
+
+ },
+ "debounce": 5,
+ "rgb_matrix": {
+ "driver": "ws2812",
+ "sleep": true,
+ "led_process_limit": 8,
+ "default_mode": "SOLID_COLOR",
+ "animations": {
+ "solid_color": true,
+ "breathing": true,
+ "gradient_up_down": true,
+ "gradient_left_right": true,
+ "cycle_out_in": true,
+ "cycle_out_in_dual": true,
+ "cycle_pinwheel": true,
+ "cycle_spiral": true,
+ "rainbow_moving_chevron": true,
+ "rainbow_beacon": true,
+ "rainbow": true,
+ "rainbow_swirl": true,
+ "rainbow_pinwheels": true,
+ "solid_reactive_simple": true,
+ "solid_reactive": true,
+ "splash": true,
+ "multisplash": true,
+ "solid_splash": true,
+ "raindrops": true,
+ "jellybean_raindrops": true,
+ "typing_heatmap": true,
+ "digital_rain": true,
+ "pixel_rain": true
+ },
+ "layout": [
+ {"matrix": [0, 0], "x": 0, "y": 0, "flags": 4},
+ {"matrix": [0, 1], "x": 11, "y": 0, "flags": 4},
+ {"matrix": [0, 2], "x": 22, "y": 0, "flags": 4},
+ {"matrix": [0, 3], "x": 34, "y": 0, "flags": 4},
+ {"matrix": [0, 8], "x": 45, "y": 0, "flags": 4},
+ {"matrix": [0, 9], "x": 56, "y": 0, "flags": 4},
+ {"matrix": [0, 10], "x": 68, "y": 0, "flags": 4},
+ {"matrix": [0, 11], "x": 78, "y": 0, "flags": 4},
+ {"matrix": [0, 12], "x": 89, "y": 0, "flags": 4},
+ {"matrix": [0, 13], "x": 100, "y": 0, "flags": 4},
+ {"matrix": [0, 14], "x": 112, "y": 0, "flags": 4},
+ {"matrix": [0, 15], "x": 123, "y": 0, "flags": 4},
+ {"matrix": [0, 16], "x": 134, "y": 0, "flags": 4},
+ {"matrix": [0, 17], "x": 146, "y": 0, "flags": 4},
+ {"matrix": [0, 18], "x": 157, "y": 0, "flags": 4},
+ {"matrix": [0, 19], "x": 168, "y": 0, "flags": 4},
+ {"matrix": [0, 20], "x": 179, "y": 0, "flags": 4},
+ {"matrix": [1, 0], "x": 191, "y": 0, "flags": 4},
+ {"matrix": [1, 1], "x": 202, "y": 0, "flags": 4},
+ {"matrix": [1, 2], "x": 213, "y": 0, "flags": 4},
+ {"matrix": [1, 3], "x": 224, "y": 0, "flags": 4},
+ {"matrix": [1, 4], "x": 0, "y": 13, "flags": 4},
+ {"matrix": [1, 5], "x": 11, "y": 13, "flags": 4},
+ {"matrix": [1, 6], "x": 22, "y": 13, "flags": 4},
+ {"matrix": [1, 7], "x": 34, "y": 13, "flags": 4},
+ {"matrix": [1, 8], "x": 45, "y": 13, "flags": 4},
+ {"matrix": [1, 9], "x": 56, "y": 13, "flags": 4},
+ {"matrix": [1, 10], "x": 68, "y": 13, "flags": 4},
+ {"matrix": [1, 11], "x": 78, "y": 13, "flags": 4},
+ {"matrix": [1, 12], "x": 89, "y": 13, "flags": 4},
+ {"matrix": [1, 13], "x": 100, "y": 13, "flags": 4},
+ {"matrix": [1, 14], "x": 112, "y": 13, "flags": 4},
+ {"matrix": [1, 15], "x": 123, "y": 13, "flags": 4},
+ {"matrix": [1, 16], "x": 134, "y": 13, "flags": 4},
+ {"matrix": [1, 17], "x": 146, "y": 13, "flags": 4},
+ {"matrix": [1, 18], "x": 157, "y": 13, "flags": 4},
+ {"matrix": [1, 20], "x": 168, "y": 13, "flags": 4},
+ {"matrix": [2, 0], "x": 179, "y": 13, "flags": 4},
+ {"matrix": [2, 1], "x": 191, "y": 13, "flags": 4},
+ {"matrix": [2, 2], "x": 202, "y": 13, "flags": 4},
+ {"matrix": [2, 4], "x": 213, "y": 13, "flags": 4},
+ {"matrix": [2, 5], "x": 224, "y": 13, "flags": 4},
+ {"matrix": [2, 7], "x": 0, "y": 26, "flags": 4},
+ {"matrix": [2, 8], "x": 11, "y": 26, "flags": 4},
+ {"matrix": [2, 9], "x": 22, "y": 26, "flags": 4},
+ {"matrix": [2, 10], "x": 34, "y": 26, "flags": 4},
+ {"matrix": [2, 11], "x": 45, "y": 26, "flags": 4},
+ {"matrix": [2, 12], "x": 56, "y": 26, "flags": 4},
+ {"matrix": [2, 13], "x": 68, "y": 26, "flags": 4},
+ {"matrix": [2, 14], "x": 78, "y": 26, "flags": 4},
+ {"matrix": [2, 15], "x": 89, "y": 26, "flags": 4},
+ {"matrix": [2, 16], "x": 100, "y": 26, "flags": 4},
+ {"matrix": [2, 17], "x": 112, "y": 26, "flags": 4},
+ {"matrix": [2, 18], "x": 123, "y": 26, "flags": 4},
+ {"matrix": [2, 19], "x": 134, "y": 26, "flags": 4},
+ {"matrix": [2, 20], "x": 146, "y": 26, "flags": 4},
+ {"matrix": [3, 0], "x": 157, "y": 26, "flags": 4},
+ {"matrix": [3, 1], "x": 168, "y": 26, "flags": 4},
+ {"matrix": [3, 2], "x": 179, "y": 26, "flags": 4},
+ {"matrix": [3, 3], "x": 191, "y": 26, "flags": 4},
+ {"matrix": [3, 4], "x": 202, "y": 26, "flags": 4},
+ {"matrix": [3, 5], "x": 213, "y": 26, "flags": 4},
+ {"matrix": [3, 7], "x": 224, "y": 26, "flags": 4},
+ {"matrix": [3, 8], "x": 0, "y": 38, "flags": 4},
+ {"matrix": [3, 9], "x": 11, "y": 38, "flags": 4},
+ {"matrix": [3, 10], "x": 22, "y": 38, "flags": 4},
+ {"matrix": [3, 11], "x": 34, "y": 38, "flags": 4},
+ {"matrix": [3, 12], "x": 45, "y": 38, "flags": 4},
+ {"matrix": [3, 13], "x": 56, "y": 38, "flags": 4},
+ {"matrix": [3, 14], "x": 68, "y": 38, "flags": 4},
+ {"matrix": [3, 15], "x": 78, "y": 38, "flags": 4},
+ {"matrix": [3, 16], "x": 89, "y": 38, "flags": 4},
+ {"matrix": [3, 17], "x": 100, "y": 38, "flags": 4},
+ {"matrix": [3, 18], "x": 112, "y": 38, "flags": 4},
+ {"matrix": [3, 20], "x": 123, "y": 38, "flags": 4},
+ {"matrix": [4, 0], "x": 134, "y": 38, "flags": 4},
+ {"matrix": [4, 1], "x": 146, "y": 38, "flags": 4},
+ {"matrix": [4, 2], "x": 157, "y": 38, "flags": 4},
+ {"matrix": [4, 5], "x": 168, "y": 38, "flags": 4},
+ {"matrix": [4, 7], "x": 179, "y": 38, "flags": 4},
+ {"matrix": [4, 9], "x": 191, "y": 38, "flags": 4},
+ {"matrix": [4, 10], "x": 202, "y": 38, "flags": 4},
+ {"matrix": [4, 11], "x": 213, "y": 38, "flags": 4},
+ {"matrix": [4, 12], "x": 224, "y": 38, "flags": 4},
+ {"matrix": [4, 13], "x": 0, "y": 51, "flags": 4},
+ {"matrix": [4, 14], "x": 11, "y": 51, "flags": 4},
+ {"matrix": [4, 15], "x": 22, "y": 51, "flags": 4},
+ {"matrix": [4, 16], "x": 34, "y": 51, "flags": 4},
+ {"matrix": [4, 17], "x": 45, "y": 51, "flags": 4},
+ {"matrix": [4, 18], "x": 56, "y": 51, "flags": 4},
+ {"matrix": [4, 19], "x": 68, "y": 51, "flags": 4},
+ {"matrix": [5, 1], "x": 78, "y": 51, "flags": 4},
+ {"matrix": [5, 2], "x": 89, "y": 51, "flags": 4},
+ {"matrix": [5, 3], "x": 100, "y": 51, "flags": 4},
+ {"matrix": [5, 4], "x": 112, "y": 51, "flags": 4},
+ {"matrix": [5, 5], "x": 123, "y": 51, "flags": 4},
+ {"matrix": [5, 6], "x": 134, "y": 51, "flags": 4},
+ {"matrix": [5, 8], "x": 146, "y": 51, "flags": 4},
+ {"matrix": [5, 9], "x": 157, "y": 51, "flags": 4},
+ {"matrix": [5, 13], "x": 168, "y": 51, "flags": 4},
+ {"matrix": [5, 16], "x": 179, "y": 51, "flags": 4},
+ {"matrix": [5, 18], "x": 191, "y": 51, "flags": 4},
+ {"matrix": [5, 19], "x": 202, "y": 51, "flags": 4},
+ {"matrix": [5, 20], "x": 213, "y": 51, "flags": 4}
+ ]
+ },
+ "layouts": {
+ "LAYOUT": {
+ "layout": [
+ {"label": "Esc", "matrix": [0, 0], "x": 0, "y": 0},
+ {"label": "Delete", "matrix": [0, 1], "x": 1, "y": 0},
+ {"label": "Num Lock", "matrix": [0, 2], "x": 2, "y": 0},
+ {"label": "MicMute", "matrix": [0, 3], "x": 3, "y": 0},
+ {"label": "No", "matrix": [0, 4], "x": 4, "y": 0},
+ {"label": "No", "matrix": [0, 5], "x": 5, "y": 0},
+ {"label": "No", "matrix": [0, 6], "x": 6, "y": 0},
+ {"label": "Mute", "matrix": [0, 7], "x": 7, "y": 0},
+ {"label": "F1", "matrix": [0, 8], "x": 8, "y": 0},
+ {"label": "F2", "matrix": [0, 9], "x": 9, "y": 0},
+ {"label": "F3", "matrix": [0, 10], "x": 10, "y": 0},
+ {"label": "F4", "matrix": [0, 11], "x": 11, "y": 0},
+ {"label": "F5", "matrix": [0, 12], "x": 12, "y": 0},
+ {"label": "F6", "matrix": [0, 13], "x": 13, "y": 0},
+ {"label": "F7", "matrix": [0, 14], "x": 14, "y": 0},
+ {"label": "F8", "matrix": [0, 15], "x": 15, "y": 0},
+ {"label": "F9", "matrix": [0, 16], "x": 16, "y": 0},
+ {"label": "F10", "matrix": [0, 17], "x": 17, "y": 0},
+ {"label": "F11", "matrix": [0, 18], "x": 18, "y": 0},
+ {"label": "F12", "matrix": [0, 19], "x": 19, "y": 0},
+ {"label": "LGUI(KC_L)", "matrix": [0, 20], "x": 20, "y": 0},
+ {"label": "Backspace", "matrix": [1, 0], "x": 0, "y": 1},
+ {"label": "/", "matrix": [1, 1], "x": 1, "y": 1},
+ {"label": "*", "matrix": [1, 2], "x": 2, "y": 1},
+ {"label": "-", "matrix": [1, 3], "x": 3, "y": 1},
+ {"label": "DM_REC1", "matrix": [1, 4], "x": 4, "y": 1},
+ {"label": "DM_REC2", "matrix": [1, 5], "x": 5, "y": 1},
+ {"label": "`", "matrix": [1, 6], "x": 6, "y": 1},
+ {"label": "1", "matrix": [1, 7], "x": 7, "y": 1},
+ {"label": "2", "matrix": [1, 8], "x": 8, "y": 1},
+ {"label": "3", "matrix": [1, 9], "x": 9, "y": 1},
+ {"label": "4", "matrix": [1, 10], "x": 10, "y": 1},
+ {"label": "5", "matrix": [1, 11], "x": 11, "y": 1},
+ {"label": "6", "matrix": [1, 12], "x": 12, "y": 1},
+ {"label": "7", "matrix": [1, 13], "x": 13, "y": 1},
+ {"label": "8", "matrix": [1, 14], "x": 14, "y": 1},
+ {"label": "9", "matrix": [1, 15], "x": 15, "y": 1},
+ {"label": "0", "matrix": [1, 16], "x": 16, "y": 1},
+ {"label": "-", "matrix": [1, 17], "x": 17, "y": 1},
+ {"label": "=", "matrix": [1, 18], "x": 18, "y": 1},
+ {"label": "No", "matrix": [1, 19], "x": 19, "y": 1},
+ {"label": "Backspace", "matrix": [1, 20], "x": 20, "y": 1},
+ {"label": "7", "matrix": [2, 0], "x": 0, "y": 2},
+ {"label": "8", "matrix": [2, 1], "x": 1, "y": 2},
+ {"label": "9", "matrix": [2, 2], "x": 2, "y": 2},
+ {"label": "NO", "matrix": [2, 3], "x": 3, "y": 2},
+ {"label": "Calculator", "matrix": [2, 4], "x": 4, "y": 2},
+ {"label": "Print Screen", "matrix": [2, 5], "x": 5, "y": 2},
+ {"label": "No", "matrix": [2, 6], "x": 6, "y": 2},
+ {"label": "Tab", "matrix": [2, 7], "x": 7, "y": 2},
+ {"label": "Q", "matrix": [2, 8], "x": 8, "y": 2},
+ {"label": "W", "matrix": [2, 9], "x": 9, "y": 2},
+ {"label": "E", "matrix": [2, 10], "x": 10, "y": 2},
+ {"label": "R", "matrix": [2, 11], "x": 11, "y": 2},
+ {"label": "T", "matrix": [2, 12], "x": 12, "y": 2},
+ {"label": "Y", "matrix": [2, 13], "x": 13, "y": 2},
+ {"label": "U", "matrix": [2, 14], "x": 14, "y": 2},
+ {"label": "I", "matrix": [2, 15], "x": 15, "y": 2},
+ {"label": "O", "matrix": [2, 16], "x": 16, "y": 2},
+ {"label": "P", "matrix": [2, 17], "x": 17, "y": 2},
+ {"label": "[", "matrix": [2, 18], "x": 18, "y": 2},
+ {"label": "]", "matrix": [2, 19], "x": 19, "y": 2},
+ {"label": "\\", "matrix": [2, 20], "x": 20, "y": 2},
+ {"label": "4", "matrix": [3, 0], "x": 0, "y": 3},
+ {"label": "5", "matrix": [3, 1], "x": 1, "y": 3},
+ {"label": "6", "matrix": [3, 2], "x": 2, "y": 3},
+ {"label": "PSLS", "matrix": [3, 3], "x": 3, "y": 3},
+ {"label": "DM_PLY1", "matrix": [3, 4], "x": 4, "y": 3},
+ {"label": "DM_PLY2", "matrix": [3, 5], "x": 5, "y": 3},
+ {"label": "No", "matrix": [3, 6], "x": 6, "y": 3},
+ {"label": "Caps Lock", "matrix": [3, 7], "x": 7, "y": 3},
+ {"label": "A", "matrix": [3, 8], "x": 8, "y": 3},
+ {"label": "S", "matrix": [3, 9], "x": 9, "y": 3},
+ {"label": "D", "matrix": [3, 10], "x": 10, "y": 3},
+ {"label": "F", "matrix": [3, 11], "x": 11, "y": 3},
+ {"label": "G", "matrix": [3, 12], "x": 12, "y": 3},
+ {"label": "H", "matrix": [3, 13], "x": 13, "y": 3},
+ {"label": "J", "matrix": [3, 14], "x": 14, "y": 3},
+ {"label": "K", "matrix": [3, 15], "x": 15, "y": 3},
+ {"label": "L", "matrix": [3, 16], "x": 16, "y": 3},
+ {"label": ";", "matrix": [3, 17], "x": 17, "y": 3},
+ {"label": "'", "matrix": [3, 18], "x": 18, "y": 3},
+ {"label": "No", "matrix": [3, 19], "x": 19, "y": 3},
+ {"label": "Enter", "matrix": [3, 20], "x": 20, "y": 3},
+ {"label": "1", "matrix": [4, 0], "x": 0, "y": 4},
+ {"label": "2", "matrix": [4, 1], "x": 1, "y": 4},
+ {"label": "3", "matrix": [4, 2], "x": 2, "y": 4},
+ {"label": "NO", "matrix": [4, 3], "x": 3, "y": 4},
+ {"label": "No", "matrix": [4, 4], "x": 4, "y": 4},
+ {"label": "Up", "matrix": [4, 5], "x": 5, "y": 4},
+ {"label": "No", "matrix": [4, 6], "x": 6, "y": 4},
+ {"label": "Shift", "matrix": [4, 7], "x": 7, "y": 4},
+ {"label": "No", "matrix": [4, 8], "x": 8, "y": 4},
+ {"label": "Z", "matrix": [4, 9], "x": 9, "y": 4},
+ {"label": "X", "matrix": [4, 10], "x": 10, "y": 4},
+ {"label": "C", "matrix": [4, 11], "x": 11, "y": 4},
+ {"label": "V", "matrix": [4, 12], "x": 12, "y": 4},
+ {"label": "B", "matrix": [4, 13], "x": 13, "y": 4},
+ {"label": "N", "matrix": [4, 14], "x": 14, "y": 4},
+ {"label": "M", "matrix": [4, 15], "x": 15, "y": 4},
+ {"label": ",", "matrix": [4, 16], "x": 16, "y": 4},
+ {"label": ".", "matrix": [4, 17], "x": 17, "y": 4},
+ {"label": "/", "matrix": [4, 18], "x": 18, "y": 4},
+ {"label": "Shift", "matrix": [4, 19], "x": 19, "y": 4},
+ {"label": "No", "matrix": [4, 20], "x": 20, "y": 4},
+ {"label": "No", "matrix": [5, 0], "x": 0, "y": 5},
+ {"label": "0", "matrix": [5, 1], "x": 1, "y": 5},
+ {"label": ".", "matrix": [5, 2], "x": 2, "y": 5},
+ {"label": "Enter", "matrix": [5, 3], "x": 3, "y": 5},
+ {"label": "Left", "matrix": [5, 4], "x": 4, "y": 5},
+ {"label": "Down", "matrix": [5, 5], "x": 5, "y": 5},
+ {"label": "Right", "matrix": [5, 6], "x": 6, "y": 5},
+ {"label": "No", "matrix": [5, 7], "x": 7, "y": 5},
+ {"label": "Ctrl", "matrix": [5, 8], "x": 8, "y": 5},
+ {"label": "Alt", "matrix": [5, 9], "x": 9, "y": 5},
+ {"label": "No", "matrix": [5, 10], "x": 10, "y": 5},
+ {"label": "No", "matrix": [5, 11], "x": 11, "y": 5},
+ {"label": "No", "matrix": [5, 12], "x": 12, "y": 5},
+ {"label": "Space", "matrix": [5, 13], "x": 13, "y": 5},
+ {"label": "No", "matrix": [5, 14], "x": 14, "y": 5},
+ {"label": "No", "matrix": [5, 15], "x": 15, "y": 5},
+ {"label": "Alt", "matrix": [5, 16], "x": 16, "y": 5},
+ {"label": "No", "matrix": [5, 17], "x": 17, "y": 5},
+ {"label": "GUI", "matrix": [5, 18], "x": 18, "y": 5},
+ {"label": "MO(FN)", "matrix": [5, 19], "x": 19, "y": 5},
+ {"label": "Ctrl", "matrix": [5, 20], "x": 20, "y": 5}
+ ]
+ }
+ }
+}
diff --git a/keyboards/tssouthpaw/keymaps/default/keymap.c b/keyboards/tssouthpaw/keymaps/default/keymap.c
new file mode 100644
index 00000000000..6bf5c995967
--- /dev/null
+++ b/keyboards/tssouthpaw/keymaps/default/keymap.c
@@ -0,0 +1,169 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ #include QMK_KEYBOARD_H
+ #include "rgb_effects/rgb_effects.h"
+ #include "rgb_effects/work_timer.h"
+
+ // Define layers
+ enum layers {
+ BASE, // Base layer
+ FN, // Function layer
+ };
+
+ // Define custom keycodes
+ enum custom_keycodes {
+ KC_MICMUTE = SAFE_RANGE, // Microphone mute key
+ KC_MEDIA, // Media layer toggle
+ KC_WRKTMR, // Work timer toggle
+ KC_WRKPAU, // Work timer pause/resume
+ KC_CPSWRD, // Caps word toggle
+ KC_30MIN, // 30-minute timer
+ KC_1HR, // 1-hour timer
+ KC_4HR, // 4-hour timer
+ KC_8HR, // 8-hour timer
+ KC_10HR, // 10-hour timer
+ };
+
+ #ifdef ENCODER_MAP_ENABLE
+ const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][NUM_DIRECTIONS] = {
+ [BASE] = { ENCODER_CCW_CW(KC_VOLD, KC_VOLU) },
+ [FN] = { ENCODER_CCW_CW(_______, _______) },
+ };
+ #endif
+
+ /**
+ * Key processing function
+ * Handles custom keycodes and effects
+ */
+ bool process_record_user(uint16_t keycode, keyrecord_t *record) {
+ // Handle ESC key with ripple effect
+ if (keycode == KC_ESC) {
+ if (record->event.pressed) {
+ start_esc_ripple_effect();
+ return true; // Let QMK handle the actual keypress
+ } else {
+ stop_esc_ripple_effect();
+ return true;
+ }
+ }
+
+ // Handle other custom keycodes
+ switch (keycode) {
+ case KC_MUTE:
+ if (record->event.pressed) {
+ tap_code(KC_MUTE); // Trigger mute on key press
+ tap_code(KC_MPLY); // Play/pause on key press
+ }
+ return false;
+
+ case KC_MICMUTE:
+ if (record->event.pressed) {
+ tap_code16(LCTL(LSFT(KC_M))); // MS Teams mic mute shortcut
+ toggle_mic_mute_effect(); // Toggle the red-pulse effect
+ }
+ return false;
+
+ // Work timer keycodes
+ case KC_WRKTMR:
+ if (record->event.pressed) {
+ toggle_work_timer(); // Toggle timer on/off
+ }
+ return false;
+
+ case KC_30MIN:
+ if (record->event.pressed) {
+ start_timer(TIMER_TYPE_30MIN);
+ }
+ return false;
+
+ case KC_1HR:
+ if (record->event.pressed) {
+ start_timer(TIMER_TYPE_1HR);
+ }
+ return false;
+
+ case KC_4HR:
+ if (record->event.pressed) {
+ start_timer(TIMER_TYPE_4HR);
+ }
+ return false;
+
+ case KC_8HR:
+ if (record->event.pressed) {
+ start_timer(TIMER_TYPE_8HR);
+ }
+ return false;
+
+ case KC_10HR:
+ if (record->event.pressed) {
+ start_timer(TIMER_TYPE_10HR);
+ }
+ return false;
+
+ case KC_WRKPAU:
+ if (record->event.pressed) {
+ toggle_pause_work_timer(); // Pause/resume the work timer
+ }
+ return false;
+
+ case KC_CPSWRD:
+ if (record->event.pressed) {
+ caps_word_toggle(); // Toggle caps word mode
+ }
+ return false;
+
+ default:
+ return true; // Process all other keycodes normally
+ }
+ }
+
+ /**
+ * Caps Word Callback
+ * Determine which keys should maintain caps word mode
+ */
+ bool caps_word_press_user(uint16_t keycode) {
+ switch (keycode) {
+ // These keys don't disable Caps Word
+ case KC_A ... KC_Z:
+ case KC_MINS:
+ return true;
+ }
+ return false;
+ }
+
+ // Keymap definitions
+ const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+ /* Base Layer */
+ [BASE] = LAYOUT(
+ KC_ESC, KC_DEL, KC_NUM_LOCK, KC_MICMUTE, KC_NO, KC_NO, KC_NO, KC_MUTE, KC_F1, KC_F2, KC_F3, KC_F4, KC_F5, KC_F6, KC_F7, KC_F8, KC_F9, KC_F10, KC_F11, KC_F12, LGUI(KC_L),
+ KC_BSPC, KC_PSLS, KC_PAST, KC_PMNS, DM_REC1, DM_REC2, KC_GRV, KC_1, KC_2, KC_3, KC_4, KC_5, KC_6, KC_7, KC_8, KC_9, KC_0, KC_MINS, KC_EQL, KC_NO, KC_BSPC,
+ KC_P7, KC_P8, KC_P9, KC_NO, KC_CALC, KC_PSCR, KC_NO, KC_TAB, KC_Q, KC_W, KC_E, KC_R, KC_T, KC_Y, KC_U, KC_I, KC_O, KC_P, KC_LBRC, KC_RBRC, KC_BSLS,
+ KC_P4, KC_P5, KC_P6, KC_PPLS, DM_PLY1, DM_PLY2, KC_NO, KC_CAPS, KC_A, KC_S, KC_D, KC_F, KC_G, KC_H, KC_J, KC_K, KC_L, KC_SCLN, KC_QUOT, KC_NO, KC_ENT,
+ KC_P1, KC_P2, KC_P3, KC_NO, KC_NO, KC_UP, KC_NO, KC_LSFT, KC_NO, KC_Z, KC_X, KC_C, KC_V, KC_B, KC_N, KC_M, KC_COMM, KC_DOT, KC_SLSH, KC_RSFT, KC_NO,
+ KC_NO, KC_P0, KC_PDOT, KC_PENT, KC_LEFT, KC_DOWN, KC_RIGHT, KC_NO, KC_LCTL, KC_LALT, KC_NO, KC_NO, KC_NO, KC_SPC, KC_NO, KC_NO, KC_RALT, KC_NO, KC_RGUI, MO(FN), KC_RCTL
+ ),
+
+ /* Function Layer */
+ [FN] = LAYOUT(
+ _______, _______, _______, _______, KC_NO , KC_NO , KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
+ _______, _______, _______, RGB_VAD, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______,
+ _______, KC_8HR , KC_10HR, KC_NO , _______, _______, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______,
+ KC_4HR , _______, _______, RGB_VAI, _______, _______, KC_NO , KC_CPSWRD, _______, KC_WRKTMR, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO , _______,
+ KC_1HR , _______, KC_30MIN, KC_NO , KC_NO , RGB_HUI, KC_NO , _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, _______, KC_NO ,
+ KC_NO , _______, _______, _______, RGB_RMOD, RGB_HUD, RGB_MOD, KC_NO , _______, _______, KC_NO , KC_NO , KC_NO , KC_WRKPAU, KC_NO , KC_NO , _______, KC_NO , _______, _______, _______
+ ),
+ };
diff --git a/keyboards/tssouthpaw/keymaps/default/readme.md b/keyboards/tssouthpaw/keymaps/default/readme.md
new file mode 100644
index 00000000000..360dc6c2ef9
--- /dev/null
+++ b/keyboards/tssouthpaw/keymaps/default/readme.md
@@ -0,0 +1,35 @@
+/**
+ * This is a readme.md file that should be placed in the keymaps/default/ directory
+ * to provide information about the keymap.
+ */
+
+ ## keymaps/default/readme.md
+
+ ```markdown
+ # Default Keymap for TS Southpaw
+
+ This is the default keymap for the TS Southpaw keyboard. It includes a base layer with a standard QWERTY layout plus southpaw numpad, and a function layer with work timer controls and RGB lighting adjustments.
+
+ ## Features
+
+ - Southpaw numpad layout (numpad on the left side)
+ - Work timer system with multiple duration options
+ - Volume control via rotary encoder
+ - RGB lighting effects and controls
+ - Arrow keys highlighted in blue
+ - ESC key ripple effect
+ - Caps Lock and Num Lock indicators
+ - Microphone mute function with indicator
+
+ ## Layers
+
+ ### Base Layer
+ - Standard QWERTY layout
+ - Left-side numpad
+ - Media controls
+ - Function key row (F1-F12)
+
+ ### Function Layer (Accessed via key to left of right Control)
+ - RGB lighting controls
+ - Work timer controls (30MIN, 1HR, 4HR, 8HR, 10HR)
+ - Caps Word toggle
\ No newline at end of file
diff --git a/keyboards/tssouthpaw/rgb_effects/rgb_effects.c b/keyboards/tssouthpaw/rgb_effects/rgb_effects.c
new file mode 100644
index 00000000000..c79f4c12842
--- /dev/null
+++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.c
@@ -0,0 +1,267 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+/**
+ * TS Southpaw RGB Effects System
+ */
+
+ #include "rgb_effects.h"
+ #include "quantum.h"
+ #include "rgb_matrix.h"
+ #include "work_timer.h"
+
+ /**
+ * Color definitions for RGB effects
+ */
+ static const rgb_color_t CAPS_LOCK_COLOR = {255, 60, 0}; // Orange for Caps Lock
+ static const rgb_color_t NUM_LOCK_COLOR = {255, 60, 0}; // Orange for Num Lock
+ static const rgb_color_t ESC_RIPPLE_COLOR = {255, 0, 0}; // Red for ESC ripple effect
+ static const rgb_color_t MIC_MUTE_COLOR = {255, 0, 0}; // Red for Mic Mute
+ static const rgb_color_t ARROW_KEYS_COLOR = {0, 0, 255}; // Blue for arrow keys
+
+ /**
+ * State tracking structure for LED effects
+ */
+ typedef struct {
+ bool active;
+ uint8_t prev_rgb_mode;
+ } led_state_t;
+
+ /**
+ * State variables for LED indicators
+ */
+ static led_state_t caps_lock_state = {false, 0};
+ static led_state_t num_lock_state = {false, 0};
+ static led_state_t mic_mute_state = {false, 0};
+
+ /**
+ * ESC ripple effect state variables
+ */
+ static struct {
+ bool active;
+ uint8_t step;
+ uint16_t timer;
+ bool expanding;
+ } ripple_state = {false, 0, 0, true};
+
+ /**
+ * ESC ripple effect LED configuration
+ */
+ #define MAX_RIPPLE_LAYERS 6
+ #define MAX_LEDS_PER_LAYER 10
+
+ // LED indices for each ripple layer
+ // 255 is used as a placeholder for unused indices
+ static const uint8_t esc_splash_ripple[MAX_RIPPLE_LAYERS][MAX_LEDS_PER_LAYER] = {
+ {0, 255, 255, 255, 255, 255, 255, 255, 255, 255}, // Center LED only
+ {1, 19, 255, 255, 255, 255, 255, 255, 255, 255}, // First ring
+ {38, 39, 6, 7, 15, 255, 255, 255, 255, 255}, // Second ring
+ {42, 43, 25, 48, 49, 255, 255, 255, 255, 255}, // Third ring
+ {80, 81, 67, 68, 75, 76, 255, 255, 255, 255}, // Fourth ring
+ {102, 103, 71, 36, 18, 93, 100, 255, 255, 255} // Fifth ring
+ };
+
+ // Number of actual LEDs in each ripple layer for more efficient processing
+ static const uint8_t leds_per_layer[MAX_RIPPLE_LAYERS] = {1, 2, 5, 5, 6, 7};
+
+ /**
+ * Arrow key LED configuration
+ */
+ static const uint8_t arrow_key_leds[] = {78, 94, 95, 96};
+ #define ARROW_KEY_COUNT (sizeof(arrow_key_leds) / sizeof(arrow_key_leds[0]))
+
+ /**
+ * Apply a pulsing effect to a specific LED
+ *
+ * @param led_index The index of the LED to apply the effect to
+ * @param color The base RGB color to use
+ */
+ static void apply_pulse_effect(uint8_t led_index, rgb_color_t color) {
+ // Create a pulsing effect by varying brightness based on timer
+ uint8_t brightness = abs((timer_read() / 2) % 510 - 255);
+ rgb_matrix_set_color(led_index,
+ (color.r * brightness) / 255,
+ (color.g * brightness) / 255,
+ (color.b * brightness) / 255);
+ }
+
+ /**
+ * Handle the state change for an indicator LED
+ *
+ * @param active Whether the indicator should be active
+ * @param state Pointer to the LED state structure to update
+ */
+ static void update_indicator_state(bool active, led_state_t *state) {
+ if (active) {
+ if (!state->active) {
+ state->prev_rgb_mode = rgb_matrix_get_mode();
+ state->active = true;
+ }
+ } else if (state->active) {
+ state->active = false;
+ rgb_matrix_mode_noeeprom(state->prev_rgb_mode);
+ }
+ }
+
+ /**
+ * Handle Caps Lock indicator lighting
+ */
+ void handle_caps_lock_rgb(void) {
+ bool caps_active = host_keyboard_led_state().caps_lock;
+ update_indicator_state(caps_active, &caps_lock_state);
+
+ if (caps_lock_state.active) {
+ apply_pulse_effect(CAPS_LOCK_LED, CAPS_LOCK_COLOR);
+ }
+ }
+
+ /**
+ * Handle Num Lock indicator lighting
+ * Note: Active when Num Lock is OFF (inverse logic)
+ */
+ void handle_num_lock_rgb(void) {
+ bool num_lock_inactive = !host_keyboard_led_state().num_lock;
+ update_indicator_state(num_lock_inactive, &num_lock_state);
+
+ if (num_lock_state.active) {
+ apply_pulse_effect(NUM_LOCK_LED, NUM_LOCK_COLOR);
+ }
+ }
+
+ /**
+ * Toggle the Mic Mute effect state
+ */
+ void toggle_mic_mute_effect(void) {
+ if (!mic_mute_state.active) {
+ mic_mute_state.prev_rgb_mode = rgb_matrix_get_mode();
+ mic_mute_state.active = true;
+ } else {
+ mic_mute_state.active = false;
+ rgb_matrix_mode_noeeprom(mic_mute_state.prev_rgb_mode);
+ }
+ }
+
+ /**
+ * Handle Mic Mute indicator lighting
+ */
+ void handle_mic_mute_rgb(void) {
+ if (mic_mute_state.active) {
+ apply_pulse_effect(MIC_MUTE_LED, MIC_MUTE_COLOR);
+ }
+ }
+
+ /**
+ * Start the ESC ripple effect
+ */
+ void start_esc_ripple_effect(void) {
+ ripple_state.active = true;
+ ripple_state.step = 0;
+ ripple_state.timer = timer_read();
+ ripple_state.expanding = true;
+ }
+
+ /**
+ * Stop the ESC ripple effect
+ */
+ void stop_esc_ripple_effect(void) {
+ ripple_state.active = false;
+ }
+
+ /**
+ * Handle the ESC ripple effect animation
+ */
+ void handle_esc_ripple_effect(void) {
+ if (!ripple_state.active) return;
+
+ // Update ripple step based on timer (every 15ms)
+ if (timer_elapsed(ripple_state.timer) > 15) {
+ ripple_state.timer = timer_read();
+
+ // Update step based on direction
+ if (ripple_state.expanding) {
+ ripple_state.step++;
+ if (ripple_state.step >= MAX_RIPPLE_LAYERS - 1) {
+ ripple_state.step = MAX_RIPPLE_LAYERS - 1;
+ ripple_state.expanding = false;
+ }
+ } else {
+ if (ripple_state.step > 0) {
+ ripple_state.step--;
+ } else {
+ ripple_state.expanding = true;
+ }
+ }
+ }
+
+ // Turn off all ripple LEDs first
+ for (uint8_t layer = 0; layer < MAX_RIPPLE_LAYERS; layer++) {
+ for (uint8_t i = 0; i < leds_per_layer[layer]; i++) {
+ uint8_t led_index = esc_splash_ripple[layer][i];
+ if (led_index != 255) { // Skip placeholder values
+ rgb_matrix_set_color(led_index, 0, 0, 0);
+ }
+ }
+ }
+
+ // Set active layer LEDs
+ uint8_t current_layer = ripple_state.step;
+ for (uint8_t i = 0; i < leds_per_layer[current_layer]; i++) {
+ uint8_t led_index = esc_splash_ripple[current_layer][i];
+ if (led_index != 255) { // Skip placeholder values
+ rgb_matrix_set_color(led_index,
+ ESC_RIPPLE_COLOR.r,
+ ESC_RIPPLE_COLOR.g,
+ ESC_RIPPLE_COLOR.b);
+ }
+ }
+ }
+
+ /**
+ * Handle arrow key highlighting
+ */
+ static void handle_arrow_keys(void) {
+ // Only override arrows in SOLID_COLOR mode
+ if (rgb_matrix_get_mode() == RGB_MATRIX_SOLID_COLOR) {
+ uint8_t val = rgb_matrix_get_val(); // current brightness (0–255)
+
+ for (uint8_t i = 0; i < ARROW_KEY_COUNT; i++) {
+ rgb_matrix_set_color(arrow_key_leds[i],
+ ARROW_KEYS_COLOR.r * val / 255,
+ ARROW_KEYS_COLOR.g * val / 255,
+ ARROW_KEYS_COLOR.b * val / 255);
+ }
+ }
+ }
+
+ /**
+ * Combined overlay for arrow-keys + reactive handlers
+ */
+ bool rgb_matrix_indicators_user(void) {
+ // Apply arrow key highlighting
+ handle_arrow_keys();
+
+ // Run all reactive handlers
+ handle_caps_lock_rgb();
+ handle_num_lock_rgb();
+ handle_mic_mute_rgb();
+ handle_esc_ripple_effect();
+
+ // Handle work timer through extern call (implemented in work_timer.c)
+ update_work_timer();
+ handle_work_timer();
+
+ return true; // continue with the normal effect pipeline
+ }
\ No newline at end of file
diff --git a/keyboards/tssouthpaw/rgb_effects/rgb_effects.h b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h
new file mode 100644
index 00000000000..2333833ea68
--- /dev/null
+++ b/keyboards/tssouthpaw/rgb_effects/rgb_effects.h
@@ -0,0 +1,85 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ #pragma once
+
+ #include "quantum.h"
+
+ // LED indices for indicator LEDs
+ #define CAPS_LOCK_LED 62 // LED index for Caps Lock indicator
+ #define NUM_LOCK_LED 2 // LED index for Num Lock indicator
+ #define MIC_MUTE_LED 3 // LED index for KC_MICMUTE
+
+/**
+ * RGB Effect types enumeration
+ * For potential future extension of effect types
+ */
+ typedef enum {
+ RGB_EFFECT_NONE,
+ RGB_EFFECT_BREATHING,
+ RGB_EFFECT_RAINBOW,
+ RGB_EFFECT_CUSTOM
+ } rgb_effect_type_t;
+
+ /**
+ * RGB Color structure for cleaner color definitions
+ */
+ typedef struct {
+ uint8_t r;
+ uint8_t g;
+ uint8_t b;
+ } rgb_color_t;
+
+ /**
+ * Function declarations for handling RGB effects
+ */
+
+ // Handle LED indicators for keyboard state
+ void handle_caps_lock_rgb(void);
+ void handle_num_lock_rgb(void);
+ void handle_mic_mute_rgb(void);
+
+ // Ripple effect functions
+ void handle_esc_ripple_effect(void);
+ void start_esc_ripple_effect(void);
+ void stop_esc_ripple_effect(void);
+
+ // Microphone mute effect toggle
+ void toggle_mic_mute_effect(void);
+
+ /**
+ * Utility functions for RGB effects
+ */
+
+ // Gets the keyboard's current RGB mode
+ static inline uint8_t get_current_rgb_mode(void) {
+ return rgb_matrix_get_mode();
+ }
+
+ // Sets an LED to a specific RGB color
+ static inline void set_led_color(uint8_t led_index, uint8_t r, uint8_t g, uint8_t b) {
+ rgb_matrix_set_color(led_index, r, g, b);
+ }
+
+ // Calculate color gradient between two colors based on progress (0.0 - 1.0)
+ static inline rgb_color_t calculate_gradient_color(rgb_color_t start, rgb_color_t end, float progress) {
+ rgb_color_t result;
+ result.r = start.r + (int)((float)(end.r - start.r) * progress);
+ result.g = start.g + (int)((float)(end.g - start.g) * progress);
+ result.b = start.b + (int)((float)(end.b - start.b) * progress);
+ return result;
+ }
+
\ No newline at end of file
diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.c b/keyboards/tssouthpaw/rgb_effects/work_timer.c
new file mode 100644
index 00000000000..7b76452dce5
--- /dev/null
+++ b/keyboards/tssouthpaw/rgb_effects/work_timer.c
@@ -0,0 +1,393 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#include "work_timer.h"
+#include "quantum.h"
+#include "rgb_effects/rgb_effects.h"
+#include "timer.h"
+
+// Simple work timer structure
+typedef struct {
+ // Basic state
+ bool active; // Is the timer running?
+ bool paused; // Is the timer paused?
+ work_timer_type_t type; // Timer type (30min, 1hr, etc.)
+
+ // Time tracking
+ uint32_t start_time; // When this timer started
+ uint32_t end_time; // When this timer should end
+ uint32_t pause_time; // When timer was paused (if paused)
+ uint32_t break_time; // Break duration for this timer
+
+ // Midpoint break tracking
+ uint32_t mid_point; // Time value at midpoint (when break starts)
+ uint32_t mid_point_end; // Time value when break ends
+} work_timer_t;
+
+// Global timer state
+static work_timer_t timer = {0};
+
+// Timer colors
+static const rgb_color_t TIMER_START_COLOR = {WORK_TIMER_START_R, WORK_TIMER_START_G, WORK_TIMER_START_B};
+static const rgb_color_t TIMER_MID_COLOR = {WORK_TIMER_MID_R, WORK_TIMER_MID_G, WORK_TIMER_MID_B};
+static const rgb_color_t TIMER_END_COLOR = {WORK_TIMER_END_R, WORK_TIMER_END_G, WORK_TIMER_END_B};
+static const rgb_color_t TIMER_BREAK_COLOR = {WORK_TIMER_LUNCH_R, WORK_TIMER_LUNCH_G, WORK_TIMER_LUNCH_B};
+
+// Function declarations
+static void save_timer_state(void);
+static void load_timer_state(void);
+static void display_progress_bar(uint8_t num_leds, float progress, float brightness);
+
+/**
+ * Configure a timer with the specified type
+ */
+static void configure_timer(work_timer_type_t type) {
+ timer.type = type;
+
+ // Set break duration based on timer type
+ switch (type) {
+ case TIMER_TYPE_30MIN:
+ timer.break_time = MID_BREAK_30MIN_DURATION;
+ break;
+
+ case TIMER_TYPE_1HR:
+ timer.break_time = MID_BREAK_1HR_DURATION;
+ break;
+
+ case TIMER_TYPE_4HR:
+ timer.break_time = MID_BREAK_4HR_DURATION;
+ break;
+
+ case TIMER_TYPE_8HR:
+ timer.break_time = LUNCH_BREAK_DURATION;
+ break;
+
+ case TIMER_TYPE_10HR:
+ timer.break_time = LUNCH_BREAK_DURATION;
+ break;
+
+ default:
+ timer.break_time = LUNCH_BREAK_DURATION;
+ break;
+ }
+
+ // Calculate timer duration (excluding break)
+ uint32_t work_duration = 0;
+ switch (type) {
+ case TIMER_TYPE_30MIN: work_duration = TIMER_30MIN_DURATION; break;
+ case TIMER_TYPE_1HR: work_duration = TIMER_1HR_DURATION; break;
+ case TIMER_TYPE_4HR: work_duration = TIMER_4HR_DURATION; break;
+ case TIMER_TYPE_8HR: work_duration = TIMER_8HR_DURATION; break;
+ case TIMER_TYPE_10HR: work_duration = TIMER_10HR_DURATION; break;
+ default: work_duration = TIMER_8HR_DURATION; break;
+ }
+
+ // Calculate end time and midpoint
+ timer.end_time = timer.start_time + work_duration;
+ timer.mid_point = timer.start_time + (work_duration / 2);
+ timer.mid_point_end = timer.mid_point + timer.break_time;
+}
+
+/**
+ * Save timer state to EEPROM
+ */
+static void save_timer_state(void) {
+ uint8_t buffer[24] = {0};
+
+ // Basic state
+ buffer[0] = timer.active ? 1 : 0;
+ buffer[1] = timer.paused ? 1 : 0;
+ buffer[2] = (uint8_t)timer.type;
+
+ // Time values
+ memcpy(&buffer[3], &timer.start_time, sizeof(uint32_t));
+ memcpy(&buffer[7], &timer.end_time, sizeof(uint32_t));
+ memcpy(&buffer[11], &timer.pause_time, sizeof(uint32_t));
+ memcpy(&buffer[15], &timer.break_time, sizeof(uint32_t));
+ memcpy(&buffer[19], &timer.mid_point, sizeof(uint32_t));
+
+ // Write to EEPROM
+ eeprom_update_block(buffer, (void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer));
+}
+
+/**
+ * Load timer state from EEPROM
+ */
+static void load_timer_state(void) {
+ uint8_t buffer[24] = {0};
+
+ // Read from EEPROM
+ eeprom_read_block(buffer, (const void *)EEPROM_WORK_TIMER_ACTIVE, sizeof(buffer));
+
+ // Basic state
+ timer.active = buffer[0] ? true : false;
+ timer.paused = buffer[1] ? true : false;
+ timer.type = (work_timer_type_t)buffer[2];
+
+ // Time values
+ memcpy(&timer.start_time, &buffer[3], sizeof(uint32_t));
+ memcpy(&timer.end_time, &buffer[7], sizeof(uint32_t));
+ memcpy(&timer.pause_time, &buffer[11], sizeof(uint32_t));
+ memcpy(&timer.break_time, &buffer[15], sizeof(uint32_t));
+ memcpy(&timer.mid_point, &buffer[19], sizeof(uint32_t));
+
+ // Calculate mid_point_end from loaded values
+ if (timer.active) {
+ timer.mid_point_end = timer.mid_point + timer.break_time;
+
+ // Verify timer is still valid
+ if (!timer.paused) {
+ uint32_t current_time = timer_read32();
+ if (current_time > timer.end_time) {
+ timer.active = false;
+ save_timer_state();
+ }
+ }
+ }
+}
+
+/**
+ * Display progress bar with gradient colors
+ */
+static void display_progress_bar(uint8_t num_leds, float progress, float brightness) {
+ // Progress is a value from 0.0 to 1.0
+ float hours_per_led = 1.0f / (float)num_leds;
+
+ // Determine how many LEDs should be lit
+ uint8_t leds_lit = (uint8_t)(progress / hours_per_led);
+ if (leds_lit > num_leds) leds_lit = num_leds;
+
+ // Calculate progress for the partially-lit LED
+ float current_led_progress = (progress - (leds_lit * hours_per_led)) / hours_per_led;
+
+ // Set colors for each LED in the progress bar
+ for (uint8_t i = 0; i < num_leds; i++) {
+ rgb_color_t color = {0, 0, 0}; // Default to off
+
+ if (i < leds_lit) {
+ // Fully lit LED - calculate gradient color based on position
+ float position = (float)i / (float)(num_leds - 1);
+
+ // Use green to orange gradient for most of bar, orange to red at end
+ if (position < 0.7f) {
+ // First 70% is green to orange
+ float adj_progress = position / 0.7f;
+ color = calculate_gradient_color(TIMER_START_COLOR, TIMER_MID_COLOR, adj_progress);
+ } else {
+ // Last 30% is orange to red
+ float adj_progress = (position - 0.7f) / 0.3f;
+ color = calculate_gradient_color(TIMER_MID_COLOR, TIMER_END_COLOR, adj_progress);
+ }
+
+ // Apply brightness
+ color.r = (uint8_t)((float)color.r * brightness);
+ color.g = (uint8_t)((float)color.g * brightness);
+ color.b = (uint8_t)((float)color.b * brightness);
+ }
+ else if (i == leds_lit && current_led_progress > 0.0f) {
+ // Partially lit LED
+ float position = (float)i / (float)(num_leds - 1);
+ rgb_color_t full_color;
+
+ if (position < 0.7f) {
+ float adj_progress = position / 0.7f;
+ full_color = calculate_gradient_color(TIMER_START_COLOR, TIMER_MID_COLOR, adj_progress);
+ } else {
+ float adj_progress = (position - 0.7f) / 0.3f;
+ full_color = calculate_gradient_color(TIMER_MID_COLOR, TIMER_END_COLOR, adj_progress);
+ }
+
+ // Dim by progress and brightness
+ color.r = (uint8_t)((float)full_color.r * current_led_progress * brightness);
+ color.g = (uint8_t)((float)full_color.g * current_led_progress * brightness);
+ color.b = (uint8_t)((float)full_color.b * current_led_progress * brightness);
+ }
+
+ // Set the LED color
+ rgb_matrix_set_color(WORK_TIMER_LED_START + i, color.r, color.g, color.b);
+ }
+}
+
+/**
+ * Initialize the work timer
+ */
+void work_timer_init(void) {
+ load_timer_state();
+}
+
+/**
+ * Toggle the work timer on/off
+ */
+void toggle_work_timer(void) {
+ if (timer.active) {
+ // Turn off timer
+ timer.active = false;
+ timer.paused = false;
+
+ save_timer_state();
+
+ // Force RGB refresh
+ rgb_matrix_mode_noeeprom(rgb_matrix_get_mode());
+ } else {
+ // Start a new timer
+ timer.active = true;
+ timer.paused = false;
+ timer.start_time = timer_read32();
+ timer.pause_time = 0;
+
+ // Configure for current timer type
+ configure_timer(timer.type);
+ save_timer_state();
+ }
+}
+
+/**
+ * Start a specific timer type
+ */
+void start_timer(work_timer_type_t type) {
+ // Start fresh
+ timer.active = true;
+ timer.paused = false;
+ timer.start_time = timer_read32();
+ timer.pause_time = 0;
+
+ // Configure based on type
+ configure_timer(type);
+ save_timer_state();
+}
+
+/**
+ * Pause or resume the timer
+ */
+void toggle_pause_work_timer(void) {
+ if (!timer.active) return;
+
+ if (timer.paused) {
+ // Resume - calculate time paused and adjust timers
+ uint32_t current_time = timer_read32();
+ uint32_t pause_duration = current_time - timer.pause_time;
+
+ // Extend all time points by pause duration
+ timer.end_time += pause_duration;
+ timer.mid_point += pause_duration;
+ timer.mid_point_end += pause_duration;
+
+ timer.paused = false;
+ } else {
+ // Pause
+ timer.pause_time = timer_read32();
+ timer.paused = true;
+ }
+
+ save_timer_state();
+}
+
+/**
+ * Check if the blue pulse effect should be active
+ */
+bool is_timer_pulse_active(void) {
+ if (!timer.active || timer.paused) return false;
+
+ uint32_t current_time = timer_read32();
+
+ // Only return true when we're in the mid-point break window
+ return (current_time >= timer.mid_point && current_time < timer.mid_point_end);
+}
+
+/**
+ * Update timer state
+ */
+void update_work_timer(void) {
+ if (!timer.active || timer.paused) return;
+
+ uint32_t current_time = timer_read32();
+
+ // Check if timer has ended
+ if (current_time >= timer.end_time) {
+ timer.active = false;
+ save_timer_state();
+ rgb_matrix_mode_noeeprom(rgb_matrix_get_mode());
+ }
+}
+
+/**
+ * Handle work timer visualization
+ */
+void handle_work_timer(void) {
+ static bool was_active = false;
+
+ if (!timer.active) {
+ if (was_active) {
+ rgb_matrix_mode_noeeprom(rgb_matrix_get_mode());
+ was_active = false;
+ }
+ return;
+ }
+
+ was_active = true;
+
+ // Get current time and brightness
+ uint32_t current_time = timer_read32();
+ uint8_t rgb_brightness = rgb_matrix_get_val();
+ float brightness_factor = (float)rgb_brightness / 255.0f;
+
+ // Number of LEDs in the progress bar
+ const uint8_t num_leds = WORK_TIMER_LED_END - WORK_TIMER_LED_START + 1;
+
+ // Calculate progress based on active timer duration
+ float progress;
+ if (timer.paused) {
+ // When paused, use pause time for progress calculation
+ progress = (float)(timer.pause_time - timer.start_time) /
+ (float)(timer.end_time - timer.start_time);
+ } else {
+ // When active, use current time for progress calculation
+ progress = (float)(current_time - timer.start_time) /
+ (float)(timer.end_time - timer.start_time);
+ }
+
+ // Clamp progress to valid range
+ if (progress > 1.0f) progress = 1.0f;
+ if (progress < 0.0f) progress = 0.0f;
+
+ // Check if we're in the break window (midpoint of the timer)
+ if (!timer.paused && current_time >= timer.mid_point && current_time < timer.mid_point_end) {
+ // In break time - show blue pulse
+ rgb_color_t color = TIMER_BREAK_COLOR;
+
+ // Slow pulse for break time
+ uint8_t break_pulse = timer_read() / 10;
+ float break_intensity = (float)(abs(break_pulse % 510 - 255)) / 255.0f;
+
+ // Apply blue pulse to all LEDs in progress bar
+ for (uint8_t i = 0; i < num_leds; i++) {
+ rgb_matrix_set_color(WORK_TIMER_LED_START + i,
+ (uint8_t)((float)color.r * break_intensity * brightness_factor),
+ (uint8_t)((float)color.g * break_intensity * brightness_factor),
+ (uint8_t)((float)color.b * break_intensity * brightness_factor));
+ }
+ } else {
+ // Normal progress display
+ display_progress_bar(num_leds, progress, brightness_factor);
+ }
+}
+
+/**
+ * Periodic timer task
+ */
+void work_timer_task(void) {
+ update_work_timer();
+}
diff --git a/keyboards/tssouthpaw/rgb_effects/work_timer.h b/keyboards/tssouthpaw/rgb_effects/work_timer.h
new file mode 100644
index 00000000000..ea35f40a8dd
--- /dev/null
+++ b/keyboards/tssouthpaw/rgb_effects/work_timer.h
@@ -0,0 +1,89 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#pragma once
+
+#include "quantum.h"
+
+// Work Timer type enum
+typedef enum {
+ TIMER_TYPE_30MIN,
+ TIMER_TYPE_1HR,
+ TIMER_TYPE_4HR,
+ TIMER_TYPE_8HR,
+ TIMER_TYPE_10HR
+} work_timer_type_t;
+
+// Work Timer state enum
+typedef enum {
+ TIMER_STATE_INACTIVE,
+ TIMER_STATE_ACTIVE,
+ TIMER_STATE_PAUSED,
+ TIMER_STATE_LUNCH,
+ TIMER_STATE_MID_BREAK,
+ TIMER_STATE_COMPLETED
+} work_timer_state_t;
+
+// Work Timer Color definitions - modified for more yellow/orange focus
+// Start point color (green)
+#define WORK_TIMER_START_R 0
+#define WORK_TIMER_START_G 255
+#define WORK_TIMER_START_B 0
+
+// Mid point color (orange)
+#define WORK_TIMER_MID_R 255
+#define WORK_TIMER_MID_G 112
+#define WORK_TIMER_MID_B 0
+
+// End point color (red)
+#define WORK_TIMER_END_R 255
+#define WORK_TIMER_END_G 0
+#define WORK_TIMER_END_B 0
+
+// Lunch break color (blue)
+#define WORK_TIMER_LUNCH_R 0
+#define WORK_TIMER_LUNCH_G 0
+#define WORK_TIMER_LUNCH_B 255
+
+// Standard timer durations (in milliseconds)
+// IMPORTANT: These represent actual working time (excluding breaks)
+#define TIMER_30MIN_DURATION 1800000 // 30 minutes
+#define TIMER_1HR_DURATION 3600000 // 1 hour
+#define TIMER_4HR_DURATION 14400000 // 4 hours
+#define TIMER_8HR_DURATION 28800000 // 8 hours (real world 9 hours with lunch)
+#define TIMER_10HR_DURATION 36000000 // 10 hours (real world 11 hours with lunch)
+
+// Break durations
+#define BREAK_WARNING_TIME 60000 // 60 seconds (kept for calculations but not used for warnings)
+#define MID_BREAK_30MIN_DURATION 30000 // 30 seconds
+#define MID_BREAK_1HR_DURATION 45000 // 45 seconds
+#define MID_BREAK_4HR_DURATION 60000 // 60 seconds
+#define LUNCH_BREAK_DURATION 3600000 // 1 hour default
+
+// Define maximum valid duration for sanity checking
+#define TIMER_MAX_DURATION 50400000 // 14 hours - sanity check
+
+// Function declarations for work timer
+void work_timer_init(void);
+void start_timer(work_timer_type_t timer_type);
+void toggle_pause_work_timer(void);
+void update_work_timer(void);
+void handle_work_timer(void);
+void toggle_work_timer(void);
+void work_timer_task(void);
+
+// Functions for checking timer states
+bool is_timer_pulse_active(void);
diff --git a/keyboards/tssouthpaw/rules.mk b/keyboards/tssouthpaw/rules.mk
new file mode 100644
index 00000000000..716da52f309
--- /dev/null
+++ b/keyboards/tssouthpaw/rules.mk
@@ -0,0 +1,5 @@
+# This is the rules.mk file for the RGB Effects firmware.
+SRC += rgb_effects/rgb_effects.c rgb_effects/work_timer.c
+
+# Enable encoder functionality
+ENCODER_ENABLE = yes
diff --git a/keyboards/tssouthpaw/tssouthpaw.c b/keyboards/tssouthpaw/tssouthpaw.c
new file mode 100644
index 00000000000..54cab161306
--- /dev/null
+++ b/keyboards/tssouthpaw/tssouthpaw.c
@@ -0,0 +1,88 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ #include "tssouthpaw.h"
+ #include "rgb_effects/rgb_effects.h"
+ #include "rgb_effects/work_timer.h"
+
+ /**
+ * Keyboard initialization
+ * Called once at startup
+ */
+ void keyboard_post_init_kb(void) {
+ // Initialize RGB lighting effects
+ rgb_matrix_mode_noeeprom(RGB_MATRIX_SOLID_COLOR);
+ rgb_matrix_sethsv_noeeprom(HSV_GREEN); // Set default color to green
+
+ // Enable N-Key Rollover
+ keymap_config.nkro = true;
+
+ // Load saved work timer state from EEPROM if applicable
+ work_timer_init();
+
+ // Continue with any user-level initialization
+ keyboard_post_init_user();
+ }
+
+ /**
+ * Power management function - Called when system is going to sleep
+ */
+ void suspend_power_down_kb(void) {
+ // If timer pulse is active, ensure RGB matrix remains active
+ if (is_timer_pulse_active()) {
+ // Ensure RGB matrix has reasonable brightness for notifications
+ uint8_t val = rgb_matrix_get_val();
+ if (val < RGB_MATRIX_MINIMUM_BRIGHTNESS) {
+ rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), RGB_MATRIX_MINIMUM_BRIGHTNESS);
+ }
+
+ // Return early to skip deep sleep routines
+ return;
+ }
+
+ // Only disable RGB effects when no timer pulse is active
+ rgb_matrix_set_suspend_state(true);
+ suspend_power_down_user();
+ }
+
+ /**
+ * Power management function - Called when system is waking from sleep
+ */
+ void suspend_wakeup_init_kb(void) {
+ // Make sure the timer pulse state is properly reflected
+ // This ensures the timer visuals are immediately visible
+ if (is_timer_pulse_active()) {
+ // Ensure RGB matrix effect is enabled with proper brightness
+ uint8_t val = rgb_matrix_get_val();
+ if (val < RGB_MATRIX_MINIMUM_BRIGHTNESS) {
+ rgb_matrix_sethsv_noeeprom(rgb_matrix_get_hue(), rgb_matrix_get_sat(), RGB_MATRIX_MINIMUM_BRIGHTNESS);
+ }
+ }
+
+ suspend_wakeup_init_user();
+ }
+
+ /**
+ * Regular task hook to ensure timer updates even during suspend
+ */
+ void housekeeping_task_kb(void) {
+ // Update work timer even during suspend
+ if (is_timer_pulse_active()) {
+ update_work_timer();
+ handle_work_timer();
+ }
+ }
+
\ No newline at end of file
diff --git a/keyboards/tssouthpaw/tssouthpaw.h b/keyboards/tssouthpaw/tssouthpaw.h
new file mode 100644
index 00000000000..42947807a9e
--- /dev/null
+++ b/keyboards/tssouthpaw/tssouthpaw.h
@@ -0,0 +1,21 @@
+/* Copyright 2025 TS Design Works LLC
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+ #pragma once
+
+ #include "quantum.h"
+
+ // Include to ensure backward compatibility with existing user keymaps