Franja sobre el input con el % usado de la sesión de 5 horas y de la semana, el tiempo al reset y un semáforo de ritmo

Un mod para Claude Code que muestra, arriba del input, cuánto llevás usado de la sesión de 5 horas y de la semana, cuánto deberías llevar a esta altura y cuánto falta para el reset. Isaac te avisa si estás quemando tokens.

| Elemento | Qué es |
|---|---|
| Isaac | Reacciona al ritmo de la sesión de 5 horas: pulgar arriba (verde), tranquilo (amarillo), gritando (rojo), tirado en el piso (100% usado) |
Anillo + 34% | Usado de la sesión de 5 horas. El anillo se llena con el % y toma el color del semáforo: verde si vas igual o por debajo de lo esperado, amarillo hasta yellowMargin puntos por encima, rojo más allá. Durante los primeros graceMinutes nunca marca rojo |
Diana + 20% | Lo que correspondería haber usado según el tiempo transcurrido |
Reloj + 2h 41m | Tiempo hasta el reset |
7d + anillo + 48% | Usado de la semana, con el mismo semáforo. El tooltip dice cuándo se reinicia la semana y, si vas por encima, cuánto aflojar |
7d + diana + 55% | Lo que correspondería haber usado de la semana, repartido parejo en los 7 días (24/7) |
| Flecha circular | La ventana se reinició; se actualiza con la próxima respuesta |
Al pasar el mouse sobre un valor aparece al instante un tooltip con su descripción. Cuando vas por encima del ritmo, el tooltip del anillo dice cuánto tiempo aflojar para volver a verde. La terminal muestra la misma información en una línea con emojis, sin tooltips:
🟢 34% 🎯 20% ⏳ 2h 41m · 7d 🟢 48% 🎯 55%
Cuándo se actualiza: el % usado llega con las respuestas de la API en la sesión (Claude Code avisa al terminar cada turno o cuando sube un punto entero). El tiempo restante y el % esperado se recalculan cada 60 segundos.
Se oculta si usás una API key en lugar de una suscripción, o si todavía no hubo ninguna respuesta en la sesión.
El mod se instala desde el marketplace gontzalo. En Claude Code:
/plugin marketplace add diazgonza17/claude-plugins
/plugin install usage-pace@gontzalo
O desde la terminal:
claude plugin marketplace add diazgonza17/claude-plugins
claude plugin install usage-pace@gontzalo
También podés pedírselo a Claude: "Instalá el mod usage-pace de github.com/diazgonza17/usage-pace". Claude sigue las instrucciones de la sección siguiente y lo deja configurado en tu idioma.
El widget aparece arriba del input en la próxima sesión, después de la primera respuesta.
Si sos Claude y te pidieron instalar este mod:
es o en) según el idioma en el que el usuario te escribe en esta conversación. No uses el idioma del sistema. Si no está claro, preguntale.bash claude plugin marketplace add diazgonza17/claude-plugins claude plugin install usage-pace@gontzalo --config language=<es|en> ``/plugin configure usage-pace@gontzalo.Con /plugin configure usage-pace@gontzalo en Claude Code, o pidiéndoselo a Claude:
| Opción | Por defecto | Qué hace |
|---|---|---|
language | es | Idioma de los tooltips: es o en |
yellowMargin | 10 | Puntos por encima de lo esperado que se toleran en amarillo antes de pasar a rojo |
showWeekly | true | Muestra el grupo 7d con el ritmo semanal. En false queda solo la sesión de 5 horas |
graceMinutes | 15 | Minutos al inicio de la sesión de 5 horas en los que nunca se marca rojo |
weeklyGraceHours | 8 | Horas al inicio de la semana en las que nunca se marca rojo. Por defecto es la misma proporción que los 15 minutos de la sesión |
/plugin marketplace update gontzalo
claude plugin validate .
claude plugin test .
python3 scripts/build-preview.py
python3 scripts/build-demo.py
python3 scripts/build-stage.py
python3 scripts/build-social.py
validate revisa el mod y lista todo lo que llama.test corre los tests de la lógica, los textos y el dibujo.build-preview.py dibuja con el código del plugin cada estado de ejemplo y regenera docs/preview.html, con la versión desktop en los dos idiomas (con tooltips al pasar el mouse) y la de terminal, y la captura docs/screenshot.png si tenés Chrome instalado.build-demo.py arma docs/demo.gif: los cuatro estados con sus tooltips (los de 5 horas y los semanales), en inglés, para compartir. Necesita Chrome y Pillow.build-stage.py genera docs/stage.html, una página para grabar la pantalla: alterna los cuatro estados cada 2 segundos sobre una réplica del input de Claude Code, con los tooltips reales al pasar el mouse. Espacio pausa, ← → cambian de estado, L cambia el idioma.build-social.py genera docs/social-preview.png (1280×640), la imagen que muestra GitHub al compartir el link del repo. Se sube en Settings → Social preview.Regenerá estos archivos cuando cambies el dibujo o los textos.
Publicar una versión: subí version en .claude-plugin/plugin.json y hacé push a main. A los usuarios solo les llega cuando cambia la versión, así que los commits intermedios no se publican.
Los textos de cada idioma están en hooks/i18n.ts.
Los sprites de Isaac están en assets/isaac/. El módulo no lee archivos y el desktop descarta imágenes embebidas, así que hooks/sprites.ts los guarda convertidos a vectores. Si cambiás un PNG, regeneralo con python3 scripts/build-sprites.py (necesita Pillow).
hooks/register.tsx 91 lines1import { atom, read, update } from 'claude-code'
2import type { EngineInterface, Register, SessionRateLimit } from 'claude-code'
3
4import type { Reading } from '../types'
5import { toLanguage } from './i18n'
6import { WEEK_MS, computePace } from './pace'
7import { PILL_HEIGHT, itemsFor, pillSvg, textLabel } from './pill'
8
9const fiveHour = atom({ plugin: 'usage-pace', key: 'fiveHour' } as const, null)
10const sevenDay = atom({ plugin: 'usage-pace', key: 'sevenDay' } as const, null)
11
12const toReading = (limits: readonly SessionRateLimit[], kind: string): Reading | null => {
13 const window = limits.find(limit => limit.kind === kind)
14 if (!window?.resetsAt) {
15 return null
16 }
17
18 return { percentUsed: window.percentUsed, resetsAt: window.resetsAt }
19}
20
21const saveReadings = async ($: EngineInterface, limits: readonly SessionRateLimit[]) => {
22 const reading = toReading(limits, 'five_hour')
23 const weekReading = toReading(limits, 'seven_day')
24 await update($, fiveHour, () => reading)
25 await update($, sevenDay, () => weekReading)
26}
27
28export const register: Register = (on, options) => {
29 const config = {
30 yellowMargin: Number(options.yellowMargin ?? 10),
31 graceMinutes: Number(options.graceMinutes ?? 15),
32 }
33 const weekConfig = { ...config, graceMinutes: Number(options.weeklyGraceHours ?? 8) * 60 }
34 const showWeekly = Boolean(options.showWeekly ?? true)
35 const language = toLanguage(options.language)
36
37 on('session.start', async ($, e, next) => {
38 const { rateLimits } = await $.session.usage()
39 if (rateLimits.length > 0) {
40 await saveReadings($, rateLimits)
41 }
42 // Keeps the countdown and the expected % fresh between measurements.
43 $.clock.every(60_000, () => $.ui.invalidate('ui.render'))
44
45 return next(e)
46 })
47
48 on('session.measure', async ($, e, next) => {
49 if (e.changed.includes('rateLimits')) {
50 await saveReadings($, e.rateLimits)
51 }
52
53 return next(e)
54 })
55
56 // One row above the prompt; on desktop an SVG so each value carries a native tooltip.
57 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
58 const reading = await read($, fiveHour)
59 const weekReading = showWeekly ? await read($, sevenDay) : null
60 if (e.props.hasSurvey || (reading === null && weekReading === null)) {
61 return next(e)
62 }
63
64 const now = await $.clock.now()
65 const pace = reading && computePace(reading.percentUsed, Date.parse(reading.resetsAt), now, config)
66 const weekPace =
67 weekReading && computePace(weekReading.percentUsed, Date.parse(weekReading.resetsAt), now, weekConfig, WEEK_MS)
68 const items = itemsFor(pace, language, weekPace)
69
70 if (e.surface === 'desktop') {
71 const { Box, Svg } = $.ui.resolve(e)
72 const { source, width } = pillSvg(items)
73 const alt = items.map(item => item.tip).join('. ')
74
75 return (
76 <Box flexDirection="row">
77 <Svg source={source} alt={alt} width={width} height={PILL_HEIGHT} isInteractive />
78 </Box>
79 )
80 }
81
82 const { Box, Text } = $.ui.resolve(e)
83
84 return (
85 <Box flexDirection="row">
86 <Text dimColor>{textLabel(items)}</Text>
87 </Box>
88 )
89 })
90}
91hooks/i18n.ts 48 lines1export type Language = 'es' | 'en'
2
3export type Strings = {
4 used: string
5 doingGood: string
6 lookOut: (wait: string) => string
7 burning: (wait: string) => string
8 limit: string
9 expected: string
10 reset: string
11 stale: string
12 weekUsed: (reset: string) => string
13 weekExpected: string
14 weekStale: string
15}
16
17export const STRINGS: Record<Language, Strings> = {
18 es: {
19 used: 'Usado de la sesión de 5 horas',
20 doingGood: 'venís bien',
21 lookOut: wait => `cuidado (aflojá por ${wait})`,
22 burning: wait => `estás quemando tokens! (pisá el freno por ${wait})`,
23 limit: 'límite alcanzado',
24 expected: 'Lo que correspondería haber usado a esta altura',
25 reset: 'Tiempo hasta que se reinicie la sesión',
26 stale: 'La sesión se reinició; se actualiza con la próxima respuesta',
27 weekUsed: reset => `Usado de la semana, se reinicia en ${reset}`,
28 weekExpected: 'Lo que correspondería haber usado esta semana',
29 weekStale: 'La semana se reinició; se actualiza con la próxima respuesta',
30 },
31 en: {
32 used: 'Used of the 5-hour session',
33 doingGood: 'doing good',
34 lookOut: wait => `look out (slow down for ${wait})`,
35 burning: wait => `you're burning tokens! (press the brakes for ${wait})`,
36 limit: 'limit reached',
37 expected: 'What you should have used by now',
38 reset: 'Time until the session resets',
39 stale: 'The session reset; it updates with the next response',
40 weekUsed: reset => `Used this week, resets in ${reset}`,
41 weekExpected: 'What you should have used this week by now',
42 weekStale: 'The week reset; it updates with the next response',
43 },
44}
45
46// The `language` option, Spanish when unset or unknown.
47export const toLanguage = (value: unknown): Language => (value === 'en' ? 'en' : 'es')
48hooks/pace.ts 75 lines1export const WINDOW_MS = 5 * 60 * 60 * 1000
2export const WEEK_MS = 7 * 24 * 60 * 60 * 1000
3
4export type Light = 'green' | 'yellow' | 'red'
5
6export type Pace =
7 | { kind: 'stale' }
8 | {
9 kind: 'live'
10 light: Light
11 used: number
12 expected: number
13 remainingMs: number
14 /** How long without using Claude until the pace is green again. */
15 slowDownMs: number
16 }
17
18export type PaceConfig = { yellowMargin: number; graceMinutes: number }
19
20// Expected usage grows linearly over the window: with 4h left of 5h, ~20% is on pace.
21export const computePace = (
22 used: number,
23 resetsAtMs: number,
24 nowMs: number,
25 { yellowMargin, graceMinutes }: PaceConfig,
26 windowMs = WINDOW_MS,
27): Pace => {
28 const remainingMs = resetsAtMs - nowMs
29 if (remainingMs <= 0) {
30 return { kind: 'stale' }
31 }
32
33 const elapsedMs = Math.max(0, windowMs - remainingMs)
34 const expected = (elapsedMs / windowMs) * 100
35 const delta = used - expected
36 const isInGrace = elapsedMs < graceMinutes * 60 * 1000
37
38 let light: Light = 'green'
39 if (used >= 100) {
40 light = 'red'
41 } else if (delta > yellowMargin && !isInGrace) {
42 light = 'red'
43 } else if (delta > 0) {
44 light = 'yellow'
45 }
46
47 // Paused, `used` stays put while `expected` keeps growing: the gap closes at
48 // 100 points per window.
49 const slowDownMs = Math.max(0, (delta / 100) * windowMs)
50
51 return { kind: 'live', light, used, expected, remainingMs, slowDownMs }
52}
53
54export const formatRemaining = (ms: number): string => {
55 const minutes = Math.ceil(ms / 60000)
56 const days = Math.floor(minutes / (24 * 60))
57 const hours = Math.floor(minutes / 60) % 24
58 const rest = minutes % 60
59 if (days > 0) return `${days}d ${hours}h`
60
61 return hours > 0 ? `${hours}h ${String(rest).padStart(2, '0')}m` : `${rest}m`
62}
63
64// "15 min", "1h", "1h 15min", "1d 3h": the wait a tip suggests.
65export const formatWait = (ms: number): string => {
66 const minutes = Math.max(1, Math.ceil(ms / 60000))
67 const days = Math.floor(minutes / (24 * 60))
68 const hours = Math.floor(minutes / 60) % 24
69 const rest = minutes % 60
70 if (days > 0) return hours === 0 ? `${days}d` : `${days}d ${hours}h`
71 if (hours === 0) return `${rest} min`
72
73 return rest === 0 ? `${hours}h` : `${hours}h ${rest}min`
74}
75hooks/pill.ts 235 lines1import { STRINGS } from './i18n'
2import type { Language, Strings } from './i18n'
3import { formatRemaining, formatWait } from './pace'
4import type { Light, Pace } from './pace'
5import { SPRITES } from './sprites'
6
7export type Icon = 'pace' | 'target' | 'clock' | 'refresh'
8
9// `prefix` names the window a group belongs to ("7d"); the 5-hour group has
10// Isaac instead.
11export type Item = { icon: Icon; label: string; tip: string; light?: Light; used?: number; prefix?: string }
12
13// The second half of the progress tip; off pace, how long to ease off to be
14// green again (always before the reset while under 100%).
15const rhythm = (pace: Extract<Pace, { kind: 'live' }>, text: Strings): string => {
16 if (pace.used >= 100) return text.limit
17 if (pace.light === 'green') return text.doingGood
18
19 const wait = formatWait(pace.slowDownMs)
20
21 return pace.light === 'yellow' ? text.lookOut(wait) : text.burning(wait)
22}
23
24const WEEK_PREFIX = '7d'
25
26// The 5-hour group (Isaac, ring, expected, reset) and, when there is a weekly
27// reading, the weekly one (ring and expected; its reset is in the ring's tip).
28export const itemsFor = (pace: Pace | null, language: Language, weekPace: Pace | null = null): Item[] => {
29 const text = STRINGS[language]
30 const items: Item[] = []
31
32 if (pace?.kind === 'stale') {
33 items.push({ icon: 'refresh', label: '', tip: text.stale })
34 } else if (pace) {
35 items.push(
36 {
37 icon: 'pace',
38 label: `${Math.round(pace.used)}%`,
39 tip: `${text.used} · ${rhythm(pace, text)}`,
40 light: pace.light,
41 used: pace.used,
42 },
43 { icon: 'target', label: `${Math.round(pace.expected)}%`, tip: text.expected },
44 { icon: 'clock', label: formatRemaining(pace.remainingMs), tip: text.reset },
45 )
46 }
47
48 if (weekPace?.kind === 'stale') {
49 items.push({ icon: 'refresh', label: '', tip: text.weekStale, prefix: WEEK_PREFIX })
50 } else if (weekPace) {
51 items.push(
52 {
53 icon: 'pace',
54 label: `${Math.round(weekPace.used)}%`,
55 tip: `${text.weekUsed(formatRemaining(weekPace.remainingMs))} · ${rhythm(weekPace, text)}`,
56 light: weekPace.light,
57 used: weekPace.used,
58 prefix: WEEK_PREFIX,
59 },
60 { icon: 'target', label: `${Math.round(weekPace.expected)}%`, tip: text.weekExpected },
61 )
62 }
63
64 return items
65}
66
67const EMOJI: Record<Light, string> = { green: '🟢', yellow: '🟡', red: '🔴' }
68
69// The terminal draws text only: each icon as its emoji, the weekly group after a dot.
70export const textLabel = (items: Item[]): string =>
71 items
72 .map((item, index) => {
73 const emoji = item.icon === 'pace' ? EMOJI[item.light ?? 'green'] : { target: '🎯', clock: '⏳', refresh: '🔄' }[item.icon]
74 const value = item.label === '' ? emoji : `${emoji} ${item.label}`
75 if (!item.prefix) return value
76
77 return `${index > 0 ? '· ' : ''}${item.prefix} ${value}`
78 })
79 .join(' ')
80
81const FILL: Record<Light, string> = { green: '#22c55e', yellow: '#eab308', red: '#ef4444' }
82
83const HEIGHT = 20
84const MID = HEIGHT / 2
85const ICON = 14
86const ICON_GAP = 5
87const ITEM_GAP = 14
88const LABEL_SIZE = 13
89const TIP_SIZE = 12
90const TIP_PAD = 8
91const FONT = 'system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif'
92
93// Advance widths of the macOS system font at 12px, measured in the desktop's
94// engine; other sizes scale. Labels and tips also get `textLength`, so a
95// slightly different font keeps the layout exact.
96const WIDTHS_12: Record<string, number> = {
97 ...Object.fromEntries('0123456789'.split('').map((d, i) => [d, [7.56, 5.57, 7.24, 7.52, 7.73, 7.42, 7.64, 6.84, 7.66, 7.64][i]])),
98 a: 6.63, b: 7.38, c: 6.72, d: 7.38, e: 6.86, f: 4.34, g: 7.31, h: 7.06, i: 2.97, j: 2.96, k: 6.52, l: 3.04,
99 m: 10.45, n: 7.01, o: 7.09, p: 7.33, q: 7.31, r: 4.57, s: 6.28, t: 4.36, u: 7.01, v: 6.51, w: 9.3, x: 6.3,
100 y: 6.52, z: 6.47, A: 8.09, L: 6.82, T: 7.61, U: 8.85, W: 11.62, "'": 2.6, '-': 5.66, '!': 3.73, ' ': 3.2, '.': 3.56, ',': 3.56, ';': 3.56, ':': 3.56,
101 '%': 11.11, '·': 3.56, '(': 4.59, ')': 4.59, '+': 7.56, á: 6.63, é: 6.86, í: 2.97, ó: 7.09, ú: 7.01, ñ: 7.01,
102}
103
104const textWidth = (text: string, size: number) =>
105 Array.from(text).reduce((sum, char) => sum + (WIDTHS_12[char] ?? 7), 0) * (size / 12)
106
107const escape = (text: string) =>
108 text.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"')
109
110const iconSvg = (item: Item, x: number): string => {
111 const cx = x + ICON / 2
112 const r = 5.5
113 switch (item.icon) {
114 case 'pace': {
115 const circumference = 2 * Math.PI * r
116 const filled = Math.max(1, (Math.min(100, item.used ?? 0) / 100) * circumference)
117 return (
118 `<circle class="track" cx="${cx}" cy="${MID}" r="${r}" fill="none" stroke-width="2.4"/>` +
119 `<circle cx="${cx}" cy="${MID}" r="${r}" fill="none" stroke="${FILL[item.light ?? 'green']}" stroke-width="2.4" ` +
120 `stroke-linecap="round" stroke-dasharray="${filled.toFixed(2)} ${circumference.toFixed(2)}" transform="rotate(-90 ${cx} ${MID})"/>`
121 )
122 }
123 case 'target':
124 // A target with an arrow stuck in its centre, fletching up and to the right.
125 return (
126 `<circle class="line" cx="${cx}" cy="${MID}" r="${r}" fill="none" stroke-width="1.3"/>` +
127 `<circle class="line" cx="${cx}" cy="${MID}" r="2.5" fill="none" stroke-width="1.3"/>` +
128 `<path class="line" d="M${cx} ${MID}L${cx + 6.2} ${MID - 6.2}" fill="none" stroke-width="1.4" stroke-linecap="round"/>` +
129 `<path class="line" d="M${cx + 4.4} ${MID - 7}l1.8 0.8l0.8 1.8M${cx + 3.2} ${MID - 5.8}l1.8 0.8l0.8 1.8" fill="none" stroke-width="1.1" stroke-linecap="round" stroke-linejoin="round"/>` +
130 `<circle class="dot" cx="${cx}" cy="${MID}" r="1.1"/>`
131 )
132 case 'clock':
133 return (
134 `<circle class="line" cx="${cx}" cy="${MID}" r="${r}" fill="none" stroke-width="1.3"/>` +
135 `<path class="line" d="M${cx} ${MID - 3.2}V${MID}l2.3 1.4" fill="none" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>`
136 )
137 case 'refresh':
138 return (
139 `<path class="line" d="M${cx + 4.6} ${MID - 2.4}A5 5 0 1 0 ${cx + 5} ${MID + 0.6}" fill="none" stroke-width="1.3" stroke-linecap="round"/>` +
140 `<path class="line" d="M${cx + 4.9} ${MID - 5.2}V${MID - 2.2}H${cx + 1.9}" fill="none" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round"/>`
141 )
142 }
143}
144
145const ISAAC_GAP = 5
146const PREFIX_GAP = 4
147const SEPARATOR_GAP = 8
148
149// Isaac reacts to the pace: thumbs up, idle, screaming, or flat on the floor at
150// the limit. Drawn as vector pixels (the desktop strips embedded images) at half
151// their native size, so a 2x screen shows each sprite pixel crisp.
152const isaacSvg = (item: Item, x: number): { markup: string; width: number } => {
153 const sprite = (item.used ?? 0) >= 100 ? SPRITES.limit : SPRITES[item.light ?? 'green']
154 const width = sprite.width / 2
155 const y = MID - sprite.height / 4
156 const paths = sprite.paths.map(([fill, d]) => `<path fill="${fill}" d="${d}"/>`).join('')
157 const markup = `<g transform="translate(${x} ${y}) scale(0.5)" shape-rendering="crispEdges">${paths}</g>`
158
159 return { markup, width }
160}
161
162const STYLE = [
163 ':root{color-scheme:light dark;background:transparent}',
164 `text{font-family:${FONT}}`,
165 '.label{font-size:13px;fill:#6b6b6b}.prefix{font-weight:600}.line{stroke:#6b6b6b}.dot{fill:#6b6b6b}.track{stroke:#d9d9d9}',
166 '.item{cursor:default}.item:hover .label{fill:#1a1a1a}.item:hover .line{stroke:#1a1a1a}.item:hover .dot{fill:#1a1a1a}',
167 '.tip{opacity:0;pointer-events:none;transition:opacity 80ms}',
168 '.tip rect,.tip path{fill:#fff;stroke:#d4d4d4}.tip text{font-size:12px;fill:#1a1a1a}',
169 '@media (prefers-color-scheme:dark){',
170 '.label{fill:#9b9b9b}.line{stroke:#9b9b9b}.dot{fill:#9b9b9b}.track{stroke:#3d3d3d}',
171 '.item:hover .label{fill:#ececec}.item:hover .line{stroke:#ececec}.item:hover .dot{fill:#ececec}',
172 '.tip rect,.tip path{fill:#2b2b2b;stroke:#454545}.tip text{fill:#ececec}}',
173].join('')
174
175// One SVG for the desktop. Each value is a hover group; its tip is a sibling
176// shown by CSS the moment the pointer is on it, to the right of the values
177// (the frame is one row tall, so nothing can float above it).
178export const pillSvg = (items: Item[]): { source: string; width: number } => {
179 let x = 1
180 const groups: string[] = []
181 for (const [index, item] of items.entries()) {
182 // A thin rule between the 5-hour group and the weekly one.
183 if (item.prefix && index > 0) {
184 const ruleX = x - ITEM_GAP / 2 + SEPARATOR_GAP / 2
185 groups.push(`<path class="track" d="M${ruleX} 4V${HEIGHT - 4}" stroke-width="1"/>`)
186 x += SEPARATOR_GAP
187 }
188 const start = x
189 const isaac = item.icon === 'pace' && !item.prefix ? isaacSvg(item, x) : null
190 if (isaac) {
191 x += isaac.width + ISAAC_GAP
192 }
193 let prefix = ''
194 if (item.prefix) {
195 const prefixWidth = textWidth(item.prefix, LABEL_SIZE)
196 prefix = `<text class="label prefix" x="${x}" y="${MID}" dominant-baseline="central" textLength="${prefixWidth.toFixed(2)}" lengthAdjust="spacing">${escape(item.prefix)}</text>`
197 x += prefixWidth + PREFIX_GAP
198 }
199 const labelWidth = item.label === '' ? 0 : textWidth(item.label, LABEL_SIZE)
200 const label =
201 labelWidth > 0
202 ? `<text class="label" x="${x + ICON + ICON_GAP}" y="${MID}" dominant-baseline="central" textLength="${labelWidth.toFixed(2)}" lengthAdjust="spacing">${escape(item.label)}</text>`
203 : ''
204 const end = x + ICON + (labelWidth > 0 ? ICON_GAP + labelWidth : 0)
205 groups.push(
206 `<g class="item" id="i${index}"><rect x="${start - 3}" y="0" width="${end - start + 6}" height="${HEIGHT}" fill="transparent"/>${isaac?.markup ?? ''}${prefix}${iconSvg(item, x)}${label}</g>`,
207 )
208 x = end + ITEM_GAP
209 }
210
211 const tipX = x - ITEM_GAP + 10
212 let widest = 0
213 const tips = items.map((item, index) => {
214 const textW = textWidth(item.tip, TIP_SIZE)
215 const boxW = textW + TIP_PAD * 2
216 widest = Math.max(widest, boxW)
217 const caret = `<path d="M${tipX + 0.5} ${MID - 4}L${tipX - 4} ${MID}L${tipX + 0.5} ${MID + 4}" stroke-width="1"/>`
218 return (
219 `<g class="tip" id="t${index}">` +
220 `<rect x="${tipX}" y="1" width="${boxW.toFixed(2)}" height="${HEIGHT - 2}" rx="5" stroke-width="1"/>${caret}` +
221 `<text x="${tipX + TIP_PAD}" y="${MID}" dominant-baseline="central" textLength="${textW.toFixed(2)}" lengthAdjust="spacing">${escape(item.tip)}</text>` +
222 `</g>`
223 )
224 })
225
226 const reveal = items.map((_, index) => `#i${index}:hover~#t${index}`).join(',')
227 const width = Math.ceil(tipX + widest + 2)
228 const style = `${STYLE}${reveal}{opacity:1}`
229 const source = `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${HEIGHT}" viewBox="0 0 ${width} ${HEIGHT}"><style>${style}</style>${groups.join('')}${tips.join('')}</svg>`
230
231 return { source, width }
232}
233
234export const PILL_HEIGHT = HEIGHT
235hooks/sprites.ts 11 lines1// Generated by scripts/build-sprites.py from assets/isaac/*.png. Do not edit.
2
3export type Sprite = { width: number; height: number; paths: readonly (readonly [fill: string, d: string])[] }
4
5export const SPRITES = {
6 green: { width: 34, height: 33, paths: [['#080000', 'M10 0h8v1h-8zM8 1h12v1h-12zM6 2h4v1h-4zM18 2h4v1h-4zM5 3h3v1h-3zM20 3h3v1h-3zM4 4h2v1h-2zM22 4h2v1h-2zM3 5h2v1h-2zM23 5h6v1h-6zM2 6h2v1h-2zM24 6h6v1h-6zM2 7h2v1h-2zM24 7h2v1h-2zM29 7h2v1h-2zM1 8h2v1h-2zM24 8h2v1h-2zM29 8h2v1h-2zM1 9h2v1h-2zM24 9h1v1h-1zM29 9h2v1h-2zM0 10h2v1h-2zM24 10h1v1h-1zM29 10h2v1h-2zM0 11h2v1h-2zM5 11h4v1h-4zM19 11h4v1h-4zM24 11h1v1h-1zM29 11h2v1h-2zM0 12h2v1h-2zM4 12h1v1h-1zM7 12h3v1h-3zM18 12h1v1h-1zM21 12h4v1h-4zM27 12h5v1h-5zM0 13h2v1h-2zM4 13h1v1h-1zM7 13h3v1h-3zM18 13h1v1h-1zM21 13h3v1h-3zM26 13h7v1h-7zM0 14h2v1h-2zM4 14h6v1h-6zM11 14h6v1h-6zM18 14h6v1h-6zM26 14h1v1h-1zM31 14h2v1h-2zM1 15h2v1h-2zM11 15h1v1h-1zM16 15h1v1h-1zM22 15h2v1h-2zM25 15h1v1h-1zM32 15h2v1h-2zM1 16h2v1h-2zM11 16h6v1h-6zM21 16h2v1h-2zM25 16h1v1h-1zM32 16h2v1h-2zM2 17h2v1h-2zM12 17h4v1h-4zM20 17h3v1h-3zM32 17h2v1h-2zM2 18h2v1h-2zM20 18h1v1h-1zM22 18h1v1h-1zM32 18h2v1h-2zM3 19h2v1h-2zM19 19h2v1h-2zM32 19h2v1h-2zM4 20h2v1h-2zM19 20h1v1h-1zM32 20h2v1h-2zM5 21h3v1h-3zM19 21h1v1h-1zM23 21h1v1h-1zM31 21h3v1h-3zM6 22h4v1h-4zM18 22h2v1h-2zM24 22h2v1h-2zM30 22h3v1h-3zM7 23h13v1h-13zM25 23h7v1h-7zM7 24h2v1h-2zM10 24h11v1h-11zM24 24h7v1h-7zM6 25h2v1h-2zM19 25h7v1h-7zM6 26h2v1h-2zM10 26h1v1h-1zM19 26h6v1h-6zM6 27h2v1h-2zM10 27h1v1h-1zM19 27h3v1h-3zM7 28h4v1h-4zM14 28h2v1h-2zM19 28h2v1h-2zM8 29h3v1h-3zM14 29h2v1h-2zM19 29h2v1h-2zM9 30h3v1h-3zM14 30h2v1h-2zM18 30h3v1h-3zM10 31h10v1h-10zM11 32h3v1h-3zM16 32h3v1h-3z'], ['#cf9c9b', 'M10 2h8v1h-8zM8 3h1v1h-1zM19 3h1v1h-1zM6 4h1v1h-1zM21 4h1v1h-1zM5 5h1v1h-1zM22 5h1v1h-1zM4 6h1v1h-1zM23 6h1v1h-1zM26 7h1v1h-1zM28 7h1v1h-1zM3 8h1v1h-1zM25 9h1v1h-1zM2 10h1v1h-1zM25 10h1v1h-1zM2 11h1v1h-1zM4 11h1v1h-1zM9 11h1v1h-1zM18 11h1v1h-1zM23 11h1v1h-1zM25 11h1v1h-1zM28 11h1v1h-1zM2 12h1v1h-1zM25 12h2v1h-2zM2 13h2v1h-2zM25 13h1v1h-1zM3 14h1v1h-1zM24 14h2v1h-2zM27 14h1v1h-1zM30 14h1v1h-1zM3 15h1v1h-1zM5 15h4v1h-4zM19 15h3v1h-3zM24 15h1v1h-1zM26 15h1v1h-1zM31 15h1v1h-1zM4 16h1v1h-1zM23 16h2v1h-2zM4 17h1v1h-1zM11 17h1v1h-1zM16 17h1v1h-1zM23 17h2v1h-2zM5 18h1v1h-1zM23 18h3v1h-3zM6 19h1v1h-1zM23 19h4v1h-4zM7 20h1v1h-1zM20 20h1v1h-1zM23 20h4v1h-4zM9 21h1v1h-1zM18 21h1v1h-1zM20 21h2v1h-2zM25 21h5v1h-5zM11 22h6v1h-6zM20 22h2v1h-2zM23 22h1v1h-1zM27 22h2v1h-2zM22 23h2v1h-2zM9 24h1v1h-1zM8 25h1v1h-1zM11 25h8v1h-8zM8 26h1v1h-1zM11 26h1v1h-1zM18 26h1v1h-1zM8 27h1v1h-1zM13 28h1v1h-1zM16 28h1v1h-1zM16 29h1v1h-1zM16 30h1v1h-1z'], ['#e3c6c5', 'M9 3h10v1h-10zM7 4h14v1h-14zM6 5h16v1h-16zM5 6h18v1h-18zM4 7h20v1h-20zM27 7h1v1h-1zM4 8h20v1h-20zM26 8h3v1h-3zM3 9h21v1h-21zM26 9h3v1h-3zM3 10h21v1h-21zM26 10h3v1h-3zM3 11h1v1h-1zM10 11h8v1h-8zM26 11h2v1h-2zM3 12h1v1h-1zM10 12h8v1h-8zM10 13h8v1h-8zM10 14h1v1h-1zM17 14h1v1h-1zM28 14h2v1h-2zM10 15h1v1h-1zM17 15h1v1h-1zM27 15h4v1h-4zM5 16h6v1h-6zM17 16h4v1h-4zM26 16h6v1h-6zM5 17h6v1h-6zM17 17h3v1h-3zM26 17h6v1h-6zM6 18h14v1h-14zM26 18h6v1h-6zM7 19h12v1h-12zM21 19h2v1h-2zM27 19h5v1h-5zM8 20h11v1h-11zM21 20h2v1h-2zM27 20h5v1h-5zM10 21h8v1h-8zM22 21h1v1h-1zM22 22h1v1h-1zM9 25h2v1h-2zM9 26h1v1h-1zM12 26h6v1h-6zM9 27h1v1h-1zM11 27h8v1h-8zM11 28h2v1h-2zM17 28h2v1h-2zM11 29h3v1h-3zM17 29h2v1h-2zM12 30h2v1h-2zM17 30h1v1h-1z'], ['#ffffff', 'M5 12h2v1h-2zM19 12h2v1h-2zM5 13h2v1h-2zM19 13h2v1h-2zM12 15h4v1h-4z'], ['#b97371', 'M24 13h1v1h-1zM2 14h1v1h-1zM4 15h1v1h-1zM9 15h1v1h-1zM18 15h1v1h-1zM3 16h1v1h-1zM25 17h1v1h-1zM4 18h1v1h-1zM21 18h1v1h-1zM5 19h1v1h-1zM6 20h1v1h-1zM8 21h1v1h-1zM24 21h1v1h-1zM30 21h1v1h-1zM10 22h1v1h-1zM17 22h1v1h-1zM26 22h1v1h-1zM29 22h1v1h-1zM20 23h2v1h-2zM24 23h1v1h-1zM21 24h3v1h-3z']] },
7 yellow: { width: 28, height: 33, paths: [['#080000', 'M10 0h8v1h-8zM8 1h12v1h-12zM6 2h4v1h-4zM18 2h4v1h-4zM5 3h3v1h-3zM20 3h3v1h-3zM4 4h2v1h-2zM22 4h2v1h-2zM3 5h2v1h-2zM23 5h2v1h-2zM2 6h2v1h-2zM24 6h2v1h-2zM2 7h2v1h-2zM24 7h2v1h-2zM1 8h2v1h-2zM25 8h2v1h-2zM1 9h2v1h-2zM25 9h2v1h-2zM0 10h2v1h-2zM26 10h2v1h-2zM0 11h2v1h-2zM5 11h4v1h-4zM19 11h4v1h-4zM26 11h2v1h-2zM0 12h2v1h-2zM4 12h1v1h-1zM7 12h3v1h-3zM18 12h1v1h-1zM21 12h3v1h-3zM26 12h2v1h-2zM0 13h2v1h-2zM4 13h1v1h-1zM7 13h3v1h-3zM18 13h1v1h-1zM21 13h3v1h-3zM26 13h2v1h-2zM0 14h2v1h-2zM4 14h6v1h-6zM12 14h4v1h-4zM18 14h6v1h-6zM26 14h2v1h-2zM1 15h2v1h-2zM4 15h6v1h-6zM11 15h6v1h-6zM18 15h6v1h-6zM25 15h2v1h-2zM1 16h2v1h-2zM5 16h4v1h-4zM11 16h1v1h-1zM16 16h1v1h-1zM19 16h4v1h-4zM25 16h2v1h-2zM2 17h2v1h-2zM11 17h6v1h-6zM24 17h2v1h-2zM2 18h2v1h-2zM24 18h2v1h-2zM3 19h2v1h-2zM23 19h2v1h-2zM4 20h2v1h-2zM22 20h2v1h-2zM5 21h3v1h-3zM20 21h3v1h-3zM6 22h4v1h-4zM18 22h4v1h-4zM7 23h14v1h-14zM6 24h2v1h-2zM10 24h8v1h-8zM20 24h2v1h-2zM5 25h2v1h-2zM21 25h2v1h-2zM5 26h2v1h-2zM9 26h1v1h-1zM18 26h1v1h-1zM21 26h2v1h-2zM5 27h2v1h-2zM9 27h1v1h-1zM18 27h1v1h-1zM21 27h2v1h-2zM6 28h4v1h-4zM13 28h2v1h-2zM18 28h4v1h-4zM7 29h3v1h-3zM13 29h2v1h-2zM18 29h3v1h-3zM8 30h3v1h-3zM13 30h2v1h-2zM17 30h3v1h-3zM9 31h10v1h-10zM10 32h3v1h-3zM15 32h3v1h-3z'], ['#cf9c9b', 'M10 2h8v1h-8zM8 3h1v1h-1zM19 3h1v1h-1zM6 4h1v1h-1zM21 4h1v1h-1zM5 5h1v1h-1zM22 5h1v1h-1zM4 6h1v1h-1zM23 6h1v1h-1zM3 8h1v1h-1zM24 8h1v1h-1zM2 10h1v1h-1zM25 10h1v1h-1zM2 11h1v1h-1zM4 11h1v1h-1zM9 11h1v1h-1zM18 11h1v1h-1zM23 11h1v1h-1zM25 11h1v1h-1zM2 12h1v1h-1zM25 12h1v1h-1zM2 13h2v1h-2zM24 13h2v1h-2zM3 14h1v1h-1zM24 14h1v1h-1zM3 15h1v1h-1zM24 15h1v1h-1zM4 16h1v1h-1zM9 16h1v1h-1zM18 16h1v1h-1zM23 16h1v1h-1zM4 17h1v1h-1zM8 17h1v1h-1zM19 17h1v1h-1zM23 17h1v1h-1zM8 20h1v1h-1zM19 20h1v1h-1zM9 21h1v1h-1zM18 21h1v1h-1zM11 22h6v1h-6zM8 24h1v1h-1zM19 24h1v1h-1zM7 25h1v1h-1zM10 25h8v1h-8zM20 25h1v1h-1zM7 26h1v1h-1zM10 26h1v1h-1zM17 26h1v1h-1zM20 26h1v1h-1zM7 27h1v1h-1zM20 27h1v1h-1zM12 28h1v1h-1zM15 28h1v1h-1zM15 29h1v1h-1zM15 30h1v1h-1z'], ['#e3c6c5', 'M9 3h10v1h-10zM7 4h14v1h-14zM6 5h16v1h-16zM5 6h18v1h-18zM4 7h20v1h-20zM4 8h20v1h-20zM3 9h22v1h-22zM3 10h22v1h-22zM3 11h1v1h-1zM10 11h8v1h-8zM24 11h1v1h-1zM3 12h1v1h-1zM10 12h8v1h-8zM24 12h1v1h-1zM10 13h8v1h-8zM10 14h2v1h-2zM16 14h2v1h-2zM10 15h1v1h-1zM17 15h1v1h-1zM10 16h1v1h-1zM17 16h1v1h-1zM9 17h2v1h-2zM17 17h2v1h-2zM8 18h12v1h-12zM8 19h12v1h-12zM9 20h10v1h-10zM10 21h8v1h-8zM9 24h1v1h-1zM18 24h1v1h-1zM8 25h2v1h-2zM18 25h2v1h-2zM8 26h1v1h-1zM11 26h6v1h-6zM19 26h1v1h-1zM8 27h1v1h-1zM10 27h8v1h-8zM19 27h1v1h-1zM10 28h2v1h-2zM16 28h2v1h-2zM10 29h3v1h-3zM16 29h2v1h-2zM11 30h2v1h-2zM16 30h1v1h-1z'], ['#ffffff', 'M5 12h2v1h-2zM19 12h2v1h-2zM5 13h2v1h-2zM19 13h2v1h-2zM12 16h4v1h-4z'], ['#b97371', 'M2 14h1v1h-1zM25 14h1v1h-1zM3 16h1v1h-1zM24 16h1v1h-1zM4 18h1v1h-1zM23 18h1v1h-1zM10 22h1v1h-1zM17 22h1v1h-1z'], ['#9cc8cc', 'M5 17h3v1h-3zM20 17h3v1h-3zM6 19h1v1h-1zM20 19h1v1h-1zM7 20h1v1h-1zM20 20h1v1h-1z'], ['#9cc8cd', 'M5 18h3v1h-3zM20 18h3v1h-3zM21 19h1v1h-1z'], ['#68989c', 'M5 19h1v1h-1z'], ['#9dc9cd', 'M7 19h1v1h-1z'], ['#67979c', 'M22 19h1v1h-1zM6 20h1v1h-1zM19 21h1v1h-1z'], ['#67979b', 'M21 20h1v1h-1zM8 21h1v1h-1z']] },
8 red: { width: 28, height: 33, paths: [['#080000', 'M10 0h8v1h-8zM8 1h12v1h-12zM6 2h4v1h-4zM18 2h4v1h-4zM5 3h3v1h-3zM20 3h3v1h-3zM4 4h2v1h-2zM22 4h2v1h-2zM3 5h2v1h-2zM23 5h2v1h-2zM2 6h2v1h-2zM24 6h2v1h-2zM2 7h2v1h-2zM5 7h4v1h-4zM19 7h4v1h-4zM24 7h2v1h-2zM1 8h2v1h-2zM4 8h1v1h-1zM7 8h3v1h-3zM18 8h1v1h-1zM21 8h3v1h-3zM25 8h2v1h-2zM1 9h2v1h-2zM4 9h1v1h-1zM7 9h3v1h-3zM18 9h1v1h-1zM21 9h3v1h-3zM25 9h2v1h-2zM0 10h2v1h-2zM4 10h6v1h-6zM12 10h4v1h-4zM18 10h6v1h-6zM26 10h2v1h-2zM0 11h2v1h-2zM4 11h6v1h-6zM11 11h1v1h-1zM16 11h1v1h-1zM18 11h6v1h-6zM26 11h2v1h-2zM0 12h2v1h-2zM11 12h1v1h-1zM13 12h2v1h-2zM16 12h1v1h-1zM26 12h2v1h-2zM0 13h2v1h-2zM11 13h6v1h-6zM26 13h2v1h-2zM0 14h2v1h-2zM11 14h6v1h-6zM26 14h2v1h-2zM1 15h6v1h-6zM11 15h6v1h-6zM21 15h6v1h-6zM1 16h7v1h-7zM11 16h2v1h-2zM15 16h2v1h-2zM20 16h7v1h-7zM0 17h3v1h-3zM6 17h3v1h-3zM12 17h4v1h-4zM19 17h3v1h-3zM25 17h3v1h-3zM0 18h2v1h-2zM7 18h2v1h-2zM19 18h2v1h-2zM26 18h2v1h-2zM0 19h2v1h-2zM7 19h2v1h-2zM19 19h2v1h-2zM26 19h2v1h-2zM0 20h2v1h-2zM7 20h2v1h-2zM19 20h2v1h-2zM26 20h2v1h-2zM0 21h2v1h-2zM7 21h2v1h-2zM19 21h2v1h-2zM26 21h2v1h-2zM1 22h2v1h-2zM8 22h2v1h-2zM18 22h2v1h-2zM25 22h2v1h-2zM2 23h2v1h-2zM8 23h12v1h-12zM24 23h2v1h-2zM3 24h3v1h-3zM8 24h12v1h-12zM22 24h3v1h-3zM4 25h6v1h-6zM18 25h6v1h-6zM6 26h4v1h-4zM18 26h4v1h-4zM8 27h2v1h-2zM18 27h2v1h-2zM8 28h2v1h-2zM13 28h2v1h-2zM18 28h2v1h-2zM8 29h2v1h-2zM13 29h2v1h-2zM18 29h2v1h-2zM8 30h3v1h-3zM13 30h2v1h-2zM17 30h3v1h-3zM9 31h10v1h-10zM10 32h3v1h-3zM15 32h3v1h-3z'], ['#cf9c9b', 'M10 2h8v1h-8zM8 3h1v1h-1zM19 3h1v1h-1zM6 4h1v1h-1zM11 4h1v1h-1zM16 4h1v1h-1zM21 4h1v1h-1zM5 5h1v1h-1zM10 5h1v1h-1zM17 5h1v1h-1zM22 5h1v1h-1zM4 6h4v1h-4zM11 6h1v1h-1zM16 6h1v1h-1zM20 6h4v1h-4zM9 7h2v1h-2zM17 7h2v1h-2zM3 8h1v1h-1zM24 8h1v1h-1zM2 10h1v1h-1zM11 10h1v1h-1zM16 10h1v1h-1zM25 10h1v1h-1zM2 11h1v1h-1zM25 11h1v1h-1zM2 12h1v1h-1zM4 12h1v1h-1zM8 12h2v1h-2zM18 12h2v1h-2zM23 12h1v1h-1zM25 12h1v1h-1zM2 13h3v1h-3zM23 13h3v1h-3zM3 14h2v1h-2zM23 14h2v1h-2zM8 16h1v1h-1zM19 16h1v1h-1zM3 17h1v1h-1zM5 17h1v1h-1zM11 17h1v1h-1zM16 17h1v1h-1zM22 17h1v1h-1zM24 17h1v1h-1zM2 18h1v1h-1zM6 18h1v1h-1zM21 18h1v1h-1zM25 18h1v1h-1zM13 19h2v1h-2zM2 20h1v1h-1zM25 20h1v1h-1zM3 21h1v1h-1zM6 21h1v1h-1zM9 21h1v1h-1zM18 21h1v1h-1zM21 21h1v1h-1zM24 21h1v1h-1zM4 22h2v1h-2zM11 22h6v1h-6zM22 22h2v1h-2zM10 25h8v1h-8zM10 26h1v1h-1zM17 26h1v1h-1zM12 28h1v1h-1zM15 28h1v1h-1zM15 29h1v1h-1zM15 30h1v1h-1z'], ['#e3c6c5', 'M9 3h10v1h-10zM7 4h4v1h-4zM12 4h4v1h-4zM17 4h4v1h-4zM6 5h4v1h-4zM12 5h4v1h-4zM18 5h4v1h-4zM12 6h4v1h-4zM11 7h6v1h-6zM10 8h8v1h-8zM3 9h1v1h-1zM10 9h8v1h-8zM24 9h1v1h-1zM3 10h1v1h-1zM10 10h1v1h-1zM17 10h1v1h-1zM24 10h1v1h-1zM3 11h1v1h-1zM10 11h1v1h-1zM17 11h1v1h-1zM24 11h1v1h-1zM3 12h1v1h-1zM10 12h1v1h-1zM17 12h1v1h-1zM24 12h1v1h-1zM8 13h3v1h-3zM17 13h3v1h-3zM8 14h3v1h-3zM17 14h3v1h-3zM8 15h3v1h-3zM17 15h3v1h-3zM9 16h2v1h-2zM17 16h2v1h-2zM4 17h1v1h-1zM9 17h2v1h-2zM17 17h2v1h-2zM23 17h1v1h-1zM3 18h3v1h-3zM9 18h10v1h-10zM22 18h3v1h-3zM2 19h5v1h-5zM9 19h4v1h-4zM15 19h4v1h-4zM21 19h5v1h-5zM3 20h4v1h-4zM9 20h10v1h-10zM21 20h4v1h-4zM4 21h2v1h-2zM10 21h8v1h-8zM22 21h2v1h-2zM11 26h6v1h-6zM10 27h8v1h-8zM10 28h2v1h-2zM16 28h2v1h-2zM10 29h3v1h-3zM16 29h2v1h-2zM11 30h2v1h-2zM16 30h1v1h-1z'], ['#b97371', 'M11 5h1v1h-1zM16 5h1v1h-1zM8 6h3v1h-3zM17 6h3v1h-3zM4 7h1v1h-1zM23 7h1v1h-1zM2 14h1v1h-1zM25 14h1v1h-1zM2 21h1v1h-1zM25 21h1v1h-1zM3 22h1v1h-1zM6 22h2v1h-2zM10 22h1v1h-1zM17 22h1v1h-1zM20 22h2v1h-2zM24 22h1v1h-1zM4 23h4v1h-4zM20 23h4v1h-4zM6 24h2v1h-2zM20 24h2v1h-2z'], ['#ffffff', 'M5 8h2v1h-2zM19 8h2v1h-2zM5 9h2v1h-2zM19 9h2v1h-2zM12 11h4v1h-4zM13 16h2v1h-2z'], ['#9cc8cc', 'M5 12h3v1h-3zM20 12h3v1h-3zM5 13h3v1h-3zM20 13h3v1h-3zM5 14h3v1h-3zM7 15h1v1h-1z'], ['#837f7f', 'M12 12h1v1h-1zM15 12h1v1h-1z'], ['#9cc8cd', 'M20 14h3v1h-3zM20 15h1v1h-1z']] },
9 limit: { width: 36, height: 27, paths: [['#080000', 'M20 0h6v1h-6zM17 1h12v1h-12zM15 2h6v1h-6zM25 2h6v1h-6zM14 3h4v1h-4zM22 3h1v1h-1zM28 3h4v1h-4zM13 4h3v1h-3zM23 4h1v1h-1zM30 4h3v1h-3zM11 5h4v1h-4zM21 5h1v1h-1zM24 5h1v1h-1zM31 5h2v1h-2zM9 6h5v1h-5zM21 6h2v1h-2zM32 6h2v1h-2zM7 7h7v1h-7zM21 7h2v1h-2zM32 7h3v1h-3zM4 8h5v1h-5zM11 8h2v1h-2zM21 8h2v1h-2zM33 8h2v1h-2zM3 9h5v1h-5zM11 9h2v1h-2zM21 9h2v1h-2zM33 9h2v1h-2zM2 10h3v1h-3zM6 10h2v1h-2zM10 10h3v1h-3zM21 10h1v1h-1zM34 10h2v1h-2zM2 11h2v1h-2zM6 11h2v1h-2zM10 11h2v1h-2zM18 11h3v1h-3zM34 11h2v1h-2zM1 12h7v1h-7zM10 12h2v1h-2zM18 12h1v1h-1zM20 12h2v1h-2zM34 12h2v1h-2zM0 13h2v1h-2zM6 13h2v1h-2zM10 13h2v1h-2zM18 13h1v1h-1zM20 13h3v1h-3zM34 13h2v1h-2zM0 14h2v1h-2zM6 14h2v1h-2zM10 14h2v1h-2zM18 14h1v1h-1zM20 14h3v1h-3zM34 14h2v1h-2zM0 15h2v1h-2zM6 15h2v1h-2zM10 15h2v1h-2zM18 15h1v1h-1zM20 15h3v1h-3zM34 15h2v1h-2zM0 16h2v1h-2zM7 16h2v1h-2zM10 16h3v1h-3zM18 16h1v1h-1zM20 16h2v1h-2zM34 16h2v1h-2zM0 17h13v1h-13zM18 17h3v1h-3zM33 17h2v1h-2zM1 18h12v1h-12zM33 18h2v1h-2zM1 19h2v1h-2zM6 19h2v1h-2zM11 19h3v1h-3zM21 19h2v1h-2zM32 19h3v1h-3zM1 20h2v1h-2zM6 20h2v1h-2zM12 20h2v1h-2zM21 20h2v1h-2zM25 20h1v1h-1zM32 20h2v1h-2zM2 21h2v1h-2zM6 21h2v1h-2zM13 21h2v1h-2zM21 21h1v1h-1zM24 21h1v1h-1zM31 21h2v1h-2zM3 22h7v1h-7zM14 22h2v1h-2zM21 22h1v1h-1zM23 22h1v1h-1zM30 22h3v1h-3zM4 23h14v1h-14zM28 23h4v1h-4zM10 24h11v1h-11zM25 24h6v1h-6zM17 25h12v1h-12zM19 26h7v1h-7z'], ['#cf9c9b', 'M21 2h4v1h-4zM18 3h1v1h-1zM21 3h1v1h-1zM23 3h1v1h-1zM27 3h1v1h-1zM16 4h1v1h-1zM21 4h2v1h-2zM24 4h1v1h-1zM29 4h1v1h-1zM15 5h1v1h-1zM22 5h2v1h-2zM30 5h1v1h-1zM14 6h1v1h-1zM24 6h1v1h-1zM31 6h1v1h-1zM9 8h2v1h-2zM13 8h1v1h-1zM32 8h1v1h-1zM8 9h2v1h-2zM22 10h1v1h-1zM33 10h1v1h-1zM12 11h1v1h-1zM21 11h1v1h-1zM33 11h1v1h-1zM12 12h1v1h-1zM22 12h1v1h-1zM2 13h1v1h-1zM5 13h1v1h-1zM12 13h1v1h-1zM12 14h1v1h-1zM5 15h1v1h-1zM8 15h2v1h-2zM13 15h1v1h-1zM2 16h4v1h-4zM13 16h1v1h-1zM22 16h1v1h-1zM33 16h1v1h-1zM13 17h3v1h-3zM21 17h2v1h-2zM31 17h2v1h-2zM14 18h7v1h-7zM23 18h1v1h-1zM29 18h3v1h-3zM4 19h2v1h-2zM8 19h3v1h-3zM14 19h7v1h-7zM23 19h9v1h-9zM4 20h2v1h-2zM15 20h5v1h-5zM23 20h1v1h-1zM26 20h5v1h-5zM16 21h3v1h-3zM23 21h1v1h-1zM26 21h4v1h-4zM17 22h1v1h-1zM25 22h4v1h-4zM23 23h4v1h-4zM23 24h1v1h-1z'], ['#e3c6c5', 'M19 3h2v1h-2zM24 3h3v1h-3zM17 4h3v1h-3zM25 4h4v1h-4zM16 5h3v1h-3zM25 5h5v1h-5zM15 6h4v1h-4zM23 6h1v1h-1zM25 6h6v1h-6zM14 7h4v1h-4zM23 7h9v1h-9zM14 8h4v1h-4zM23 8h9v1h-9zM13 9h5v1h-5zM20 9h1v1h-1zM23 9h10v1h-10zM8 10h2v1h-2zM13 10h5v1h-5zM20 10h1v1h-1zM23 10h10v1h-10zM8 11h2v1h-2zM13 11h5v1h-5zM22 11h11v1h-11zM8 12h2v1h-2zM13 12h5v1h-5zM23 12h11v1h-11zM3 13h1v1h-1zM8 13h2v1h-2zM13 13h5v1h-5zM23 13h11v1h-11zM2 14h4v1h-4zM8 14h2v1h-2zM13 14h5v1h-5zM23 14h11v1h-11zM2 15h3v1h-3zM14 15h4v1h-4zM23 15h11v1h-11zM14 16h4v1h-4zM23 16h10v1h-10zM16 17h2v1h-2zM23 17h8v1h-8zM24 18h5v1h-5z'], ['#68b2b4', 'M20 4h1v1h-1zM20 8h1v1h-1zM18 10h1v1h-1zM21 23h1v1h-1z'], ['#9acccd', 'M19 5h2v1h-2zM19 6h2v1h-2zM18 7h3v1h-3zM18 8h2v1h-2zM18 9h2v1h-2zM19 10h1v1h-1zM20 20h1v1h-1zM19 21h2v1h-2zM18 22h3v1h-3zM18 23h3v1h-3zM21 24h2v1h-2z'], ['#b97371', 'M10 9h1v1h-1zM5 10h1v1h-1zM4 11h2v1h-2zM12 15h1v1h-1zM6 16h1v1h-1zM9 16h1v1h-1zM13 18h1v1h-1zM21 18h2v1h-2zM32 18h1v1h-1zM3 19h1v1h-1zM3 20h1v1h-1zM8 20h4v1h-4zM14 20h1v1h-1zM24 20h1v1h-1zM31 20h1v1h-1zM4 21h2v1h-2zM8 21h5v1h-5zM15 21h1v1h-1zM22 21h1v1h-1zM25 21h1v1h-1zM30 21h1v1h-1zM10 22h4v1h-4zM16 22h1v1h-1zM22 22h1v1h-1zM24 22h1v1h-1zM29 22h1v1h-1zM22 23h1v1h-1zM27 23h1v1h-1zM24 24h1v1h-1z'], ['#ffffff', 'M19 12h1v1h-1zM19 13h1v1h-1zM19 14h1v1h-1zM19 15h1v1h-1zM19 16h1v1h-1z'], ['#efe0df', 'M4 13h1v1h-1z']] },
10} satisfies Record<string, Sprite>
11types/index.d.ts 8 lines1export type Reading = { percentUsed: number; resetsAt: string }
2
3declare module 'claude-code' {
4 interface PluginState {
5 'usage-pace': { fiveHour: Reading | null; sevenDay: Reading | null }
6 }
7}
8