AI Usage

Show pace dividers on AI usage pages (Codex, Claude, Kimi Code)

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

Advertisement:

// ==UserScript==
// @name         AI Usage
// @namespace    https://github.com/a322655
// @version      1.1.0
// @author       WindFade
// @description  Show pace dividers on AI usage pages (Codex, Claude, Kimi Code)
// @license      MIT
// @homepageURL  https://github.com/eigenigma/ai-usage-userscript
// @supportURL   https://github.com/eigenigma/ai-usage-userscript/issues
// @match        https://chatgpt.com/codex/cloud/settings/analytics*
// @match        https://claude.ai/settings/usage*
// @match        https://www.kimi.com/code/console*
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_unregisterMenuCommand
// @grant        unsafeWindow
// @run-at       document-start
// ==/UserScript==

(function() {
	"use strict";
	var _GM_getValue = (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
	var _GM_registerMenuCommand = (() => typeof GM_registerMenuCommand != "undefined" ? GM_registerMenuCommand : void 0)();
	var _GM_setValue = (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
	var _GM_unregisterMenuCommand = (() => typeof GM_unregisterMenuCommand != "undefined" ? GM_unregisterMenuCommand : void 0)();
	var _unsafeWindow = (() => typeof unsafeWindow != "undefined" ? unsafeWindow : void 0)();
	var interceptedData = null;
	var USAGE_API_PATH = "/backend-api/wham/usage";
	var isUsageApiUrl = (url) => url.includes(USAGE_API_PATH) === true && url.includes("daily") === false && url.includes("credit") === false;
	var extractUrlFromInput = (input) => {
		if (typeof input === "string") return input;
		if (input instanceof Request) return input.url;
		return "";
	};
	var handleInterceptedResponse = (response) => {
		if (response.ok === false) return;
		response.clone().json().then((data) => {
			interceptedData = data;
		}).catch(() => void 0);
	};
	var installFetchInterceptor = () => {
		const originalFetch = _unsafeWindow.fetch;
		_unsafeWindow.fetch = new Proxy(originalFetch, { apply: (target, thisArg, args) => {
			const result = Reflect.apply(target, thisArg, args);
			if (isUsageApiUrl(extractUrlFromInput(args[0])) === true) result.then(handleInterceptedResponse).catch(() => void 0);
			return result;
		} });
	};
	if (globalThis.location.hostname === "chatgpt.com") installFetchInterceptor();
	var toWindow = (apiWindow) => {
		if (apiWindow === null || apiWindow === void 0) return null;
		if (apiWindow.limit_window_seconds <= 0 || apiWindow.reset_at <= 0) return null;
		return {
			durationMs: apiWindow.limit_window_seconds * 1e3,
			resetAt: new Date(apiWindow.reset_at * 1e3)
		};
	};
	var resolveRateLimitWindow = (rateLimit, headerText) => {
		if (/weekly/iu.test(headerText) === true) return toWindow(rateLimit.secondary_window);
		if (/\d+\s*hour/iu.test(headerText) === true) return toWindow(rateLimit.primary_window);
		return null;
	};
	var findAdditionalModelWindow = (additionalLimits, headerText) => {
		for (const model of additionalLimits) if (headerText.includes(model.limit_name) === true) return resolveRateLimitWindow(model.rate_limit, headerText);
		return null;
	};
	var findCodexRateLimitWindow = (headerText) => {
		if (interceptedData === null) return null;
		if (interceptedData.additional_rate_limits !== void 0) {
			const modelWindow = findAdditionalModelWindow(interceptedData.additional_rate_limits, headerText);
			if (modelWindow !== null) return modelWindow;
		}
		if (/code\s*review/iu.test(headerText) === true) return toWindow(interceptedData.code_review_rate_limit?.primary_window ?? null);
		return resolveRateLimitWindow(interceptedData.rate_limit ?? {
			primary_window: null,
			secondary_window: null
		}, headerText);
	};
	var DISABLED_LABELS_KEY = `dividerDisabledLabels:${globalThis.location.hostname}`;
	var readDisabledLabels = () => {
		const stored = _GM_getValue(DISABLED_LABELS_KEY, []);
		if (Array.isArray(stored) === false) return [];
		return stored.filter((entry) => typeof entry === "string");
	};
	var toggleDivider = (label) => {
		const disabledLabels = readDisabledLabels();
		_GM_setValue(DISABLED_LABELS_KEY, disabledLabels.includes(label) === true ? disabledLabels.filter((entry) => entry !== label) : [...disabledLabels, label]);
	};
	var registeredMenuIds = [];
	var lastMenuSignature = null;
	var syncSettingsMenu = (labels, disabledLabels, onToggle) => {
		const uniqueLabels = [...new Set(labels)];
		const menuSignature = JSON.stringify([uniqueLabels, disabledLabels]);
		if (menuSignature === lastMenuSignature) return;
		lastMenuSignature = menuSignature;
		for (const menuId of registeredMenuIds) _GM_unregisterMenuCommand(menuId);
		registeredMenuIds = uniqueLabels.map((label) => _GM_registerMenuCommand(`${disabledLabels.includes(label) === true ? "✗" : "✓"} ${label}`, () => {
			toggleDivider(label);
			onToggle();
		}));
	};
	var DAY_ABBR_TO_INDEX = {
		sun: 0,
		mon: 1,
		tue: 2,
		wed: 3,
		thu: 4,
		fri: 5,
		sat: 6
	};
	var parseTimeTokens = (hourToken, minuteToken, meridiemToken) => {
		const hourValue = Number.parseInt(hourToken, 10);
		const minuteValue = Number.parseInt(minuteToken, 10);
		if (Number.isNaN(hourValue) === true || Number.isNaN(minuteValue) === true || hourValue < 1 || hourValue > 12 || minuteValue < 0 || minuteValue > 59) return null;
		let normalizedHours = hourValue % 12;
		if (meridiemToken.toUpperCase() === "PM") normalizedHours += 12;
		return normalizedHours * 60 + minuteValue;
	};
	var buildDateAtTimeOfDay = (totalMinutes, now) => {
		const candidateDate = new Date(now.getTime());
		candidateDate.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
		return candidateDate;
	};
	var parseDayTimeLabel = (resetLabel, now) => {
		const dayTimeMatch = resetLabel.match(/^\s*(?<day>Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*\s+(?<hour>\d{1,2}):(?<minute>\d{2})\s*(?<meridiem>[AP]M)\s*$/iu);
		if (dayTimeMatch?.groups === void 0) return null;
		const totalMinutes = parseTimeTokens(dayTimeMatch.groups["hour"] ?? "", dayTimeMatch.groups["minute"] ?? "", dayTimeMatch.groups["meridiem"] ?? "");
		if (totalMinutes === null) return null;
		const targetDayIndex = DAY_ABBR_TO_INDEX[(dayTimeMatch.groups["day"] ?? "").toLowerCase().slice(0, 3)];
		if (targetDayIndex === void 0) return null;
		const candidateDate = buildDateAtTimeOfDay(totalMinutes, now);
		let daysToAdd = targetDayIndex - candidateDate.getDay();
		if (daysToAdd < 0) daysToAdd += 7;
		if (daysToAdd === 0 && candidateDate.getTime() <= now.getTime()) daysToAdd = 7;
		candidateDate.setDate(candidateDate.getDate() + daysToAdd);
		return candidateDate;
	};
	var parseTimeOnlyLabel = (resetLabel, now) => {
		const timeMatch = resetLabel.match(/^\s*(?<hour>\d{1,2}):(?<minute>\d{2})\s*(?<meridiem>[AP]M)\s*$/iu);
		if (timeMatch?.groups === void 0) return null;
		const totalMinutes = parseTimeTokens(timeMatch.groups["hour"] ?? "", timeMatch.groups["minute"] ?? "", timeMatch.groups["meridiem"] ?? "");
		if (totalMinutes === null) return null;
		const candidateDate = buildDateAtTimeOfDay(totalMinutes, now);
		if (candidateDate.getTime() <= now.getTime()) candidateDate.setDate(candidateDate.getDate() + 1);
		return candidateDate;
	};
	var parseRelativeDurationMs = (resetLabel) => {
		const relativeMatch = resetLabel.match(/^in\s+(?:(?<days>\d+)\s+days?\s*)?(?:(?<hours>\d+)\s+(?:hours?|hrs?)\s*)?(?:(?<minutes>\d+)\s+(?:minutes?|mins?))?\s*$/iu);
		if (relativeMatch === null) return null;
		const days = Number.parseInt(relativeMatch.groups?.days ?? "0", 10) || 0;
		const hours = Number.parseInt(relativeMatch.groups?.hours ?? "0", 10) || 0;
		const minutes = Number.parseInt(relativeMatch.groups?.minutes ?? "0", 10) || 0;
		const totalMs = (days * 24 * 60 + hours * 60 + minutes) * 60 * 1e3;
		if (totalMs <= 0) return null;
		return totalMs;
	};
	var parseRelativeTimeLabel = (resetLabel, now) => {
		const durationMs = parseRelativeDurationMs(resetLabel);
		if (durationMs === null) return null;
		return new Date(now.getTime() + durationMs);
	};
	var parseResetDate = (resetLabel, now) => {
		const directTimestamp = Date.parse(resetLabel);
		if (Number.isNaN(directTimestamp) === false) return new Date(directTimestamp);
		return parseDayTimeLabel(resetLabel, now) ?? parseTimeOnlyLabel(resetLabel, now) ?? parseRelativeTimeLabel(resetLabel, now);
	};
	var clamp = (value, min, max) => {
		if (value < min) return min;
		if (value > max) return max;
		return value;
	};
	var normalizeWhitespace = (value) => value.replace(/\s+/gu, " ").trim();
	var ONE_WEEK_MS = 10080 * 60 * 1e3;
	var ONE_HOUR_MS = 3600 * 1e3;
	var FIVE_HOURS_MS = 5 * ONE_HOUR_MS;
	var MAX_FALLBACK_LABEL_LENGTH = 40;
	var LABEL_BOUNDARY_PATTERNS = [
		/resets/iu,
		/you've/iu,
		/\d/u,
		/\$/u
	];
	var sliceBeforeEarliestBoundary = (text, patterns) => {
		let boundaryIndex = text.length;
		for (const pattern of patterns) {
			const matchIndex = text.search(pattern);
			if (matchIndex >= 0 && matchIndex < boundaryIndex) boundaryIndex = matchIndex;
		}
		return text.slice(0, boundaryIndex).trim();
	};
	var deriveCardLabel = (text) => {
		const label = sliceBeforeEarliestBoundary(text, LABEL_BOUNDARY_PATTERNS);
		if (label.length > 0) return label;
		return text.slice(0, MAX_FALLBACK_LABEL_LENGTH).trim();
	};
	var CODEX_LABEL_BOUNDARY_PATTERNS = [/resets/iu, /\d+(?:\.\d+)?\s*%/u];
	var deriveCodexCardLabel = (headerText, fullText) => {
		const label = sliceBeforeEarliestBoundary(headerText, CODEX_LABEL_BOUNDARY_PATTERNS);
		if (label.length > 0) return label;
		return deriveCardLabel(fullText);
	};
	var parseHourWindowMs = (text) => {
		const hourWindowMatch = text.match(/\b(?<hours>\d+)[\s-]*hour\s+usage\s+limit\b/iu);
		if (hourWindowMatch === null) return null;
		const hours = Number.parseInt(hourWindowMatch.groups?.hours ?? "0", 10);
		if (Number.isNaN(hours) === true || hours <= 0) return null;
		return hours * ONE_HOUR_MS;
	};
	var inferDurationMs = (text, resetLabel) => {
		if (/weekly/iu.test(text) === true || /code\s*review/iu.test(text) === true) return ONE_WEEK_MS;
		if (/\brate\s+limit\b/iu.test(text) === true) return null;
		if (/\bcurrent\s+session/iu.test(text) === true) return FIVE_HOURS_MS;
		const hourWindowMs = parseHourWindowMs(text);
		if (hourWindowMs !== null) return hourWindowMs;
		if (resetLabel !== null) {
			const relativeMs = parseRelativeDurationMs(resetLabel);
			if (relativeMs !== null && relativeMs >= 24 * ONE_HOUR_MS) return ONE_WEEK_MS;
		}
		if (resetLabel !== null && /\b(?:Mon|Tue|Wed|Thu|Fri|Sat|Sun)\w*/iu.test(resetLabel) === true) return ONE_WEEK_MS;
		return null;
	};
	var extractResetLabel = (text) => {
		const label = text.match(/Resets\s+(?<label>.+)$/iu)?.groups?.label?.trim();
		if (label === void 0 || label.length === 0) return null;
		return label;
	};
	var findResetLabel = (containerElement, fullText) => {
		const candidateNodes = containerElement.querySelectorAll("p, span, div");
		for (const candidateNode of candidateNodes) {
			if (candidateNode instanceof HTMLElement === false) continue;
			const label = extractResetLabel(normalizeWhitespace(candidateNode.textContent ?? ""));
			if (label !== null) return label;
		}
		return extractResetLabel(fullText);
	};
	var parseResetInfo = (containerElement, fullText, durationSourceText, now) => {
		const resetLabel = findResetLabel(containerElement, fullText);
		return {
			resetAt: resetLabel === null ? null : parseResetDate(resetLabel, now),
			durationMs: inferDurationMs(durationSourceText, resetLabel)
		};
	};
	var CODEX_TRACK_SELECTOR = "div[class*=\"bg-[#ebebf0]\"]";
	var CODEX_FILL_SELECTOR = "div[class*=\"bg-[#\"]:not([class*=\"bg-[#ebebf0]\"])";
	var CLAUDE_TRACK_SELECTOR = "div[class~=\"bg-alpha-2\"][class~=\"h-2\"][class~=\"rounded-full\"]";
	var CLAUDE_FILL_SELECTOR = "div[class*=\"bg-fill-\"]";
	var KIMI_CARD_SELECTOR = ".stats-card";
	var KIMI_BAR_SELECTOR = ".stats-card-progress-bar";
	var KIMI_FILL_SELECTOR = ".stats-card-progress-filled";
	var validateTrackGeometry = (trackRect, fillRect) => {
		if (trackRect.width < 120 || trackRect.height < 6 || trackRect.height > 18) return false;
		if (fillRect.height < 4 || fillRect.height > 18) return false;
		if (Math.abs(fillRect.top - trackRect.top) > 2) return false;
		if (fillRect.width < 0 || fillRect.width > trackRect.width + 1) return false;
		return true;
	};
	var resolveCodexTrackContainer = (articleElement) => {
		const trackNode = articleElement.querySelector(CODEX_TRACK_SELECTOR);
		if (trackNode instanceof HTMLElement === false) return null;
		const trackContainerNode = trackNode.parentElement;
		if (trackContainerNode instanceof HTMLElement === false) return null;
		const trackRect = trackNode.getBoundingClientRect();
		const fillCandidates = trackContainerNode.querySelectorAll(CODEX_FILL_SELECTOR);
		for (const candidate of fillCandidates) {
			if (candidate instanceof HTMLElement === false) continue;
			if (validateTrackGeometry(trackRect, candidate.getBoundingClientRect())) return trackContainerNode;
		}
		return null;
	};
	var collectCodexCards = (now) => {
		const cards = [];
		const articleNodes = document.querySelectorAll("article");
		for (const articleNode of articleNodes) {
			const fullText = normalizeWhitespace(articleNode.textContent ?? "");
			if (/remaining/iu.test(fullText) === false) continue;
			const trackContainerElement = resolveCodexTrackContainer(articleNode);
			if (trackContainerElement === null) continue;
			const headerText = normalizeWhitespace(articleNode.querySelector("header")?.textContent ?? "");
			const label = deriveCodexCardLabel(headerText, fullText);
			const apiWindow = findCodexRateLimitWindow(headerText);
			if (apiWindow !== null) {
				cards.push({
					fullText,
					label,
					trackContainerElement,
					resetAt: apiWindow.resetAt,
					durationMs: apiWindow.durationMs,
					fillMeaning: "remaining"
				});
				continue;
			}
			const { resetAt, durationMs } = parseResetInfo(articleNode, fullText, headerText.length > 0 ? headerText : fullText, now);
			cards.push({
				fullText,
				label,
				trackContainerElement,
				resetAt,
				durationMs,
				fillMeaning: "remaining"
			});
		}
		return cards;
	};
	var resolveClaudeProgressElements = (candidateNode) => {
		const fillNode = candidateNode.querySelector(CLAUDE_FILL_SELECTOR);
		if (fillNode instanceof HTMLElement === false) return null;
		if (validateTrackGeometry(candidateNode.getBoundingClientRect(), fillNode.getBoundingClientRect()) === false) return null;
		const trackContainerNode = candidateNode.parentElement;
		if (trackContainerNode instanceof HTMLElement === false) return null;
		const rowNode = trackContainerNode.parentElement?.parentElement ?? null;
		if (rowNode instanceof HTMLElement === false) return null;
		return {
			trackContainerElement: trackContainerNode,
			rowElement: rowNode
		};
	};
	var CLAUDE_SKIP_PATTERNS = [/\$[\d,.]+\s+spent/iu, /\bdaily\s+included\b/iu];
	var collectClaudeCards = (now) => {
		const cards = [];
		const trackCandidates = document.querySelectorAll(CLAUDE_TRACK_SELECTOR);
		for (const candidateNode of trackCandidates) {
			if (candidateNode instanceof HTMLElement === false) continue;
			const resolved = resolveClaudeProgressElements(candidateNode);
			if (resolved === null) continue;
			const rowText = normalizeWhitespace(resolved.rowElement.textContent ?? "");
			if (CLAUDE_SKIP_PATTERNS.some((pattern) => pattern.test(rowText)) === true) continue;
			const sectionHeadingText = normalizeWhitespace(candidateNode.closest("section")?.querySelector("h1, h2, h3, h4")?.textContent ?? "");
			const { resetAt, durationMs } = parseResetInfo(resolved.rowElement, rowText, `${sectionHeadingText} ${rowText}`, now);
			cards.push({
				fullText: rowText,
				label: deriveCardLabel(rowText),
				trackContainerElement: resolved.trackContainerElement,
				resetAt,
				durationMs,
				fillMeaning: "used"
			});
		}
		return cards;
	};
	var collectKimiCards = (now) => {
		const cards = [];
		const cardNodes = document.querySelectorAll(KIMI_CARD_SELECTOR);
		for (const cardNode of cardNodes) {
			if (cardNode instanceof HTMLElement === false) continue;
			const barNode = cardNode.querySelector(KIMI_BAR_SELECTOR);
			const fillNode = cardNode.querySelector(KIMI_FILL_SELECTOR);
			if (barNode instanceof HTMLElement === false || fillNode instanceof HTMLElement === false) continue;
			if (validateTrackGeometry(barNode.getBoundingClientRect(), fillNode.getBoundingClientRect()) === false) continue;
			const fullText = normalizeWhitespace(cardNode.textContent ?? "");
			const { resetAt, durationMs } = parseResetInfo(cardNode, fullText, fullText, now);
			cards.push({
				fullText,
				label: deriveCardLabel(fullText),
				trackContainerElement: barNode,
				resetAt,
				durationMs,
				fillMeaning: "used"
			});
		}
		return cards;
	};
	var buildResetByDurationLookup = (cards) => {
		const lookup = new Map();
		for (const card of cards) {
			if (card.durationMs === null || card.resetAt === null) continue;
			if (lookup.has(card.durationMs) === false) lookup.set(card.durationMs, card.resetAt);
		}
		return lookup;
	};
	var findWeeklyReset = (cards) => {
		for (const card of cards) if (/weekly/iu.test(card.fullText) === true && card.resetAt !== null) return card.resetAt;
		return null;
	};
	var resolveMissingResetInformation = (cards) => {
		const resetByDurationLookup = buildResetByDurationLookup(cards);
		const weeklyReset = findWeeklyReset(cards);
		for (const card of cards) {
			if (card.resetAt !== null) continue;
			if (card.durationMs !== null) {
				const fallbackReset = resetByDurationLookup.get(card.durationMs);
				if (fallbackReset !== void 0) {
					card.resetAt = fallbackReset;
					continue;
				}
			}
			if (/code\s*review/iu.test(card.fullText) === true && weeklyReset !== null) {
				card.durationMs = ONE_WEEK_MS;
				card.resetAt = weeklyReset;
			}
		}
	};
	var collectUsageCards = (now) => {
		const hostname = globalThis.location.hostname;
		if (hostname === "claude.ai") return collectClaudeCards(now);
		if (hostname === "www.kimi.com") return collectKimiCards(now);
		return collectCodexCards(now);
	};
	var DIVIDER_CLASS = "ai-usage-pace-divider";
	var DIVIDER_BAR_CLASS = "ai-usage-pace-divider-bar";
	var UPDATE_INTERVAL_MS = 3e4;
	var DIVIDER_COLOR = "rgb(249, 115, 22)";
	var DIVIDER_HIT_AREA_WIDTH = "12px";
	var DIVIDER_BAR_WIDTH = "2px";
	var computeTargetRemainingRatio = (card, now) => {
		if (card.resetAt === null || card.durationMs === null || card.durationMs <= 0) return null;
		const resetTimeMs = card.resetAt.getTime();
		if (Number.isFinite(resetTimeMs) === false) return null;
		const cycleStartMs = resetTimeMs - card.durationMs;
		return clamp(1 - clamp(now.getTime() - cycleStartMs, 0, card.durationMs) / card.durationMs, 0, 1);
	};
	var computeDividerLeftPercent = (card, targetRemainingRatio) => {
		if (card.fillMeaning === "used") return (1 - targetRemainingRatio) * 100;
		return targetRemainingRatio * 100;
	};
	var ensureDividerElement = (trackContainer) => {
		const existingDivider = trackContainer.querySelector(`.${DIVIDER_CLASS}`);
		if (existingDivider !== null) return existingDivider;
		const dividerElement = document.createElement("div");
		dividerElement.className = DIVIDER_CLASS;
		trackContainer.append(dividerElement);
		return dividerElement;
	};
	var ensureBarElement = (dividerElement) => {
		const existingBar = dividerElement.querySelector(`.${DIVIDER_BAR_CLASS}`);
		if (existingBar !== null) return existingBar;
		const barElement = document.createElement("div");
		barElement.className = DIVIDER_BAR_CLASS;
		dividerElement.append(barElement);
		return barElement;
	};
	var removeDividerElement = (trackContainer) => {
		const dividerElement = trackContainer.querySelector(`.${DIVIDER_CLASS}`);
		if (dividerElement !== null) dividerElement.remove();
	};
	var buildDividerTooltip = (targetRemainingRatio) => {
		return `Pace marker: expected ${(targetRemainingRatio * 100).toFixed(1)}% remaining`;
	};
	var applyDividerStyles = (dividerElement, leftPercent) => {
		dividerElement.style.position = "absolute";
		dividerElement.style.top = "-2px";
		dividerElement.style.bottom = "-2px";
		dividerElement.style.left = `${leftPercent.toFixed(4)}%`;
		dividerElement.style.width = DIVIDER_HIT_AREA_WIDTH;
		dividerElement.style.transform = "translateX(-50%)";
		dividerElement.style.backgroundColor = "transparent";
		dividerElement.style.cursor = "help";
		dividerElement.style.zIndex = "5";
	};
	var applyBarStyles = (barElement) => {
		barElement.style.position = "absolute";
		barElement.style.top = "0";
		barElement.style.bottom = "0";
		barElement.style.left = "50%";
		barElement.style.width = DIVIDER_BAR_WIDTH;
		barElement.style.transform = "translateX(-50%)";
		barElement.style.borderRadius = "9999px";
		barElement.style.pointerEvents = "none";
		barElement.style.backgroundColor = DIVIDER_COLOR;
		barElement.style.boxShadow = "0 0 0 1px rgba(255, 255, 255, 0.7)";
	};
	var updateDividerElement = (card, targetRemainingRatio) => {
		const trackContainer = card.trackContainerElement;
		if (getComputedStyle(trackContainer).position === "static") trackContainer.style.position = "relative";
		const leftPercent = computeDividerLeftPercent(card, targetRemainingRatio);
		const dividerElement = ensureDividerElement(trackContainer);
		applyDividerStyles(dividerElement, leftPercent);
		applyBarStyles(ensureBarElement(dividerElement));
		dividerElement.title = buildDividerTooltip(targetRemainingRatio);
	};
	var renderPaceDividers = () => {
		const now = new Date();
		const cards = collectUsageCards(now);
		if (globalThis.location.hostname !== "chatgpt.com") resolveMissingResetInformation(cards);
		const disabledLabels = readDisabledLabels();
		const paceableLabels = [];
		for (const card of cards) {
			const targetRemainingRatio = computeTargetRemainingRatio(card, now);
			if (targetRemainingRatio === null) {
				removeDividerElement(card.trackContainerElement);
				continue;
			}
			paceableLabels.push(card.label);
			if (disabledLabels.includes(card.label) === true) {
				removeDividerElement(card.trackContainerElement);
				continue;
			}
			updateDividerElement(card, targetRemainingRatio);
		}
		syncSettingsMenu(paceableLabels, disabledLabels, scheduleRender);
	};
	var renderScheduled = false;
	var scheduleRender = () => {
		if (renderScheduled === true) return;
		renderScheduled = true;
		globalThis.requestAnimationFrame(() => {
			renderScheduled = false;
			renderPaceDividers();
		});
	};
	var setupAutoRefresh = () => {
		new MutationObserver(scheduleRender).observe(document.body, {
			childList: true,
			subtree: true
		});
		globalThis.setInterval(scheduleRender, UPDATE_INTERVAL_MS);
		globalThis.addEventListener("resize", scheduleRender);
		document.addEventListener("visibilitychange", () => {
			if (document.visibilityState === "visible") scheduleRender();
		});
	};
	var bootstrap = () => {
		const globalWindow = globalThis;
		if (globalWindow.__aiUsageDividerInitialized__ === true) return;
		globalWindow.__aiUsageDividerInitialized__ = true;
		const init = () => {
			scheduleRender();
			globalThis.setTimeout(() => {
				scheduleRender();
			}, 300);
			globalThis.setTimeout(() => {
				scheduleRender();
			}, 2e3);
			setupAutoRefresh();
		};
		if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", init);
		else init();
	};
	bootstrap();
})();