Claude logo above the prompt that decays as context fills, and heals on compact or clear

A Claude Code mod: the Claude logo sits above the prompt (top right) and decays as context fills.
| Context | Look |
|---|---|
| 0-30% | Normal orange |
| 30-65% | Green zombie, growing blood |
| 65-90% | Bone spreads from the top-left of the head |
| 90%+ | Full skeleton |
<img src="assets/0.svg" width="150" alt="0-30%"> <img src="assets/40.svg" width="150" alt="30-55%"> <img src="assets/60.svg" width="150" alt="55-65%"> <img src="assets/70.svg" width="150" alt="65-80%"> <img src="assets/80.svg" width="150" alt="80-90%"> <img src="assets/90.svg" width="150" alt="90%+">
It heals when you /compact or /clear. Test with /zombie 0-100, reset with /zombie off.
/plugin install context-zombie --marketplace thepragmaticbear/context-zombie
Answer y to add the marketplace, then pick the user scope.
hooks/decay.tsx 98 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4const forced = atom({ plugin: 'context-zombie', key: 'forced' } as const, null)
5const percent = atom({ plugin: 'context-zombie', key: 'percent' } as const, 0)
6
7// Claude Code mascot: the shape never changes, only the colour.
8const LOGO = [' ▐▛███▜▌ ', '▝▜█████▛▘', ' ▘▘ ▝▝ ']
9
10// Stable pseudo-random 0..1 per cell, so blood stays put between redraws.
11const rnd = (r: number, c: number) => {
12 const x = Math.sin((r + 1) * 12.9898 + (c + 1) * 78.233) * 43758.5453
13 return x - Math.floor(x)
14}
15
16const BONE = 'rgb(230,225,205)'
17
18// percent -> rows of [char, color]: orange -> green -> bloody dark green,
19// then from 65% bone spreads from the top-left of the head until all skeleton at 90%.
20const zombie = (p: number): [string, string][][] => {
21 const skin = p < 30 ? 'claude' : p < 55 ? 'rgb(120,190,90)' : 'rgb(70,130,60)'
22 const blood = p < 30 ? 0 : Math.min(0.6, (p - 30) / 60) // share of cells gone red
23 const reach = p < 65 ? -1 : (p - 65) / 25 + 0.1 // bone front, 0 = top-left, 1 = bottom-right
24 return LOGO.map((row, r) =>
25 [...row].map((ch, c): [string, string] => {
26 if (ch === ' ') return [ch, skin]
27 if ((c + r * 2) / 12 < reach) return [ch, BONE]
28 if (rnd(r, c) < blood) {
29 // scorched cell: solid dark red (what ▒ looked like), other blood cells bright red
30 return r < 2 && rnd(c, r) < 0.5 ? ['█', 'rgb(107,25,25)'] : [ch, 'rgb(170,20,20)']
31 }
32 return [ch, skin]
33 }),
34 )
35}
36
37async function refresh($: any) {
38 try {
39 if ((await read($, forced)) !== null) return // /zombie override active
40 const { context } = await $.session.usage()
41 await update($, percent, () => context.percent ?? 0)
42 } catch {
43 // session not bound yet
44 }
45}
46
47export const register: Register = on => {
48 on('session.start', async ($, e, next) => {
49 await $.command.register({
50 name: 'zombie',
51 description: 'Fake the context percent to test the logo: /zombie 0-100, or /zombie off',
52 })
53 await refresh($)
54 $.clock.every(3000, () => refresh($)) // catches /clear and auto-compact too
55 return next(e)
56 })
57 on('command.run', { command: 'zombie' }, async ($, e) => {
58 const arg = e.args.trim()
59 if (arg === 'off' || arg === '') {
60 await update($, forced, () => null)
61 await refresh($)
62 return { text: 'Zombie override off; tracking real context.' }
63 }
64 const n = Math.min(100, Math.max(0, Number(arg)))
65 if (Number.isNaN(n)) return { text: 'Usage: /zombie 0-100 | off' }
66 await update($, forced, () => n)
67 await update($, percent, () => n)
68 return { text: `Context faked at ${n}%.` }
69 })
70 on('turn.complete', async ($, e, next) => (await refresh($), next(e)))
71 on('session.compact', async ($, e, next) => {
72 const out = await next(e)
73 await refresh($)
74 return out
75 })
76
77 on('ui.render', { component: 'PromptHint' }, async ($, e, next) => {
78 const p = await read($, percent)
79 const { Box, Text } = $.ui.resolve(e)
80 const rows = zombie(p)
81
82 return (
83 <Box flexDirection="row" justifyContent="flex-end" width="100%">
84 <Box flexDirection="column">
85 {rows.map(row => (
86 <Box flexDirection="row">
87 {row.map(([ch, color]) => (
88 <Text color={color}>{ch}</Text>
89 ))}
90 </Box>
91 ))}
92 </Box>
93 <Text dimColor> {Math.round(p)}%</Text>
94 </Box>
95 )
96 })
97}
98types/state.d.ts 8 lines1export type Percent = number
2
3declare module 'claude-code' {
4 interface PluginState {
5 'context-zombie': { percent: number; forced: number | null }
6 }
7}
8