Visual usage tracker showing time delta and percentage - see if you're OVER or UNDER budget
// ==UserScript==
// @name Claude Usage Reticle
// @namespace https://github.com/KatsuJinCode
// @version 2.0.1
// @description Visual usage tracker showing time delta and percentage - see if you're OVER or UNDER budget
// @author KatsuJinCode
// @match https://claude.ai/*
// @icon https://claude.ai/favicon.ico
// @grant none
// @license MIT
// @homepageURL https://github.com/KatsuJinCode/claude-usage-reticle
// @supportURL https://github.com/KatsuJinCode/claude-usage-reticle/issues
// @run-at document-idle
// ==/UserScript==
(function() {
'use strict';
var style = document.createElement('style');
style.textContent = '.usage-reticle{position:absolute;width:2px;height:100%;background:#3b82f6;box-shadow:0 0 2px rgba(0,0,0,.5);pointer-events:none;z-index:10;top:0}.usage-reticle::after{content:"";position:absolute;left:-3px;bottom:-5px;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent;border-bottom:5px solid #3b82f6}.usage-reticle-label{position:absolute;bottom:-22px;left:50%;transform:translateX(-50%);background:#3b82f6;color:#fff;padding:1px 4px;border-radius:2px;font-size:9px;font-weight:600;white-space:nowrap}.delta-reticle{position:absolute;width:2px;height:100%;box-shadow:0 0 2px rgba(0,0,0,.5);pointer-events:none;z-index:10;top:0}.delta-reticle::before{content:"";position:absolute;left:-3px;top:-5px;width:0;height:0;border-left:4px solid transparent;border-right:4px solid transparent}.delta-reticle-label{position:absolute;top:-22px;left:50%;transform:translateX(-50%);padding:1px 4px;border-radius:2px;font-size:9px;font-weight:600;white-space:nowrap;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,0.9),0 0 4px rgba(0,0,0,0.7),0 0 8px rgba(0,0,0,0.4);border:1px solid #000}.reticle-overlay{position:absolute;height:100%;top:0;pointer-events:none;z-index:4;border-radius:4px}.reticle-glow{position:absolute;height:100%;top:0;pointer-events:none;z-index:3;border-radius:4px}';
document.head.appendChild(style);
var days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
function fmtTime(d, short) {
var day = days[d.getDay()];
var h = d.getHours();
var m = d.getMinutes();
var ap = h >= 12 ? 'PM' : 'AM';
h = h % 12;
if (h === 0) h = 12;
var ts = h + ':' + (m < 10 ? '0' : '') + m + ' ' + ap;
return short ? ts : day + ' ' + ts;
}
function fmtDelta(hrs, pct) {
var over = hrs >= 0;
hrs = Math.abs(hrs);
var d = Math.floor(hrs / 24);
var h = Math.floor(hrs % 24);
var m = Math.round((hrs - Math.floor(hrs)) * 60);
var t = '';
if (d > 0) t = d + 'd ' + h + 'h';
else if (h > 0) t = h + 'h' + (m > 0 ? ' ' + m + 'm' : '');
else t = m + 'm';
return t + ' ' + (over ? 'OVER' : 'UNDER') + ' (' + Math.abs(Math.round(pct)) + '%)';
}
function getColor(pct) {
var raw = Math.min(Math.abs(pct) / 100 * 2, 1);
var p = 0.35 + (1 - 0.35) * raw;
if (pct < 0) {
var sat = 5 + p * 70;
var lit = 95 - p * 55;
return 'hsl(142,' + sat + '%,' + lit + '%)';
} else {
var sat = 5 + p * 75;
var lit = 95 - p * 55;
return 'hsl(0,' + sat + '%,' + lit + '%)';
}
}
function addReticles() {
var containers = document.querySelectorAll('div.flex.flex-row.gap-x-8.justify-between.items-center');
var added = 0;
containers.forEach(function(c) {
var p = c.querySelector('p.text-text-400.whitespace-nowrap');
if (!p) return;
var t = p.textContent;
var titleEl = c.querySelector('p.text-text-100');
var isSession = titleEl && titleEl.textContent.toLowerCase().includes('current session');
var windowHrs = isSession ? 5 : 168;
var hrsUntil, reset;
var m1 = t.match(/in\s+(?:(\d+)\s*hr?)?\s*(?:(\d+)\s*min)?/i);
if (m1 && (m1[1] || m1[2])) {
hrsUntil = parseInt(m1[1] || 0) + (parseInt(m1[2] || 0) / 60);
reset = new Date(Date.now() + hrsUntil * 3600000);
} else {
var m2 = t.match(/(sun|mon|tue|wed|thu|fri|sat)\w*\s+(\d+):(\d+)\s*(am|pm)/i);
if (!m2) return;
var h = parseInt(m2[2]);
if (m2[4].toLowerCase() === 'pm' && h !== 12) h += 12;
if (m2[4].toLowerCase() === 'am' && h === 12) h = 0;
var di = {sun:0, mon:1, tue:2, wed:3, thu:4, fri:5, sat:6};
var rd = di[m2[1].toLowerCase().slice(0, 3)];
var now = new Date();
reset = new Date();
reset.setHours(h, parseInt(m2[3]), 0, 0);
var d = rd - now.getDay();
if (d < 0) d += 7;
if (d === 0 && reset <= now) d = 7;
reset.setDate(now.getDate() + d);
hrsUntil = (reset - now) / 3600000;
}
var nowPos = Math.max(0, Math.min(100, ((windowHrs - hrsUntil) / windowHrs) * 100));
var bar = c.querySelector('div.bg-bg-000.rounded.border.h-4');
if (!bar) return;
var fill = bar.querySelector('div');
var usagePos = 0;
if (fill) {
var w = fill.style.width;
if (w) usagePos = parseFloat(w);
}
var windowStart = new Date(reset.getTime() - windowHrs * 3600000);
var usageHrs = (usagePos / 100) * windowHrs;
var usageTime = new Date(windowStart.getTime() + usageHrs * 3600000);
var usageLbl = fmtTime(usageTime, isSession);
var diffPct = usagePos - nowPos;
var diffHrs = (diffPct / 100) * windowHrs;
var deltaLbl = fmtDelta(diffHrs, diffPct);
var color = getColor(diffPct);
var raw = Math.min(Math.abs(diffPct) / 100 * 2, 1);
var intensity = 0.35 + (1 - 0.35) * raw;
bar.style.position = 'relative';
bar.style.overflow = 'visible';
// Remove old elements
bar.querySelectorAll('.delta-reticle,.usage-reticle,.reticle-overlay,.reticle-glow').forEach(function(el) {
el.remove();
});
// Add overlay/glow
if (diffPct > 0) {
// Over budget - red glow + overlay
var glow = document.createElement('div');
glow.className = 'reticle-glow';
glow.style.left = nowPos + '%';
glow.style.width = Math.abs(diffPct) + '%';
glow.style.boxShadow = '0 0 ' + (8 + intensity * 15) + 'px ' + (2 + intensity * 5) + 'px hsla(0,' + (50 + intensity * 30) + '%,' + (50 - intensity * 10) + '%,' + (0.4 + intensity * 0.4) + ')';
bar.appendChild(glow);
var ov = document.createElement('div');
ov.className = 'reticle-overlay';
ov.style.left = nowPos + '%';
ov.style.width = Math.abs(diffPct) + '%';
ov.style.background = 'hsla(0,' + (60 + intensity * 20) + '%,' + (40 - intensity * 10) + '%,' + (0.55 + intensity * 0.25) + ')';
bar.appendChild(ov);
} else if (diffPct < 0) {
// Under budget - green overlay
var ov = document.createElement('div');
ov.className = 'reticle-overlay';
ov.style.left = usagePos + '%';
ov.style.width = Math.abs(diffPct) + '%';
ov.style.background = 'hsla(142,' + (40 + intensity * 30) + '%,' + (50 - intensity * 10) + '%,' + (0.4 + intensity * 0.35) + ')';
bar.appendChild(ov);
}
// Delta reticle (at NOW position)
var dr = document.createElement('div');
dr.className = 'delta-reticle';
dr.style.left = nowPos + '%';
dr.style.background = color;
var arrowStyle = document.createElement('style');
arrowStyle.textContent = '.delta-reticle::before{border-top:5px solid ' + color + '}';
dr.appendChild(arrowStyle);
var dlbl = document.createElement('div');
dlbl.className = 'delta-reticle-label';
dlbl.style.background = color;
dlbl.textContent = deltaLbl;
dr.appendChild(dlbl);
bar.appendChild(dr);
// Usage reticle (at usage position)
var ur = document.createElement('div');
ur.className = 'usage-reticle';
ur.style.left = usagePos + '%';
var ulbl = document.createElement('div');
ulbl.className = 'usage-reticle-label';
ulbl.textContent = usageLbl;
ur.appendChild(ulbl);
bar.appendChild(ur);
added++;
});
return added;
}
// Initial attempt
var count = addReticles();
// Retry if nothing found (page still loading)
if (count === 0) {
var attempts = 0;
var interval = setInterval(function() {
attempts++;
if (addReticles() > 0 || attempts >= 10) {
clearInterval(interval);
}
}, 1000);
}
// Auto-refresh every minute to keep positions current
setInterval(addReticles, 60000);
// Watch for SPA navigation
var lastUrl = location.href;
new MutationObserver(function() {
if (location.href !== lastUrl) {
lastUrl = location.href;
setTimeout(addReticles, 1000);
}
}).observe(document.body, {childList: true, subtree: true});
})();