Step 1 — HTML (Widget Code)
Paste this entire block into the HTML tab.
<!DOCTYPE html>
<html>
<head>
<style>
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@400;700&display=swap');
:root {
--text-color: {{textColor}};
--font-size: {{fontSize}}px;
--shadow-color: {{shadowColor}};
--shadow-blur: {{shadowBlurRadius}}px;
}
html, body {
margin: 0;
padding: 0;
background: transparent;
}
#timer {
font-family: 'Nunito', sans-serif;
font-size: calc(var(--font-size) * 1.15);
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1.05;
user-select: none;
text-align: center;
color: var(--text-color);
text-shadow:
0 2px 0 rgba(0,0,0,0.4),
0 0 var(--shadow-blur) var(--shadow-color);
background-size: cover;
background-position: center;
background-repeat: repeat;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: currentColor;
}
</style>
</head>
<body>
<div id="timer">00:00:00</div>
<script>
const pad2 = n => String(Math.floor(n)).padStart(2, '0');
function parseHHMMSS(s) {
if (!s) return 0;
const p = s.split(':').map(v => parseInt(v, 10) || 0);
if (p.length === 3) return p[0]*3600 + p[1]*60 + p[2];
if (p.length === 2) return p[0]*60 + p[1];
return p[0];
}
function format(sec) {
sec = Math.max(0, Math.floor(sec));
return `${pad2(sec/3600)}:${pad2((sec%3600)/60)}:${pad2(sec%60)}`;
}
let elTimer;
let defaultStartSeconds = 0;
let endAtMs = 0;
let tick = null;
let paused = true;
let pausedAt = 0;
function render() {
let secs;
if (paused && !pausedAt) {
secs = defaultStartSeconds;
} else {
secs = Math.max(0, Math.round((endAtMs - Date.now()) / 1000));
}
elTimer.textContent = format(secs);
}
function startTick() {
clearInterval(tick);
tick = setInterval(render, 250);
}
function startTimer() {
endAtMs = Date.now() + defaultStartSeconds * 1000;
paused = false;
pausedAt = 0;
startTick();
render();
}
function pauseTimer() {
if (!paused) {
paused = true;
pausedAt = Date.now();
clearInterval(tick);
} else {
paused = false;
endAtMs += Date.now() - pausedAt;
pausedAt = 0;
startTick();
}
render();
}
function resetTimer() {
paused = true;
pausedAt = 0;
clearInterval(tick);
render();
}
function applyGifFill(url) {
if (url && url.trim() !== "") {
elTimer.style.backgroundImage = `url("${url}")`;
elTimer.style.color = "transparent";
elTimer.style.webkitTextFillColor = "transparent";
} else {
elTimer.style.backgroundImage = "none";
elTimer.style.color = getComputedStyle(document.documentElement)
.getPropertyValue('--text-color');
elTimer.style.webkitTextFillColor = "currentColor";
}
}
window.addEventListener('onWidgetLoad', obj => {
const f = obj.detail.fieldData;
elTimer = document.getElementById('timer');
defaultStartSeconds = parseHHMMSS(f.startTime || "01:00:00");
applyGifFill(f.gifFill);
render();
});
window.addEventListener('onWidgetFieldChanged', obj => {
if (obj.detail.field === 'startTime') {
defaultStartSeconds = parseHHMMSS(obj.detail.value);
resetTimer();
}
if (obj.detail.field === 'gifFill') {
applyGifFill(obj.detail.value);
}
});
window.addEventListener('onEventReceived', obj => {
const ev = obj.detail?.event;
if (!ev) return;
if (ev.listener === 'widget-button') {
if (ev.field === 'startBtn') startTimer();
if (ev.field === 'pauseBtn') pauseTimer();
if (ev.field === 'resetBtn') resetTimer();
return;
}
if (ev.listener !== 'message') return;
const msg = ev.data.text.trim();
const tags = ev.data.tags || {};
const isBroadcaster = !!tags.badges?.broadcaster;
const isMod = tags.mod === "1";
if (isBroadcaster) {
if (msg === "!start") startTimer();
if (msg === "!pause") pauseTimer();
if (msg === "!reset") resetTimer();
}
if (isMod && msg.startsWith("!paint")) {
const url = msg.replace("!paint", "").trim();
applyGifFill(url);
}
});
</script>
</body>
</html>
Step 2 — Fields (JSON)
Paste this into the Fields tab.
{
"startTime": {
"type": "text",
"label": "Starting Time (HH:MM:SS)",
"value": "01:00:00"
},
"gifFill": {
"type": "text",
"label": "GIF Text Fill URL (optional)",
"value": "",
"placeholder": "https://example.com/text.gif"
},
"textColor": {
"type": "colorpicker",
"label": "Text Color",
"value": "#FFD85E"
},
"fontSize": {
"type": "number",
"label": "Font Size",
"value": 96,
"min": 16,
"max": 400
},
"shadowColor": {
"type": "colorpicker",
"label": "Shadow Color",
"value": "#000000"
},
"shadowBlurRadius": {
"type": "number",
"label": "Shadow Blur",
"value": 16,
"min": 0,
"max": 50
},
"startBtn": {
"type": "button",
"label": "Start Timer",
"hidden": true
},
"pauseBtn": {
"type": "button",
"label": "Pause / Resume",
"hidden": true
},
"resetBtn": {
"type": "button",
"label": "Reset Timer",
"hidden": true
}
}