first commit

This commit is contained in:
Carlos Narro
2026-03-18 23:11:18 +01:00
commit ed2d44fc13
6 changed files with 1879 additions and 0 deletions

648
content.js Normal file
View File

@@ -0,0 +1,648 @@
// Claude Usage Tracker - Content Script
(function() {
'use strict';
// Días en español - índice 0 = Domingo
const DAYS_ES = ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'];
const DAYS_ES_DISPLAY = ['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'];
const DAYS_FULL_ES = ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'];
// Boost 2× promotion config (March 2026)
// Peak hours: 8AM - 2PM ET (Eastern Time, UTC-4 in March)
// Outside peak = 2× boost
const BOOST_END_DATE = new Date('2026-03-28T06:59:00Z'); // March 27, 11:59 PM PT = March 28, 6:59 AM UTC
const PEAK_START_UTC = 12; // 8AM ET = 12:00 UTC
const PEAK_END_UTC = 18; // 2PM ET = 18:00 UTC
// Session tracking
let sessionStartTime = null;
const SESSION_KEY = 'claude_usage_tracker_session';
function initSession() {
const stored = localStorage.getItem(SESSION_KEY);
if (stored) {
const data = JSON.parse(stored);
const storedDate = new Date(data.startTime);
const now = new Date();
if (storedDate.toDateString() === now.toDateString() &&
(now - storedDate) < 4 * 60 * 60 * 1000) {
sessionStartTime = new Date(data.startTime);
} else {
sessionStartTime = new Date();
saveSession();
}
} else {
sessionStartTime = new Date();
saveSession();
}
}
function saveSession() {
localStorage.setItem(SESSION_KEY, JSON.stringify({
startTime: sessionStartTime.toISOString()
}));
}
function formatTime(date) {
return date.toLocaleTimeString('es-ES', {
hour: '2-digit',
minute: '2-digit'
});
}
/**
* Calcula el estado del boost 2×
* Retorna { isActive, isBoostPeriod, nextChangeIn, nextChangeTime, promotionEnded }
*/
function getBoostStatus() {
const now = new Date();
// Verificar si la promoción ha terminado
if (now >= BOOST_END_DATE) {
return { isActive: false, isBoostPeriod: false, promotionEnded: true };
}
const utcHour = now.getUTCHours();
const utcMinutes = now.getUTCMinutes();
// Peak hours: 12:00 - 18:00 UTC (8AM - 2PM ET)
const isPeakHour = utcHour >= PEAK_START_UTC && utcHour < PEAK_END_UTC;
const isBoostPeriod = !isPeakHour; // Boost es cuando NO es hora pico
// Calcular tiempo hasta el próximo cambio
let nextChangeHour;
if (isPeakHour) {
// Estamos en pico, próximo cambio es cuando termine (18:00 UTC)
nextChangeHour = PEAK_END_UTC;
} else {
// Estamos en boost, próximo cambio es cuando empiece el pico
if (utcHour < PEAK_START_UTC) {
nextChangeHour = PEAK_START_UTC;
} else {
// Después de las 18:00 UTC, el próximo pico es mañana a las 12:00 UTC
nextChangeHour = PEAK_START_UTC + 24;
}
}
const minutesUntilChange = (nextChangeHour * 60) - (utcHour * 60 + utcMinutes);
const hoursUntilChange = Math.floor(minutesUntilChange / 60);
const minsUntilChange = minutesUntilChange % 60;
// Calcular hora local del próximo cambio
const nextChangeTime = new Date(now);
nextChangeTime.setUTCHours(nextChangeHour % 24, 0, 0, 0);
if (nextChangeHour >= 24) {
nextChangeTime.setDate(nextChangeTime.getDate() + 1);
}
return {
isActive: true,
isBoostPeriod,
nextChangeIn: `${hoursUntilChange}h ${minsUntilChange}m`,
nextChangeTime: formatTime(nextChangeTime),
promotionEnded: false
};
}
function formatDuration(ms) {
const hours = Math.floor(ms / (1000 * 60 * 60));
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes}m`;
}
const SESSION_DURATION_HOURS = 5; // Las sesiones de Claude duran 5 horas
/**
* Parsea el tiempo restante desde texto como "Se restablece en 3 h 44 min"
* Retorna el tiempo restante en minutos
*/
function parseTimeRemaining(text) {
const match = text.match(/(\d+)\s*h\s*(\d+)\s*min/i);
if (match) {
return parseInt(match[1]) * 60 + parseInt(match[2]);
}
// Solo minutos: "Se restablece en 44 min"
const minMatch = text.match(/(\d+)\s*min/i);
if (minMatch) {
return parseInt(minMatch[1]);
}
// Solo horas: "Se restablece en 3 h"
const hourMatch = text.match(/(\d+)\s*h/i);
if (hourMatch) {
return parseInt(hourMatch[1]) * 60;
}
return 0;
}
/**
* Parsea la información de la página real de Claude
* Busca los progressbar con aria-valuenow y el texto "Se restablece..."
*/
function parsePageData() {
const data = {
sessionUsage: 0,
sessionResetText: '',
sessionTimeRemainingMin: 0,
sessionStartTime: null,
sessionEndTime: null,
weeklyUsage: 0,
weeklyResetText: '',
resetDayIndex: -1,
resetHour: '',
daysUntilReset: 0,
daysPassed: 0
};
// Buscar todas las secciones con progressbar
const sections = document.querySelectorAll('section');
for (const section of sections) {
const rows = section.querySelectorAll('.flex.flex-row');
for (const row of rows) {
const labelEl = row.querySelector('p.font-base.text-text-100');
const resetTextEl = row.querySelector('p.font-base.text-text-400');
const progressBar = row.querySelector('[role="progressbar"]');
if (!labelEl || !progressBar) continue;
const label = labelEl.textContent.trim();
const usage = parseFloat(progressBar.getAttribute('aria-valuenow')) || 0;
const resetText = resetTextEl ? resetTextEl.textContent.trim() : '';
if (label === 'Sesión actual') {
data.sessionUsage = usage;
data.sessionResetText = resetText;
// Calcular tiempo restante y horas de inicio/fin
data.sessionTimeRemainingMin = parseTimeRemaining(resetText);
if (data.sessionTimeRemainingMin > 0) {
const now = new Date();
const sessionDurationMin = SESSION_DURATION_HOURS * 60;
const elapsedMin = sessionDurationMin - data.sessionTimeRemainingMin;
// Hora de inicio = ahora - tiempo transcurrido
data.sessionStartTime = new Date(now.getTime() - elapsedMin * 60 * 1000);
// Hora de fin = ahora + tiempo restante
data.sessionEndTime = new Date(now.getTime() + data.sessionTimeRemainingMin * 60 * 1000);
}
} else if (label === 'Todos los modelos') {
data.weeklyUsage = usage;
data.weeklyResetText = resetText;
// Parsear "Se restablece vie, 4:59" para obtener día y hora
const resetMatch = resetText.match(/Se restablece\s+(\w+),?\s*([\d:]+)/i);
if (resetMatch) {
const dayAbbr = resetMatch[1].toLowerCase();
data.resetHour = resetMatch[2];
// Encontrar el índice del día
data.resetDayIndex = DAYS_ES.findIndex(d => d === dayAbbr || d.startsWith(dayAbbr.substring(0, 3)));
}
}
}
}
// Calcular días hasta el reinicio y días pasados
if (data.resetDayIndex >= 0) {
const now = new Date();
const todayIndex = now.getDay(); // 0 = Domingo, 1 = Lunes, etc.
// Calcular días hasta el reinicio
let daysUntil = data.resetDayIndex - todayIndex;
if (daysUntil <= 0) {
daysUntil += 7;
}
// Si es el mismo día pero ya pasó la hora, es la próxima semana
if (daysUntil === 7 || (daysUntil === 0 && data.resetHour)) {
const [resetH, resetM] = data.resetHour.split(':').map(Number);
if (now.getHours() > resetH || (now.getHours() === resetH && now.getMinutes() >= resetM)) {
daysUntil = 7;
}
}
data.daysUntilReset = daysUntil;
data.daysPassed = 7 - daysUntil;
}
return data;
}
/**
* Calcula el desglose diario basado en los datos reales
* Usa HORAS transcurridas para mayor precisión
*/
function calculateDailyUsage(pageData) {
const { weeklyUsage, resetDayIndex, resetHour } = pageData;
const now = new Date();
const todayIndex = now.getDay();
const currentHour = now.getHours() + now.getMinutes() / 60;
// Calcular el momento exacto del último reinicio
const [resetH, resetM] = (resetHour || '5:00').split(':').map(Number);
// Encontrar la fecha del último reinicio
let lastResetDate = new Date(now);
let daysSinceReset = todayIndex - resetDayIndex;
if (daysSinceReset < 0) daysSinceReset += 7;
if (daysSinceReset === 0) {
// Es el día de reinicio, ¿ya pasó la hora?
if (now.getHours() < resetH || (now.getHours() === resetH && now.getMinutes() < resetM)) {
daysSinceReset = 7; // Aún no ha reiniciado, usar el de la semana pasada
}
}
lastResetDate.setDate(now.getDate() - daysSinceReset);
lastResetDate.setHours(resetH, resetM || 0, 0, 0);
// Horas totales transcurridas desde el reinicio
const msElapsed = now - lastResetDate;
const hoursElapsed = msElapsed / (1000 * 60 * 60);
const totalWeekHours = 7 * 24; // 168 horas
// Porcentaje de la semana transcurrido
const weekProgressPercent = (hoursElapsed / totalWeekHours) * 100;
// Uso ideal hasta ahora (proporcional a las horas transcurridas)
const idealUsedByNow = weekProgressPercent;
// Uso ideal por día (para referencia visual)
const idealDailyPercent = 100 / 7;
// Promedio real de uso por hora
const avgUsagePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
const idealUsagePerHour = 100 / totalWeekHours;
// La semana empieza el mismo día del reinicio (ej: viernes)
// El primer día es el día del reinicio, desde la hora de reinicio
const startDayIndex = resetDayIndex;
// Calcular la posición del día actual dentro de la semana de facturación
// Posición 0 = día del reinicio, posición 1 = día siguiente, etc.
let todayPositionInWeek = todayIndex - resetDayIndex;
if (todayPositionInWeek < 0) todayPositionInWeek += 7;
const dailyBreakdown = [];
for (let i = 0; i < 7; i++) {
const dayIndex = (startDayIndex + i) % 7;
const isToday = (i === todayPositionInWeek);
const isPast = (i < todayPositionInWeek);
const isFuture = (i > todayPositionInWeek);
// Calcular cuántas horas de este día han transcurrido
let dayHoursElapsed = 0;
if (isPast) {
// Día completo ya pasado
if (i === 0) {
// Primer día (día del reinicio): desde hora de reinicio hasta medianoche
dayHoursElapsed = 24 - (resetH + (resetM || 0) / 60);
} else {
dayHoursElapsed = 24;
}
} else if (isToday) {
if (i === 0) {
// Hoy es el día del reinicio: desde hora de reinicio hasta ahora
dayHoursElapsed = currentHour - (resetH + (resetM || 0) / 60);
if (dayHoursElapsed < 0) dayHoursElapsed = 0;
} else {
// Día actual normal: desde medianoche hasta ahora
dayHoursElapsed = currentHour;
}
}
// Días futuros: dayHoursElapsed = 0
// Calcular horas totales del día (para el porcentaje de llenado)
let dayTotalHours = 24;
if (i === 0) {
// Primer día: solo cuenta desde la hora de reinicio
dayTotalHours = 24 - (resetH + (resetM || 0) / 60);
}
// Porcentaje de llenado del día (0-100%)
const dayFillPercent = dayTotalHours > 0 ? (dayHoursElapsed / dayTotalHours) * 100 : 0;
// Uso asignado a este día (promedio distribuido)
let usage = 0;
if (dayHoursElapsed > 0 && hoursElapsed > 0) {
// Distribuir el uso total proporcionalmente a las horas transcurridas
usage = (dayHoursElapsed / hoursElapsed) * weeklyUsage;
}
// Estado basado en si el uso del día está dentro del ideal
let status = 'pending';
if (dayHoursElapsed > 0) {
const idealForThisDay = (dayHoursElapsed / 24) * idealDailyPercent;
status = usage <= idealForThisDay * 1.1 ? 'good' :
usage <= idealForThisDay * 1.5 ? 'warning' : 'danger';
}
// Offset para el primer día (horas antes del reinicio que no cuentan)
const dayStartOffsetPercent = (i === 0) ? ((resetH + (resetM || 0) / 60) / 24) * 100 : 0;
dailyBreakdown.push({
dayIndex,
dayName: DAYS_ES_DISPLAY[dayIndex],
dayFullName: DAYS_FULL_ES[dayIndex],
isPast,
isToday,
isFuture,
isResetDay: i === 0,
usage,
idealUsage: idealDailyPercent,
dayFillPercent, // % del día transcurrido (para llenar la barra)
dayHoursElapsed,
dayTotalHours,
dayStartOffsetPercent, // % del día antes del reinicio (hueco a la izquierda)
status
});
}
// Calcular fecha estimada de fin del uso
const usageRatePerHour = hoursElapsed > 0 ? weeklyUsage / hoursElapsed : 0;
const hoursToReach100 = usageRatePerHour > 0 ? (100 - weeklyUsage) / usageRatePerHour : Infinity;
const hoursUntilReset = totalWeekHours - hoursElapsed;
let estimatedEndDate = null;
let willRunOut = false;
if (usageRatePerHour > 0 && hoursToReach100 < hoursUntilReset) {
// Se acabará antes del reinicio
willRunOut = true;
estimatedEndDate = new Date(now.getTime() + hoursToReach100 * 60 * 60 * 1000);
} else {
// Llegará al reinicio con uso disponible
estimatedEndDate = new Date(lastResetDate.getTime() + 7 * 24 * 60 * 60 * 1000);
}
// Determinar estado con más granularidad
const diffPercent = weeklyUsage - idealUsedByNow;
let status = 'perfect'; // Por debajo del ideal
if (diffPercent > 0 && diffPercent <= 5) status = 'good'; // Hasta 5% por encima
else if (diffPercent > 5 && diffPercent <= 15) status = 'warning'; // 5-15% por encima
else if (diffPercent > 15) status = 'danger'; // Más del 15% por encima
return {
dailyBreakdown,
idealDailyPercent,
idealUsedByNow,
hoursElapsed,
avgUsagePerHour,
idealUsagePerHour,
hoursUntilReset,
estimatedEndDate,
willRunOut,
hoursToReach100,
status
};
}
function createWeeklyTracker(pageData, dailyData) {
const container = document.createElement('div');
container.className = 'claude-usage-tracker-container';
container.id = 'claude-usage-tracker';
const usagePercent = pageData.weeklyUsage;
const resetDayName = DAYS_FULL_ES[pageData.resetDayIndex] || 'próximo reinicio';
const boostStatus = getBoostStatus();
container.innerHTML = `
${boostStatus.isActive ? `
<div class="boost-indicator ${boostStatus.isBoostPeriod ? 'active' : 'inactive'}">
<span class="boost-badge">${boostStatus.isBoostPeriod ? '⚡ 2×' : '1×'}</span>
<span class="boost-text">${boostStatus.isBoostPeriod ? 'Boost activo' : 'Horario normal'}</span>
<span class="boost-next">${boostStatus.isBoostPeriod ? '1× a las' : '2× a las'} ${boostStatus.nextChangeTime} (${boostStatus.nextChangeIn})</span>
</div>
` : ''}
<div class="claude-usage-tracker-title">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
<line x1="16" y1="2" x2="16" y2="6"></line>
<line x1="8" y1="2" x2="8" y2="6"></line>
<line x1="3" y1="10" x2="21" y2="10"></line>
</svg>
Uso semanal por días
</div>
<div class="weekly-progress-container">
${dailyData.dailyBreakdown.map(day => `
<div class="day-segment ${day.isToday ? 'today' : ''} ${day.isFuture ? 'future' : ''} ${day.isResetDay ? 'reset-day' : ''}">
<div class="day-tooltip">
${day.dayFullName}: ${day.usage.toFixed(1)}% usado
${day.isToday ? ` (${day.dayHoursElapsed.toFixed(1)}h transcurridas)` : day.isPast ? ' (completo)' : ''}
${day.isResetDay ? `<br>Reinicio a las ${pageData.resetHour}` : ''}
<br>Ideal para ${day.dayHoursElapsed.toFixed(0)}h: ${((day.dayHoursElapsed / 24) * day.idealUsage).toFixed(1)}%
</div>
<div class="day-bar">
${day.isResetDay ? `<div class="day-bar-offset" style="width: ${day.dayStartOffsetPercent}%"></div>` : ''}
<div class="day-bar-bg" style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%"></div>
<div class="day-bar-fill ${day.status === 'good' ? 'under-budget' : day.status === 'warning' ? 'on-track' : day.status === 'danger' ? 'over-budget' : ''}"
style="left: ${day.dayStartOffsetPercent}%; width: ${(100 - day.dayStartOffsetPercent) * day.dayFillPercent / 100}%">
</div>
</div>
<span class="day-label">${day.dayName}</span>
</div>
`).join('')}
</div>
<div class="progress-summary">
<div class="progress-ideal">
<span class="progress-ideal-indicator ${dailyData.status}"></span>
<span>${dailyData.status === 'perfect' ? '✓ Perfecto' : dailyData.status === 'good' ? '✓ Vas bien' : dailyData.status === 'warning' ? '⚠ Atención' : '✗ Excedido'}</span>
</div>
<div class="usage-values">
<div class="usage-value">
<span class="usage-value-label">Usado</span>
<span class="usage-value-number used">${usagePercent.toFixed(1)}%</span>
</div>
<div class="usage-value">
<span class="usage-value-label">Ideal</span>
<span class="usage-value-number ideal">${dailyData.idealUsedByNow.toFixed(1)}%</span>
</div>
<div class="usage-value">
<span class="usage-value-label">Diferencia</span>
<span class="usage-value-number ${usagePercent <= dailyData.idealUsedByNow ? 'under' : 'over'}">
${usagePercent <= dailyData.idealUsedByNow ? '-' : '+'}${Math.abs(usagePercent - dailyData.idealUsedByNow).toFixed(1)}%
</span>
</div>
</div>
</div>
<div class="estimated-end ${dailyData.willRunOut ? 'warning' : ''}">
<span class="estimated-end-label">${dailyData.willRunOut ? '⚠ Se agotará el' : '📅 Reinicio el'}</span>
<span class="estimated-end-date">${dailyData.estimatedEndDate ? dailyData.estimatedEndDate.toLocaleDateString('es-ES', { weekday: 'long', day: 'numeric', month: 'short' }) : '--'}</span>
<span class="estimated-end-time">${dailyData.estimatedEndDate ? dailyData.estimatedEndDate.toLocaleTimeString('es-ES', { hour: '2-digit', minute: '2-digit' }) : '--:--'}</span>
${dailyData.willRunOut ? `<span class="estimated-end-hours">(en ~${Math.round(dailyData.hoursToReach100)}h)</span>` : ''}
</div>
<div class="usage-info-row">
<div class="usage-stat">
<span class="usage-stat-label">Tiempo transcurrido</span>
<span class="usage-stat-value">${Math.floor(dailyData.hoursElapsed)}h ${Math.round((dailyData.hoursElapsed % 1) * 60)}m</span>
</div>
<div class="usage-stat">
<span class="usage-stat-label">Uso/día real vs ideal</span>
<span class="usage-stat-value ${dailyData.avgUsagePerHour <= dailyData.idealUsagePerHour * 1.1 ? 'good' : dailyData.avgUsagePerHour <= dailyData.idealUsagePerHour * 1.5 ? 'warning' : 'danger'}">
${(dailyData.avgUsagePerHour * 24).toFixed(2)}% / ${(dailyData.idealUsagePerHour * 24).toFixed(2)}%
</span>
</div>
<div class="usage-stat">
<span class="usage-stat-label">Horas restantes</span>
<span class="usage-stat-value">${Math.round((168 - dailyData.hoursElapsed))}h</span>
</div>
</div>
<div class="session-info-container">
<div class="session-info-title">
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
Sesión actual
</div>
<div class="session-sliders">
<div class="session-slider-row">
<span class="session-slider-label">⏱ Tiempo</span>
<div class="session-slider-track">
<div class="session-slider-fill time" style="width: ${100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100}%"></div>
<div class="session-slider-marker" style="left: ${100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100}%"></div>
</div>
<span class="session-slider-value">${Math.round(100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100)}%</span>
</div>
<div class="session-slider-row">
<span class="session-slider-label">📊 Uso</span>
<div class="session-slider-track">
<div class="session-slider-fill usage ${pageData.sessionUsage <= (100 - (pageData.sessionTimeRemainingMin / (SESSION_DURATION_HOURS * 60)) * 100) ? 'good' : 'over'}" style="width: ${pageData.sessionUsage}%"></div>
<div class="session-slider-marker" style="left: ${pageData.sessionUsage}%"></div>
</div>
<span class="session-slider-value">${pageData.sessionUsage}%</span>
</div>
</div>
<div class="session-info-grid">
<div class="session-info-item">
<span class="session-info-label">Inicio</span>
<span class="session-info-value">${pageData.sessionStartTime ? formatTime(pageData.sessionStartTime) : '--:--'}</span>
</div>
<div class="session-info-item">
<span class="session-info-label">Fin</span>
<span class="session-info-value">${pageData.sessionEndTime ? formatTime(pageData.sessionEndTime) : '--:--'}</span>
</div>
<div class="session-info-item">
<span class="session-info-label">Restante</span>
<span class="session-info-value">${pageData.sessionTimeRemainingMin > 0 ? Math.floor(pageData.sessionTimeRemainingMin / 60) + 'h ' + (pageData.sessionTimeRemainingMin % 60) + 'm' : '--'}</span>
</div>
</div>
</div>
<div class="reset-info">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="23 4 23 10 17 10"></polyline>
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
</svg>
Reinicio semanal: ${resetDayName} a las ${pageData.resetHour} (en ${pageData.daysUntilReset} día${pageData.daysUntilReset !== 1 ? 's' : ''})
</div>
`;
return container;
}
function injectTracker() {
// Remove existing tracker if present
const existing = document.getElementById('claude-usage-tracker');
if (existing) {
existing.remove();
}
initSession();
// Parsear datos reales de la página
const pageData = parsePageData();
// Si no encontramos datos, no inyectar nada
if (pageData.weeklyUsage === 0 && pageData.resetDayIndex === -1) {
console.log('[Claude Usage Tracker] No se encontraron datos de uso en la página');
return;
}
const dailyData = calculateDailyUsage(pageData);
const tracker = createWeeklyTracker(pageData, dailyData);
// Insertar justo DEBAJO del título principal h1
const mainContent = document.querySelector('main');
if (!mainContent) {
console.log('[Claude Usage Tracker] No se encontró el main');
return;
}
// Buscar el h1 del título principal "Ajustes" (visible en móvil)
const mobileH1 = mainContent.querySelector('h1.font-heading');
if (mobileH1) {
// Insertar justo después del h1
mobileH1.after(tracker);
} else {
// Fallback: insertar al principio del main content
const contentDiv = mainContent.querySelector('.pb-8') || mainContent.querySelector('div > div');
if (contentDiv) {
contentDiv.prepend(tracker);
} else {
mainContent.prepend(tracker);
}
}
console.log('[Claude Usage Tracker] Inyectado en la parte superior', {
pageData,
dailyData
});
}
// Wait for page to fully load
function waitForContent() {
const observer = new MutationObserver((mutations, obs) => {
const main = document.querySelector('main');
const hasContent = main && main.textContent.length > 100;
if (hasContent) {
obs.disconnect();
setTimeout(injectTracker, 500); // Small delay to ensure content is rendered
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// Timeout fallback
setTimeout(() => {
observer.disconnect();
injectTracker();
}, 3000);
}
// Initialize
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', waitForContent);
} else {
waitForContent();
}
// Update session time periodically
setInterval(() => {
const sessionValue = document.querySelector('.session-info-value');
if (sessionValue) {
injectTracker();
}
}, 60000); // Update every minute
})();