From 3db68f4ec17091ebf99f9f84d95d1926801be5c7 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:02:54 +0300 Subject: [PATCH 01/12] feat(motion): scanline, ring, glitch and signal spinners Signed-off-by: NovusEdge --- hooks/motion.ts | 60 ++++++++++++++++++++++++++++++++++++++++++--- hooks/packs.ts | 2 +- test/motion.test.ts | 47 ++++++++++++++++++++++++++++++++--- 3 files changed, 101 insertions(+), 8 deletions(-) diff --git a/hooks/motion.ts b/hooks/motion.ts index 59b7190f..e81f1c91 100644 --- a/hooks/motion.ts +++ b/hooks/motion.ts @@ -3,12 +3,14 @@ import { gradient, mix, wave, wave3, type Span } from './color.ts' export type Cell = { ch: string; fg: string; bg?: string } export type OrbState = 'think' | 'search' | 'work' | 'run' | 'agents' -export type SpinnerId = 'stock' | 'comet' | 'eyes' | 'orb-states' | 'clawd' | 'shimmer' +export type SpinnerId = 'stock' | 'comet' | 'eyes' | 'orb-states' | 'clawd' | 'shimmer' | 'scanline' | 'ring' | 'glitch' | 'signal' type Canvas = { w: number; h: number; d: number[] } type Spec = { name: string; cols: number; rows: number } & ( | { kind: 'glyph'; frames: string } | { kind: 'dots' | 'half'; draw: (cv: Canvas, t: number, st: OrbState) => void; useFg?: boolean } - | { kind: 'art'; art: string[][] }) + | { kind: 'art'; art: string[][] } + | { kind: 'scan' }) +type CellOpts = { color: string; bg: string; fg: string; wordLen?: number } const CLAWD = '#d77757' const canvas = (w: number, h: number): Canvas => ({ w, h, d: new Array(w * h).fill(0) }) @@ -103,6 +105,53 @@ function eyes(cv: Canvas, t: number) { } } +// One sweep crosses the bar, the one-cell gap the spinner leaves before the word, the word and a +// pause. spinnerWordSpans lights the word from the same position, so both must use this. +export const SCAN_BAR = 10 +const SCAN_MS = 45, SCAN_PAUSE = 6 +export function scanPos(tMs: number, wordLen: number, st: OrbState): number { + const speed = st === 'run' || st === 'agents' ? 1.5 : 1 + return ((Math.max(0, tMs || 0) * speed) / SCAN_MS) % (SCAN_BAR + 1 + wordLen + SCAN_PAUSE) +} + +const isLight = (hex: string) => { + const n = parseInt(hex.slice(1), 16) + return (0.299 * (n >> 16) + 0.587 * ((n >> 8) & 255) + 0.114 * (n & 255)) > 140 +} + +function scanCells(tMs: number, st: OrbState, o: CellOpts): Cell[][] { + const pos = scanPos(tMs, o.wordLen ?? 12, st), shift = Math.floor(tMs / 400) + // Toward white reads on a dark pack; on a light one it fades into the background, so text itself. + const hi = isLight(o.bg) ? o.fg : mix(o.fg, '#ffffff', 0.5), faint = mix(o.bg, o.color, 0.25) + return [Array.from({ length: SCAN_BAR }, (_, i) => { + const d = pos - i + if (d >= 0 && d < 1) return { ch: '█', fg: hi } + return { ch: (i + shift) % 2 ? '▚' : '▞', fg: d >= 1 && d < 3 ? o.color : faint } + })] +} + +function ringDraw(cv: Canvas, t: number, st: OrbState) { + const cx = (cv.w - 1) / 2, cy = (cv.h - 1) / 2 + const speed = st === 'work' || st === 'run' ? 1.5 : 1 + const [arcs, n] = st === 'agents' ? [[0, 2.094, 4.189], 3] : [[0], 9] + for (const off of arcs) for (let i = 0; i < n; i++) { + const a = t * Math.PI * speed + off - i * 0.55 + plot(cv, cx + Math.cos(a) * 5.2, cy + Math.sin(a) * 3.2, 1 - i / n) + } +} + +function signalDraw(cv: Canvas, t: number, st: OrbState) { + const amp = st === 'think' ? 0.55 : st === 'run' ? 1 : 0.8 + const trace = (a: number, off: number, v: (x: number) => number) => { + for (let x = 0; x < cv.w; x++) { + const p = t / 0.16 + x * 0.55 + off + plot(cv, x, 3.5 + a * 3.4 * (0.65 * Math.sin(p) + 0.35 * Math.sin(p * 2.7 + 1)), v(x)) + } + } + trace(amp, 0, x => (x >= cv.w - 3 ? 1 : 0.7)) + if (st === 'agents') trace(amp / 2, Math.PI, () => 0.45) +} + export const SPINNERS: Record = { stock: { name: 'stock ✻ cycle', cols: 1, rows: 1, kind: 'glyph', frames: '·✢✳✶✻✽' }, comet: { name: 'comet', cols: 3, rows: 2, kind: 'dots', draw: (cv, t) => { @@ -114,13 +163,18 @@ export const SPINNERS: Record = { 'orb-states': { name: 'orb states', cols: 4, rows: 2, kind: 'dots', draw: orbDraw }, clawd: { name: 'Clawd', cols: 5, rows: 2, kind: 'art', art: [['▐▛█▜▌', '▝▛█▜▘'], ['▐▛█▜▌', '▝▜█▛▘'], ['▐▛█▜▌', '▝▛█▜▘'], ['▐▛█▜▌', '▗▛█▜▖']] }, shimmer: { name: 'text shimmer wave', cols: 1, rows: 1, kind: 'glyph', frames: '✻' }, + scanline: { name: 'scanline sweep', cols: SCAN_BAR, rows: 1, kind: 'scan' }, + ring: { name: 'angel ring', cols: 6, rows: 2, kind: 'dots', draw: ringDraw }, + glitch: { name: 'glitch word', cols: 1, rows: 1, kind: 'glyph', frames: '◆' }, + signal: { name: 'signal trace', cols: 10, rows: 2, kind: 'dots', draw: signalDraw }, } -export function spinnerCells(id: SpinnerId, tMs: number, o: { color: string; bg: string; fg: string }, st: OrbState = 'think'): Cell[][] { +export function spinnerCells(id: SpinnerId, tMs: number, o: CellOpts, st: OrbState = 'think'): Cell[][] { tMs = Math.max(0, tMs || 0) const s = SPINNERS[id] if (s.kind === 'glyph') { const f = [...s.frames]; return [[{ ch: f[Math.floor(tMs / 120) % f.length]!, fg: o.color }]] } if (s.kind === 'art') return s.art[Math.floor(tMs / 220) % s.art.length]!.map(r => [...r].map(ch => ({ ch, fg: CLAWD }))) + if (s.kind === 'scan') return scanCells(tMs, st, o) if (s.kind === 'dots') { const cv = canvas(s.cols * 2, s.rows * 4); s.draw(cv, tMs / 1000, st); return dotCells(cv, s.cols, s.rows, o.color, o.bg) } const cv = canvas(s.cols, s.rows * 2); s.draw(cv, tMs / 1000, st) return halfCells(cv, s.cols, s.rows, s.useFg ? o.fg : o.color, o.bg) diff --git a/hooks/packs.ts b/hooks/packs.ts index e47326ca..5763cdb9 100644 --- a/hooks/packs.ts +++ b/hooks/packs.ts @@ -3,7 +3,7 @@ import { resolveTheme, PRESETS, COLOR_KEYS, GLYPH_KEYS, shown, isPlain, isUnsafe import { PACKS } from './packpresets.ts' import type { PetTint } from './pets.ts' -export const SPINNER_IDS = ['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer'] as const +export const SPINNER_IDS = ['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer', 'scanline', 'ring', 'glitch', 'signal'] as const export type SpinnerId = (typeof SPINNER_IDS)[number] export const ROW_STYLES = ['classic', 'cards', 'minimal', 'retro'] as const export type RowStyle = (typeof ROW_STYLES)[number] diff --git a/test/motion.test.ts b/test/motion.test.ts index a6144adc..842a6550 100644 --- a/test/motion.test.ts +++ b/test/motion.test.ts @@ -1,6 +1,6 @@ import { test, expect } from 'claude-code/testing' import { mix, gradient, wave } from '../hooks/color.ts' -import { SPINNERS, spinnerCells, cellsToSpans, type SpinnerId } from '../hooks/motion.ts' +import { SPINNERS, spinnerCells, scanPos, cellsToSpans, type SpinnerId } from '../hooks/motion.ts' import { cellWidth } from '../hooks/cells.ts' const O = { color: '#38e8ff', bg: '#1a0b33', fg: '#f0e6ff' } @@ -25,8 +25,8 @@ test('wave moves with time', async () => { expect(at(0)).not.toBe(at(400)) }) -test('the 0.2 library is exactly these six', async () => { - expect(ids.sort()).toEqual(['clawd', 'comet', 'eyes', 'orb-states', 'shimmer', 'stock']) +test('the library is exactly these ten', async () => { + expect(ids.sort()).toEqual(['clawd', 'comet', 'eyes', 'glitch', 'orb-states', 'ring', 'scanline', 'shimmer', 'signal', 'stock']) }) test('every frame has its declared size and only width-1 BMP cells', async () => { @@ -46,13 +46,52 @@ test('every frame has its declared size and only width-1 BMP cells', async () => }) test('frames are deterministic and move over time', async () => { - for (const id of ['comet', 'eyes', 'orb-states', 'clawd'] as const) { + for (const id of ['comet', 'eyes', 'orb-states', 'clawd', 'scanline', 'ring', 'signal'] as const) { const at = (t: number) => JSON.stringify(spinnerCells(id, t, O)) expect(at(1000)).toBe(at(1000)) expect(new Set([0, 250, 1000, 3450].map(at)).size).toBeGreaterThan(1) } }) +test('every new spinner keeps its size in every state and is never blank', async () => { + for (const id of ['scanline', 'ring', 'glitch', 'signal'] as const) for (const st of ['think', 'search', 'work', 'run', 'agents'] as const) for (const t of [0, 1000, 10_000]) { + const rows = spinnerCells(id, t, O, st) + expect(rows.length).toBe(SPINNERS[id].rows) + for (const r of rows) expect(r.length).toBe(SPINNERS[id].cols) + expect(rows.flat().some(c => c.ch !== ' ')).toBe(true) + } +}) + +test('scanline: the scan cell is a full block that crosses the bar left to right', async () => { + const at = (t: number) => spinnerCells('scanline', t, { ...O, wordLen: 10 }).flat().findIndex(c => c.ch === '█') + expect(at(0)).toBe(0) + expect(at(45 * 4)).toBe(4) + expect(at(45 * 12)).toBe(-1) // past the bar, scanning the word +}) + +test('scanline: run and agents scan faster than think', async () => { + expect(scanPos(450, 10, 'run')).toBeGreaterThan(scanPos(450, 10, 'think')) + expect(scanPos(450, 10, 'agents')).toBe(scanPos(450, 10, 'run')) +}) + +test('scanline: the scan cell reads on a light pack', async () => { + const light = { color: '#0000f2', bg: '#f2f2f2', fg: '#000091' } + const hi = spinnerCells('scanline', 0, light).flat()[0]! + expect(hi.ch).toBe('█') + expect(hi.fg).toBe('#000091') +}) + +test('ring and signal react to the activity state', async () => { + for (const id of ['ring', 'signal'] as const) { + const frames = (['think', 'run', 'agents'] as const).map(st => JSON.stringify(spinnerCells(id, 1000, O, st))) + expect(new Set(frames).size).toBe(3) + } +}) + +test('glitch draws one diamond in the spinner color', async () => { + expect(spinnerCells('glitch', 500, O)).toEqual([[{ ch: '◆', fg: O.color }]]) +}) + test('orb states draw differently', async () => { const frames = (['think', 'search', 'work', 'run', 'agents'] as const).map(st => JSON.stringify(spinnerCells('orb-states', 1000, O, st))) expect(new Set(frames).size).toBe(5) From ffa86bcb17ec3acd1cb2429eb2f67ae3da71a584 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:12:18 +0300 Subject: [PATCH 02/12] feat(spinner): scanline and glitch light the spinner word Signed-off-by: NovusEdge --- hooks/motion.ts | 44 +++++++++++++++++++++++++++++++++++++++++-- hooks/spinner.ts | 13 +++++++------ test/motion.test.ts | 45 +++++++++++++++++++++++++++++++++++++++++++- test/spinner.test.ts | 11 ++++++++++- 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/hooks/motion.ts b/hooks/motion.ts index e81f1c91..d10104f0 100644 --- a/hooks/motion.ts +++ b/hooks/motion.ts @@ -193,13 +193,53 @@ export type WordLook = { bg: string gradient?: [string, string] motion: { spinner: SpinnerId; shimmer: number; color: string } - theme: { colors: { text: string; accent: string } } + theme: { colors: { text: string; accent: string; read?: string; agent?: string } } } -export function spinnerWordSpans(look: WordLook, text: string, now: number): Span[] { +// One span per run of equal style: each span is an element the surface diffs every frame. +function runs(spans: Span[]): Span[] { + const out: Span[] = [] + for (const s of spans) { + const last = out.at(-1) + if (last && last.color === s.color && last.bg === s.bg && !!last.bold === !!s.bold) last.text += s.text + else out.push({ ...s }) + } + return out +} + +function scanWord(look: WordLook, text: string, t: number, st: OrbState): Span[] { + const chars = [...text], pos = scanPos(t, chars.length, st), c = look.theme.colors + return runs(chars.map((ch, i) => { + const d = pos - (SCAN_BAR + 1 + i) + return d >= 0 && d < 2 ? { text: ch, color: look.bg, bg: c.accent, bold: true } : { text: ch, color: c.text, bold: true } + })) +} + +// Seeded by the 70 ms frame, so every redraw within a frame draws the same glitch. A tear repeats +// the first letter in front and drops the trailing space: the line never changes length. +function glitchWord(look: WordLook, text: string, t: number, st: OrbState): Span[] { + const c = look.theme.colors, a = c.read ?? c.accent, b = c.agent ?? c.text + let seed = (Math.floor(Math.max(0, t || 0) / 70) * 9301 + 49297) % 233280 + const rnd = () => (seed = (seed * 9301 + 49297) % 233280) / 233280 + const k = st === 'run' || st === 'agents' ? 2 : 1, chars = [...text], out: Span[] = [] + const tear = rnd() < 0.12 + if (tear) out.push({ text: chars[0] ?? ' ', color: b }) + for (const ch of chars) { + const r = rnd(), pick = rnd() < 0.5 ? a : b, block = '▓▒░█▚'[Math.floor(rnd() * 5)]! + if (r < 0.10 * k) out.push({ text: block, color: pick }) + else if (r < 0.16 * k) out.push({ text: ch, color: look.bg, bg: pick }) + else out.push({ text: ch, color: c.text, bold: true }) + } + if (!tear) out.push({ text: ' ', color: c.text }) + return runs(out) +} + +export function spinnerWordSpans(look: WordLook, text: string, now: number, at?: { t: number; st: OrbState }): Span[] { const { spinner, shimmer, color } = look.motion const c = look.theme.colors const [c1, c2] = look.gradient ?? [c.accent, c.text] + if (spinner === 'scanline') return scanWord(look, text, at?.t ?? now, at?.st ?? 'think') + if (spinner === 'glitch') return glitchWord(look, text, at?.t ?? now, at?.st ?? 'think') if (spinner === 'shimmer') return wave3(text, mix(color, look.bg, 0.45), mix(color, '#ffffff', 0.6), now, Math.max(1, shimmer)) if (shimmer > 0) return wave(text, c1, c2, now, shimmer * 1.2).map(s => ({ ...s, bold: true })) if (look.gradient) return gradient(text, c1, c2).map(s => ({ ...s, bold: true })) diff --git a/hooks/spinner.ts b/hooks/spinner.ts index 742ea987..50490735 100644 --- a/hooks/spinner.ts +++ b/hooks/spinner.ts @@ -12,7 +12,7 @@ export type SpinLook = { bg: string gradient?: [string, string] motion: { spinner: SpinnerId; shimmer: 0 | 1 | 2; color: string } - theme: { colors: { text: string; accent: string; dim: string } } + theme: { colors: { text: string; accent: string; dim: string; read: string; agent: string } } } export type SpinnerProps = { look: SpinLook; input: SpinInput; reduced: boolean } @@ -36,15 +36,16 @@ export function spinnerLine(look: SpinLook, input: SpinInput, now: number): { ba const { spinner, color } = look.motion const c = look.theme.colors const t = now - input.turnAt - const badge = cellsToSpans(spinnerCells(spinner, t, { color, bg: look.bg, fg: c.text }, input.state)) - const word = spinnerWordSpans(look, input.word + '…', now) - return { badge, word, tail: `(${elapsed(t)} · esc to interrupt)`, ...(SPINNERS[spinner].rows >= 2 ? { detail: input.detail } : {}) } + const word = input.word + '…' + const badge = cellsToSpans(spinnerCells(spinner, t, { color, bg: look.bg, fg: c.text, wordLen: [...word].length }, input.state)) + const spans = spinnerWordSpans(look, word, now, { t, st: input.state }) + return { badge, word: spans, tail: `(${elapsed(t)} · esc to interrupt)`, ...(SPINNERS[spinner].rows >= 2 ? { detail: input.detail } : {}) } } export function spinnerProps(look: Look, input: SpinInput, reduced: boolean): SpinnerProps { - const { text, accent, dim } = look.theme.colors + const { text, accent, dim, read, agent } = look.theme.colors return { - look: { bg: look.bg, ...(look.gradient ? { gradient: look.gradient } : {}), motion: look.motion, theme: { colors: { text, accent, dim } } }, + look: { bg: look.bg, ...(look.gradient ? { gradient: look.gradient } : {}), motion: look.motion, theme: { colors: { text, accent, dim, read, agent } } }, input, reduced, } diff --git a/test/motion.test.ts b/test/motion.test.ts index 842a6550..c2a320dc 100644 --- a/test/motion.test.ts +++ b/test/motion.test.ts @@ -1,6 +1,6 @@ import { test, expect } from 'claude-code/testing' import { mix, gradient, wave } from '../hooks/color.ts' -import { SPINNERS, spinnerCells, scanPos, cellsToSpans, type SpinnerId } from '../hooks/motion.ts' +import { SPINNERS, spinnerCells, scanPos, cellsToSpans, spinnerWordSpans, SCAN_BAR, type SpinnerId, type WordLook } from '../hooks/motion.ts' import { cellWidth } from '../hooks/cells.ts' const O = { color: '#38e8ff', bg: '#1a0b33', fg: '#f0e6ff' } @@ -127,3 +127,46 @@ test('cellsToSpans merges runs of one color', async () => { const spans = cellsToSpans([[{ ch: 'a', fg: '#111111' }, { ch: 'b', fg: '#111111' }, { ch: 'c', fg: '#222222', bg: '#000000' }]]) expect(spans[0]).toEqual([{ text: 'ab', color: '#111111' }, { text: 'c', color: '#222222', bg: '#000000' }]) }) + +const WL = (spinner: SpinnerId): WordLook => ({ + bg: '#0d0d24', motion: { spinner, shimmer: 1, color: '#8080ff' }, + theme: { colors: { text: '#e5e5ee', accent: '#8080ff', read: '#00f2f2', agent: '#af66f7' } }, +}) +const joined = (s: { text: string }[]) => s.map(x => x.text).join('') + +test('scanline word: every character comes back in order, the two under the scan lit', async () => { + const word = 'Ünïcode…' + for (const t of [0, 500, 1000, 1700]) expect(joined(spinnerWordSpans(WL('scanline'), word, 0, { t, st: 'think' }))).toBe(word) + // the scan reaches the word's first letter after the bar and the one-cell gap + const lit = spinnerWordSpans(WL('scanline'), word, 0, { t: (SCAN_BAR + 1) * 45, st: 'think' }) + expect(lit[0]).toMatchObject({ color: '#0d0d24', bg: '#8080ff' }) + expect(spinnerWordSpans(WL('scanline'), word, 0, { t: 0, st: 'think' }).every(s => !s.bg)).toBe(true) +}) + +test('glitch word: never changes the line length and keeps letters in place', async () => { + const word = 'Thinking…', n = [...word].length + let tore = 0, swapped = 0 + for (let f = 0; f < 400; f++) { + const spans = spinnerWordSpans(WL('glitch'), word, 0, { t: f * 70, st: 'run' }) + const chars = [...joined(spans)] + expect(chars.length).toBe(n + 1) + const torn = chars.at(-1) !== ' ' + if (torn) tore++ + const body = torn ? chars.slice(1) : chars.slice(0, n) + body.forEach((ch, i) => { if (ch !== [...word][i]) { expect('▓▒░█▚').toContain(ch); swapped++ } }) + } + expect(tore).toBeGreaterThan(0) + expect(swapped).toBeGreaterThan(0) +}) + +test('glitch word: a frame is the same on every redraw within its 70 ms', async () => { + const at = (t: number) => JSON.stringify(spinnerWordSpans(WL('glitch'), 'Thinking…', 0, { t, st: 'think' })) + expect(at(140)).toBe(at(209)) +}) + +test('word effects fall back to accent and text without read and agent colors', async () => { + const bare: WordLook = { ...WL('glitch'), theme: { colors: { text: '#e5e5ee', accent: '#8080ff' } } } + for (let f = 0; f < 50; f++) for (const s of spinnerWordSpans(bare, 'Thinking…', 0, { t: f * 70, st: 'run' })) { + expect(['#e5e5ee', '#8080ff', '#0d0d24']).toContain(s.color) + } +}) diff --git a/test/spinner.test.ts b/test/spinner.test.ts index 8de1fe8a..4f8fe76f 100644 --- a/test/spinner.test.ts +++ b/test/spinner.test.ts @@ -1,7 +1,7 @@ import { test, expect } from 'claude-code/testing' import { orbStateOf, usesOwnSpinner, spinnerLine, spinnerProps, checkedSpinnerProps, elapsed } from '../hooks/spinner.ts' import SpinnerClient from '../hooks/client/spinner.tsx' -import { SPINNERS } from '../hooks/motion.ts' +import { SPINNERS, SCAN_BAR } from '../hooks/motion.ts' import { SPINNER_IDS, resolveLook, stockMotion } from '../hooks/packs.ts' import { initialModel, applyEvent } from '../hooks/model.ts' @@ -103,3 +103,12 @@ test('client props carry only what the line reads', async () => { expect(JSON.stringify(p).length).toBeLessThan(1000) expect(spinnerLine(p.look, p.input, 777)).toEqual(spinnerLine(look('arcade'), IN, 777)) }) + +test('spinnerLine hands the word effect the same clock as the badge', async () => { + const l = look('classic') + const scan = { ...l, motion: { ...l.motion, spinner: 'scanline' as const } } + const props = spinnerProps(scan, { word: 'Thinking', turnAt: 1000, detail: '', state: 'think' }, false) + const line = spinnerLine(props.look, props.input, 1000 + (SCAN_BAR + 1) * 45) + expect(line.word[0]!.bg).toBe(props.look.theme.colors.accent) + expect(line.badge[0]!.some(s => s.text.includes('█'))).toBe(false) +}) From 5057c0bc287c3df9661b398c11f269f083beda06 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:18:37 +0300 Subject: [PATCH 03/12] docs: list the scanline, ring, glitch and signal spinners Signed-off-by: NovusEdge --- .claude-plugin/plugin.json | 2 +- docs/commands.md | 2 +- docs/install.md | 4 +- docs/pack-reference.md | 6 +- installer/internal/packs/packs.json | 2336 ++++++++++++++++++++++++ installer/internal/packs/packs_test.go | 2 +- installer/internal/tui/model.go | 6 +- skills/glowup-pack/reference.md | 2 +- test/command.test.ts | 2 +- test/packs.test.ts | 2 +- 10 files changed, 2352 insertions(+), 12 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 87484ecf..bc80d3e9 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -15,7 +15,7 @@ "theme": { "type": "string", "title": "Theme", "description": "Theme name: glowup, aurora, dusk, classic, cyberpunk, vaporwave, high-contrast, or one in ~/.claude/glowup/themes", "default": "classic" }, "reducedMotion": { "type": "boolean", "title": "Reduced motion", "description": "No shimmer or spinner animation from glowup", "default": false }, "pack": { "type": "string", "title": "Pack", "description": "Pack name: classic, crt, cozy, arcade, or one in ~/.claude/glowup/packs", "default": "classic" }, - "spinner": { "type": "string", "title": "Spinner", "description": "Spinner name: stock, comet, eyes, orb-states, clawd, shimmer, or pack for the pack's own", "default": "pack" }, + "spinner": { "type": "string", "title": "Spinner", "description": "Spinner name: stock, comet, eyes, orb-states, clawd, shimmer, scanline, ring, glitch, signal, or pack for the pack's own", "default": "pack" }, "pet": { "type": "string", "title": "Pet", "description": "clawd, robot, off, or a pet in ~/.claude/glowup/pets", "default": "clawd" }, "bubbles": { "type": "string", "title": "Speech bubbles", "description": "on (template lines), off, or haiku (Claude Haiku writes some lines; small paid calls on your account)", "default": "on" }, "statusline": { "type": "string", "title": "Status line fields", "description": "Comma-separated, in order: activity, ctx, 5h, week, cost, model, effort, agents, plan, branch, changes, cwd", "default": "activity,ctx,effort,5h,week" } diff --git a/docs/commands.md b/docs/commands.md index 4702f87b..1d97e22e 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -64,7 +64,7 @@ Each refusal prints a message naming the failed check. Adding a theme whose name | Command | What it does | | --- | --- | -| `/glowup spinner ` | Use one spinner (`stock`, `comet`, `eyes`, `orb-states`, `clawd` or `shimmer`) on top of the current pack. Prints `Spinner: `. An unknown name lists the valid ones. | +| `/glowup spinner ` | Use one spinner (`stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer`, `scanline`, `ring`, `glitch` or `signal`) on top of the current pack. Prints `Spinner: `. An unknown name lists the valid ones. | | `/glowup spinner list` | List the spinners. A filled dot marks the one showing now, whether it is your override or the pack's own. | | `/glowup spinner default` | Drop the override and go back to the pack's spinner. Prints `Spinner: pack default`. | diff --git a/docs/install.md b/docs/install.md index 694b10f4..402d94eb 100644 --- a/docs/install.md +++ b/docs/install.md @@ -36,7 +36,7 @@ curl -fsSL https://glowup.khimani.dev/install.sh | sh -s -- --yes --pack crt | `--yes` | Install without the picker, with the defaults or the options below. The script adds it when there is no terminal. | | `--pack NAME` | `classic`, `crt`, `cozy` or `arcade`. Default `classic`. | | `--theme NAME` | A theme on top of the pack: `classic`, `glowup`, `aurora`, `dusk`, `cyberpunk`, `vaporwave` or `high-contrast`. `classic` keeps the pack's own colors. Default: not set. | -| `--spinner NAME` | `stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer`, or `pack` for the pack's own. Default `pack`. | +| `--spinner NAME` | `stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer`, `scanline`, `ring`, `glitch`, `signal`, or `pack` for the pack's own. Default `pack`. | | `--pet clawd\|off` | Default `clawd`. | | `--bubbles on\|off\|haiku` | Speech bubbles. `haiku` lets Claude Haiku write some lines, as small calls on your account. Default `on`. | | `--reduced-motion` | Turn off glowup's animation. | @@ -92,7 +92,7 @@ The installer sets every setting except `statusline`. To change them afterwards, | `pet` | `clawd` or `off` | `clawd` | Shows Clawd, or no pet. | | `bubbles` | `on`, `off` or `haiku` | `on` | Speech bubbles from templates, none, or lines written by Haiku. | | `theme` | A theme name | `classic` | A theme on top of the pack. `classic` keeps the pack's own colors. | -| `spinner` | A spinner name | `pack` | A spinner on top of the pack: `stock`, `comet`, `eyes`, `orb-states`, `clawd` or `shimmer`. `pack` keeps the pack's own spinner. | +| `spinner` | A spinner name | `pack` | A spinner on top of the pack: `stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer`, `scanline`, `ring`, `glitch` or `signal`. `pack` keeps the pack's own spinner. | | `statusline` | Comma-separated field ids | `activity,ctx,effort,5h,week` | The status line fields and their order. Unknown ids are dropped. See [Status line](statusline.md#choosing-the-fields). | | `reducedMotion` | `true` or `false` | `false` | Turns off glowup's animation. | diff --git a/docs/pack-reference.md b/docs/pack-reference.md index 2d7ffadc..a0cc859c 100644 --- a/docs/pack-reference.md +++ b/docs/pack-reference.md @@ -33,7 +33,7 @@ Packs are read from `~/.claude/glowup/packs`, or `$CLAUDE_CONFIG_DIR/glowup/pack "pet": { "body": "#7aa2f7", "light": "#a9c1ff", "shade": "#4a6fc4" } // Clawd's colors }, "motion": { - "spinner": "orb-states", // stock | comet | eyes | orb-states | clawd | shimmer + "spinner": "orb-states", // stock | comet | eyes | orb-states | clawd | shimmer | scanline | ring | glitch | signal "shimmer": 2, // 0 none, 1 soft, 2 fast "color": "#38e8ff", // defaults to the palette's accent "field": { "shape": "simplex", "speed": 0.3, "scale": 0.36, "dither": "4x4" } @@ -132,6 +132,10 @@ A [renderer plugin](packs.md#drawing-with-a-plugin) that answers for the pack dr | `orb-states` | 4×2 | A braille orb whose motion follows the activity: thinking, searching, working, running, agents. | | `clawd` | 5×2 | Clawd waving, in his own color. | | `shimmer` | 1×1 | A still `✻` with a shimmering word. | +| `scanline` | 10×1 | A bright sweep across a bar, and the spinner word lights as it passes. | +| `ring` | 6×2 | A bright braille arc with a fading tail circling an oval. | +| `glitch` | 1×1 | A still `◆` with a glitching word. | +| `signal` | 10×2 | A braille wave trace that scrolls and is brightest at its right edge. | The shimmer is a wave of color across the spinner word, between the gradient's two colors, or the accent and text colors when there is no gradient. `1` is soft and `2` is fast. diff --git a/installer/internal/packs/packs.json b/installer/internal/packs/packs.json index 85f6c56c..0ea5647f 100644 --- a/installer/internal/packs/packs.json +++ b/installer/internal/packs/packs.json @@ -2589,6 +2589,2342 @@ ] ] ] + }, + { + "id": "scanline", + "name": "scanline sweep", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ] + ] + }, + { + "id": "ring", + "name": "angel ring", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": "⠠", + "fg": "#7f7f7f" + }, + { + "ch": "⠂", + "fg": "#9f9f9f" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#dfdfdf" + } + ], + [ + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#5f5f5f" + }, + { + "ch": "⡀", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#5f5f5f" + }, + { + "ch": "⠂", + "fg": "#7f7f7f" + }, + { + "ch": "⠈", + "fg": "#9f9f9f" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#dfdfdf" + } + ], + [ + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⠁", + "fg": "#7f7f7f" + }, + { + "ch": "⠈", + "fg": "#9f9f9f" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + } + ], + [ + { + "ch": "⠈", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#dfdfdf" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#3f3f3f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠈", + "fg": "#7f7f7f" + }, + { + "ch": "⠐", + "fg": "#9f9f9f" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#dfdfdf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#7f7f7f" + }, + { + "ch": "⡀", + "fg": "#9f9f9f" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#dfdfdf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#dfdfdf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#9f9f9f" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#5f5f5f" + } + ], + [ + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#dfdfdf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#9f9f9f" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#3f3f3f" + }, + { + "ch": "⢄", + "fg": "#5f5f5f" + } + ], + [ + { + "ch": "⠐", + "fg": "#dfdfdf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#9f9f9f" + }, + { + "ch": "⠂", + "fg": "#7f7f7f" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#5f5f5f" + } + ], + [ + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#9f9f9f" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#dfdfdf" + }, + { + "ch": "⠐", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#3f3f3f" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#9f9f9f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#7f7f7f" + }, + { + "ch": "⠠", + "fg": "#5f5f5f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠐", + "fg": "#dfdfdf" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + }, + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⠠", + "fg": "#5f5f5f" + }, + { + "ch": "⠁", + "fg": "#3f3f3f" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#dfdfdf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#9f9f9f" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + }, + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⢀", + "fg": "#5f5f5f" + }, + { + "ch": "⠠", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ] + ] + }, + { + "id": "glitch", + "name": "glitch word", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ] + ] + }, + { + "id": "signal", + "name": "signal trace", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + } + ] + ] + ] } ], "clawd": { diff --git a/installer/internal/packs/packs_test.go b/installer/internal/packs/packs_test.go index ae6fb761..61eaa4ae 100644 --- a/installer/internal/packs/packs_test.go +++ b/installer/internal/packs/packs_test.go @@ -52,7 +52,7 @@ func TestThemesSpinnersAndClawd(t *testing.T) { if d, ok := ThemeByName("dusk"); !ok || d.Colors.Accent != "#b69cff" || d.Colors.Panel == "" { t.Fatalf("dusk = %+v", d) } - if want := []string{"stock", "comet", "eyes", "orb-states", "clawd", "shimmer"}; !slices.Equal(SpinnerIDs(), want) { + if want := []string{"stock", "comet", "eyes", "orb-states", "clawd", "shimmer", "scanline", "ring", "glitch", "signal"}; !slices.Equal(SpinnerIDs(), want) { t.Fatalf("SpinnerIDs() = %v, want %v", SpinnerIDs(), want) } for _, s := range Spinners() { diff --git a/installer/internal/tui/model.go b/installer/internal/tui/model.go index 004a767c..47de08fc 100644 --- a/installer/internal/tui/model.go +++ b/installer/internal/tui/model.go @@ -18,9 +18,9 @@ import ( const ( FormWidth = 30 // FormRows is the height the form column is padded to. The groups differ in height - // (four packs, seven themes) and bubbletea's inline renderer strands a line each - // time the view gets shorter, so the form never draws less than this. - FormRows = 14 + // (four packs, seven themes, eleven spinner options) and bubbletea's inline renderer + // strands a line each time the view gets shorter, so the form never draws less than this. + FormRows = 16 // With room for all three columns Clawd and his bubble stand left of the form. // Below it the form and preview sit side by side, and below that the preview diff --git a/skills/glowup-pack/reference.md b/skills/glowup-pack/reference.md index 55c543ff..b48d3c84 100644 --- a/skills/glowup-pack/reference.md +++ b/skills/glowup-pack/reference.md @@ -48,7 +48,7 @@ A pack is a JSON file at `~/.claude/glowup/packs/.json` (`$CLAUDE_CONFIG_D | Field | Values | Meaning | | --- | --- | --- | -| `spinner` | `stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer` | The spinner. `stock` is Claude Code's own. Use only these: an id glowup lacks falls back to `stock` with a notice. Default `stock`. | +| `spinner` | `stock`, `comet`, `eyes`, `orb-states`, `clawd`, `shimmer`, `scanline`, `ring`, `glitch`, `signal` | The spinner. `stock` is Claude Code's own. Use only these: an id glowup lacks falls back to `stock` with a notice. Default `stock`. `scanline` and `glitch` also light or glitch the spinner word. | | `shimmer` | `0`, `1`, `2` | Shimmer across the spinner word: none, soft, fast. Default `1`. | | `color` | `#rrggbb` | Spinner color. Default: the palette's `accent`. | | `field` | `none`, `warp`, `simplex`, or an object | An animated texture in the docked pane's open rows, between the tab and the status box, dithered into braille dots of one color, the palette's `faint` unless `field.color` sets it. `simplex` is Paper's dithering shader's simplex shape, and Paper settings for it carry over as they are, except `size`; `warp` is noise folded through itself. A name uses the default knobs; an object takes a `shape` and any of the knobs below. It holds still under reduced motion. Default `none`. | diff --git a/test/command.test.ts b/test/command.test.ts index 79344d35..6e917dd4 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -306,7 +306,7 @@ test('an unknown spinner is refused and lists the valid ones', async () => { const { host, store } = fakeHost() const { calls, ctl: c } = ctl() const out = await runCommand(host, 'spinner nope', c) - for (const n of ['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer']) expect(out).toContain(n) + for (const n of ['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer', 'scanline', 'ring', 'glitch', 'signal']) expect(out).toContain(n) expect(store.mix).toBeUndefined() expect(calls).toEqual([]) }) diff --git a/test/packs.test.ts b/test/packs.test.ts index f55a6c89..40724154 100644 --- a/test/packs.test.ts +++ b/test/packs.test.ts @@ -154,7 +154,7 @@ test('exportMix description is built from shown names and fits 80 characters', a test('stockMotion freezes motion', async () => { expect(stockMotion(look(pack('arcade')).look).motion).toEqual({ spinner: 'stock', shimmer: 0, color: '#38e8ff', field: { shape: 'none', ...FIELD_DEFAULTS } }) - expect(SPINNER_IDS).toHaveLength(6) + expect([...SPINNER_IDS]).toEqual(['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer', 'scanline', 'ring', 'glitch', 'signal']) }) test('exportName prefixes a built-in name and keeps any other', () => { From fbb4b741533c9469a87a2d5c3aa4641b23028450 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:23:52 +0300 Subject: [PATCH 04/12] refactor: number turns and tools in hooks/turns.ts Signed-off-by: NovusEdge --- hooks/register.tsx | 9 +++------ hooks/turns.ts | 21 +++++++++++++++++++++ test/turns.test.ts | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 hooks/turns.ts create mode 100644 test/turns.test.ts diff --git a/hooks/register.tsx b/hooks/register.tsx index e74cdebc..15846288 100644 --- a/hooks/register.tsx +++ b/hooks/register.tsx @@ -18,6 +18,7 @@ import { renderBand } from './band.tsx' import { renderPane, bubbleBox, petStripCols, type PaneExtra, type PaneView, type TabId } from './pane.tsx' import { spinnerWord, newTurnWord } from './restyle.ts' import { styleRow } from './rows.tsx' +import { makeTurns } from './turns.ts' import { orbStateOf, usesOwnSpinner, checkedSpinnerProps } from './spinner.ts' import type { PetClientProps } from './client/pet.tsx' import type { OrbState } from './motion.ts' @@ -227,20 +228,16 @@ function ask($: Engine, slot: Asked, key: string, call: () => Promise() +const turns = makeTurns() const dividerByMessage = new Map() const palette = () => ({ ...theme.colors }) // What a held answer was asked under: both layers' packs and the effects the look picked, // since a mix can take its colors and its motion from different packs. const lookKey = () => JSON.stringify([mix.colors, mix.motion, look.meters, look.dividers, look.motion.field]) // Asked while drawing: one short call per prompt and pack, kept for its redraws. -// A prompt first draws under the id "placeholder" until it is stored: it shows the number the -// stored row will take, but neither claims it nor is kept. async function dividerFor($: Engine, messageId: string): Promise { const pending = messageId === 'placeholder' - if (!pending && !turnByMessage.has(messageId)) turnByMessage.set(messageId, turnByMessage.size + 1) - const turn = turnByMessage.get(messageId) ?? turnByMessage.size + 1, pack = mix.colors, colors = palette() + const turn = turns.turnFor(messageId), pack = mix.colors, colors = palette() const key = JSON.stringify([lookKey(), colors]) const held = pending ? undefined : dividerByMessage.get(messageId) if (held?.key === key) return held.value diff --git a/hooks/turns.ts b/hooks/turns.ts new file mode 100644 index 00000000..d5014b0f --- /dev/null +++ b/hooks/turns.ts @@ -0,0 +1,21 @@ +// Turns are numbered in the order their prompts are first drawn, which is transcript order. A +// prompt first draws under the id "placeholder" until it is stored: it shows the number the stored +// row will take, but neither claims it nor is kept. Claude Code redraws old rows on every +// invalidate, so a tool keeps the number it first drew with. +export function makeTurns() { + const turnByMessage = new Map() + const seqByTool = new Map() + let turn = 0, inTurn = 0 + return { + turnFor(messageId: string): number { + if (messageId === 'placeholder') return turnByMessage.size + 1 + if (!turnByMessage.has(messageId)) turnByMessage.set(messageId, turnByMessage.size + 1) + return turnByMessage.get(messageId)! + }, + toolSeq(toolId: string): number { + if (turnByMessage.size !== turn) { turn = turnByMessage.size; inTurn = 0 } + if (!seqByTool.has(toolId)) seqByTool.set(toolId, ++inTurn) + return seqByTool.get(toolId)! + }, + } +} diff --git a/test/turns.test.ts b/test/turns.test.ts new file mode 100644 index 00000000..f0ae27b2 --- /dev/null +++ b/test/turns.test.ts @@ -0,0 +1,33 @@ +import { test, expect } from 'claude-code/testing' +import { makeTurns } from '../hooks/turns.ts' + +test('turns number prompts in first-drawn order, and a redraw keeps its number', async () => { + const t = makeTurns() + expect(t.turnFor('p1')).toBe(1) + expect(t.turnFor('p2')).toBe(2) + expect(t.turnFor('p1')).toBe(1) +}) + +test('a placeholder shows the next number without claiming it', async () => { + const t = makeTurns() + t.turnFor('p1') + expect(t.turnFor('placeholder')).toBe(2) + expect(t.turnFor('placeholder')).toBe(2) + expect(t.turnFor('p2')).toBe(2) +}) + +test('tools count from 1 within each turn', async () => { + const t = makeTurns() + t.turnFor('p1') + expect([t.toolSeq('a'), t.toolSeq('b'), t.toolSeq('c')]).toEqual([1, 2, 3]) + t.turnFor('p2') + expect([t.toolSeq('d'), t.toolSeq('e')]).toEqual([1, 2]) +}) + +test('a tool redrawn turns later keeps the number it first showed', async () => { + const t = makeTurns() + t.turnFor('p1'); t.toolSeq('a'); t.toolSeq('b') + t.turnFor('p2'); t.toolSeq('c') + expect(t.toolSeq('b')).toBe(2) + expect(t.toolSeq('d')).toBe(2) +}) From 786f0bb53c55caf6c0df69f1d6de2a4daf22b634 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:32:27 +0300 Subject: [PATCH 05/12] feat(rows): slab row style Signed-off-by: NovusEdge --- docs/pack-reference.md | 3 +- hooks/packs.ts | 2 +- hooks/register.tsx | 4 +-- hooks/rows-text.ts | 6 ++++ hooks/rows.tsx | 52 +++++++++++++++++++++++++--- skills/glowup-pack/reference.md | 2 +- test/rows.test.ts | 60 +++++++++++++++++++++++++++++++++ 7 files changed, 120 insertions(+), 9 deletions(-) diff --git a/docs/pack-reference.md b/docs/pack-reference.md index a0cc859c..34f6220e 100644 --- a/docs/pack-reference.md +++ b/docs/pack-reference.md @@ -19,7 +19,7 @@ Packs are read from `~/.claude/glowup/packs`, or `$CLAUDE_CONFIG_DIR/glowup/pack "theme": "classic", // a theme to start the palette from "palette": { "accent": "#ff3ec8", "text": "#f0e6ff" }, "bg": "#1a0b33", // what braille and half-block fades blend toward - "rows": "cards", // classic | cards | minimal | retro + "rows": "cards", // classic | cards | minimal | retro | slab "border": "bold", // round | single | double | bold | classic "borderColor": "#ff3ec8", "gradient": ["#ff3ec8", "#38e8ff"], @@ -82,6 +82,7 @@ Styles wrap or prefix a transcript row; the content Claude Code draws inside it | `cards` | An accent side bar `▎` and a `you` label | A dim side bar, and a `claude` label on the first block of a reply | A dim bordered card with a mark: `✓`, `✗`, `■` for interrupted, `…` while running | | `minimal` | A `› text` line | Claude Code's | A dim one-line `· Read src/auth.ts` once it finishes cleanly | | `retro` | A `[YOU]` tag on its own line above your prompt | A `[CLAUDE]` tag on the first block | A `[READ ]`-style tag, then `[ OK ]`, `[FAIL]`, `[STOP]` or `[....]` | +| `slab` | A full-width bar in `accent` with your prompt in the background color and `PROMPT 03` on the right | An 8-cell `─` rule in `accent` above the first block | `01 READ ` before the row, then `OK`, `FAIL`, `STOP` or `…` on the right; results indented under their call | Not styled: notifications, messages from other agents, prompts expanded with ctrl+o or `--verbose`, the folded "Read 3 files" line, and tool progress. Outside fullscreen, rows already in scrollback keep the style they were printed with. diff --git a/hooks/packs.ts b/hooks/packs.ts index 5763cdb9..27c9816c 100644 --- a/hooks/packs.ts +++ b/hooks/packs.ts @@ -5,7 +5,7 @@ import type { PetTint } from './pets.ts' export const SPINNER_IDS = ['stock', 'comet', 'eyes', 'orb-states', 'clawd', 'shimmer', 'scanline', 'ring', 'glitch', 'signal'] as const export type SpinnerId = (typeof SPINNER_IDS)[number] -export const ROW_STYLES = ['classic', 'cards', 'minimal', 'retro'] as const +export const ROW_STYLES = ['classic', 'cards', 'minimal', 'retro', 'slab'] as const export type RowStyle = (typeof ROW_STYLES)[number] export const BORDERS = ['round', 'single', 'double', 'bold', 'classic'] as const export type Border = (typeof BORDERS)[number] diff --git a/hooks/register.tsx b/hooks/register.tsx index 15846288..07838808 100644 --- a/hooks/register.tsx +++ b/hooks/register.tsx @@ -1059,7 +1059,7 @@ export const register: Register = (on, options) => { const p = e.props const own = p.origin.kind === 'composer' && !p.from && !p.task const els = $.ui.resolve(e) - const styled = styleRow(els, look, { site: 'UserMessage', text: p.text, isExpanded: p.isExpanded, own }, row) as RenderElement + const styled = styleRow(els, look, { site: 'UserMessage', text: p.text, isExpanded: p.isExpanded, own, turn: own && !p.isExpanded ? turns.turnFor(e.requestId) : undefined }, row) as RenderElement if (!own || p.isExpanded) return styled const rule = await dividerFor($, e.requestId) if (!rule) return styled @@ -1090,7 +1090,7 @@ export const register: Register = (on, options) => { const row = await next(e) if (e.surface !== 'terminal') return row const p = e.props - return styleRow($.ui.resolve(e), look, { site: 'ToolUse', tool: p.tool, input: p.input, isRunning: p.isRunning, isErrored: p.isErrored, isInterrupted: p.isInterrupted }, row) as RenderElement + return styleRow($.ui.resolve(e), look, { site: 'ToolUse', tool: p.tool, input: p.input, isRunning: p.isRunning, isErrored: p.isErrored, isInterrupted: p.isInterrupted, seq: turns.toolSeq(e.requestId) }, row) as RenderElement }) on('ui.render', { component: 'ToolResult' }, async ($, e, next) => { if (off) return next(e) diff --git a/hooks/rows-text.ts b/hooks/rows-text.ts index 19054319..8774230a 100644 --- a/hooks/rows-text.ts +++ b/hooks/rows-text.ts @@ -1,7 +1,13 @@ // JSX-free: rows.tsx and the docs gallery draw the same marks and tags. +export const SLAB_MARKS = { done: 'OK', fail: 'FAIL', stop: 'STOP', run: '…' } + export const MARKS = { cards: { done: '✓', fail: '✗', stop: '■', run: '…' }, retro: { done: '[ OK ]', fail: '[FAIL]', stop: '[STOP]', run: '[....]' }, + slab: SLAB_MARKS, } +// An MCP tool's name is mcp__server__tool; the last part is the one a reader knows. +export const slabTag = (tool: string): string => (tool.startsWith('mcp__') ? tool.split('__').at(-1)! : tool).toUpperCase().slice(0, 6).padEnd(6) + export const retroTag = (tool: string): string => `[${tool.toUpperCase().slice(0, 6).padEnd(6)}] ` diff --git a/hooks/rows.tsx b/hooks/rows.tsx index d87052bb..c53dde09 100644 --- a/hooks/rows.tsx +++ b/hooks/rows.tsx @@ -1,13 +1,13 @@ import type { Look } from './packs.ts' -import { gradient } from './color.ts' +import { gradient, mix } from './color.ts' import { describeTool } from './events.ts' import { toolGlyph } from './restyle.ts' -import { MARKS, retroTag } from './rows-text.ts' +import { MARKS, retroTag, slabTag } from './rows-text.ts' export type RowInput = - | { site: 'ToolUse'; tool: string; input: unknown; isRunning: boolean; isErrored: boolean; isInterrupted: boolean } + | { site: 'ToolUse'; tool: string; input: unknown; isRunning: boolean; isErrored: boolean; isInterrupted: boolean; seq?: number } | { site: 'ToolResult' } - | { site: 'UserMessage'; text: string; isExpanded: boolean; own: boolean } + | { site: 'UserMessage'; text: string; isExpanded: boolean; own: boolean; turn?: number } | { site: 'AssistantMessage'; isFirstOfReply: boolean; xp?: number } type Els = { Box: any; Text: any } @@ -71,6 +71,18 @@ function xpTag({ Box, Text }: Els, xp: number, color: string) { return {`+${xp} XP`} } +function roleColor(look: Look, tool: string, input: Record): string { + const c = look.theme.colors + switch (describeTool(tool, input).kind) { + case 'read': case 'search': return c.read + case 'edit': return c.edit + case 'shell': return c.shell + case 'agent': return c.agent + case 'plan': return c.accent + default: return c.dim + } +} + export function styleRow(els: Els, look: Look, row: RowInput, engine: unknown, opts: { prefixCards?: boolean } = {}): unknown { const out = draw(els, look, row, engine, opts) if (look.rows === 'classic') return out @@ -128,6 +140,38 @@ function draw(els: Els, look: Look, row: RowInput, engine: unknown, opts: { pref const m = mark(look, row, MARKS.retro) return {label(els, retroTag(row.tool), look, c.accent)}{shrinker(Box, engine)}{fixed(els, m.color, ' ' + m.mark)} } + + case 'slab': { + if (row.site === 'UserMessage') { + // The engine row opens with a blank margin line (see card()); marginTop keeps that gap, + // where a divider overlay can still sit. + return ( + + {' ' + row.text} + {row.turn ? {` PROMPT ${String(row.turn).padStart(2, '0')} `} : null} + + ) + } + if (row.site === 'ToolResult') return {engine} + if (row.site === 'AssistantMessage') { + if (!row.isFirstOfReply) return engine + return ( + + {engine} + {'─'.repeat(8)} + + ) + } + const m = mark(look, row, MARKS.slab) + const n = row.seq ? String(row.seq).padStart(2, '0') + ' ' : '' + return ( + + {n}{slabTag(row.tool)}{' '} + {shrinker(Box, engine)} + {fixed(els, m.color, ' ' + m.mark)} + + ) + } } return engine } catch { diff --git a/skills/glowup-pack/reference.md b/skills/glowup-pack/reference.md index b48d3c84..75427c79 100644 --- a/skills/glowup-pack/reference.md +++ b/skills/glowup-pack/reference.md @@ -24,7 +24,7 @@ A pack is a JSON file at `~/.claude/glowup/packs/.json` (`$CLAUDE_CONFIG_D | `theme` | a theme name | Start the palette from this theme: `classic`, `glowup`, `aurora`, `dusk`, `cyberpunk`, `vaporwave`, `high-contrast`, or a file in `~/.claude/glowup/themes`. Default `classic`. | | `palette` | object of theme color keys | Colors that replace the theme's. Keys are listed under Theme colors. Set only what differs. | | `bg` | `#rrggbb` | The terminal background glowup fades toward. Match the user's terminal background. Default: the palette's `panel`. | -| `rows` | `classic`, `cards`, `minimal`, `retro` | Transcript row style. `classic` leaves rows alone, `cards` draws side bars and bordered tool cards, `minimal` is quiet one-liners, `retro` uses `[TAG]` labels. Default `classic`. | +| `rows` | `classic`, `cards`, `minimal`, `retro`, `slab` | Transcript row style. `classic` leaves rows alone, `cards` draws side bars and bordered tool cards, `minimal` is quiet one-liners, `retro` uses `[TAG]` labels, `slab` draws your prompt as an accent bar numbered by turn and numbers each tool call. Default `classic`. | | `border` | `round`, `single`, `double`, `bold`, `classic` | Border shape around cards. Also the shape of the status box and of the pane's section boxes. Default `round`. | | `borderColor` | `#rrggbb` | Border color. Default: the palette's `faint`. | | `gradient` | array of exactly two `#rrggbb` | Start and end color of the card gradient and spinner shimmer. Default: none. | diff --git a/test/rows.test.ts b/test/rows.test.ts index 61393552..1fcbd17e 100644 --- a/test/rows.test.ts +++ b/test/rows.test.ts @@ -265,3 +265,63 @@ test('tool header marks and tags never shrink or wrap; the engine box gives up w const tag = walk(styleRow(els, look('crt'), tool(), ENGINE)).find(x => x.type === 'Box' && x.props?.flexShrink === 0 && text(x).startsWith('[READ')) expect(tag).toBeDefined() }) + +const slab = (): Look => ({ ...look('classic'), rows: 'slab' }) + +test('slab: your prompt is an accent bar with its own text and the turn number', async () => { + const l = slab(), c = l.theme.colors + const u = inner(styleRow(els, l, { ...user(), turn: 3 } as RowInput, ENGINE)) + expect(hasEngine(u)).toBe(false) + expect(walk(u).some(x => x.props?.backgroundColor === c.accent)).toBe(true) + expect(text(u)).toContain('fix the test') + expect(text(u)).toContain('PROMPT 03') + const prompt = walk(u).find(x => x.type === 'Text' && text(x).includes('fix the test')) + expect(prompt.props.color).toBe(l.bg) +}) + +test('slab: a long prompt keeps all its text and is never truncated', async () => { + const long = 'make the slab bar wrap '.repeat(20).trim() + const u = styleRow(els, slab(), { ...user({ text: long }), turn: 1 } as RowInput, ENGINE) + expect(text(u)).toContain(long) + expect(walk(u).some(x => x.props?.wrap === 'truncate' && text(x).includes('make the slab'))).toBe(false) +}) + +test('slab: an expanded or foreign prompt stays the engine row', async () => { + for (const r of [user({ isExpanded: true }), user({ own: false })]) expect(inner(styleRow(els, slab(), r, ENGINE))).toBe(ENGINE) +}) + +test('slab: tool rows carry the number, the upper-case tool in its role color, the engine line and the mark', async () => { + const l = slab(), c = l.theme.colors + const cases: [Partial>, string, string][] = [ + [{}, 'OK', c.pass], + [{ isErrored: true }, 'FAIL', c.fail], + [{ isInterrupted: true }, 'STOP', c.dim], + [{ isRunning: true }, '…', c.dim], + ] + for (const [o, mark, color] of cases) { + const r = styleRow(els, l, { ...tool(o), seq: 2 } as RowInput, ENGINE) + expect(hasEngine(r)).toBe(true) + expect(text(r)).toContain('02') + expect(text(r)).toContain('READ') + expect(walk(r).find(x => x.type === 'Text' && text(x) === 'READ ')?.props.color).toBe(c.read) + expect(walk(r).find(x => x.type === 'Text' && text(x).trim() === mark)?.props.color).toBe(color) + } + const bash = styleRow(els, l, { ...tool({ tool: 'Bash', input: { command: 'pnpm test' } }), seq: 1 } as RowInput, ENGINE) + expect(walk(bash).find(x => x.type === 'Text' && text(x) === 'BASH ')?.props.color).toBe(c.shell) +}) + +test('slab: tool results sit indented under their call', async () => { + const r = inner(styleRow(els, slab(), { site: 'ToolResult' }, ENGINE)) as any + expect(r.props.paddingLeft).toBe(4) + expect(hasEngine(r)).toBe(true) +}) + +test('slab: the first block of a reply gets an accent rule on its margin line, later blocks nothing', async () => { + const l = slab() + const first = styleRow(els, l, asst(true), ENGINE) + const rule = walk(first).find(x => x.props?.position === 'absolute') + expect([rule.props.top, rule.props.left]).toEqual([0, 0]) + expect(text(rule)).toBe('────────') + expect(hasEngine(first)).toBe(true) + expect(inner(styleRow(els, l, asst(false), ENGINE))).toBe(ENGINE) +}) From 199b86b59f4a53058e81d0af8c179878eff7e918 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 11:35:05 +0300 Subject: [PATCH 06/12] docs(changelog): spinners and the slab row style Signed-off-by: NovusEdge --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0baac29..c88df6cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Four spinners: `scanline` sweeps a bright column across a checkered bar and on through the spinner word, `ring` circles nine dots round an ellipse, `glitch` flickers the spinner word's letters into blocks in the pack's `read` and `agent` colors, and `signal` scrolls an oscilloscope trace. Each speeds up or changes shape while commands run or subagents work. Pick one with `motion.spinner` or `/glowup spinner `. +- A `slab` row style: your prompt is a full-width bar in the pack's accent, numbered by turn; tool calls are numbered within the turn with the tool in capitals and `OK` or `FAIL` on the right; a short rule opens each reply. + ## [0.9.0] - 2026-10-07 ### Added From 6764981823d599475291adc1692290439de9d664 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 12:03:20 +0300 Subject: [PATCH 07/12] Slab rows: result indent fits the width budget, prompt padding on every line, guards cover slab Signed-off-by: NovusEdge --- hooks/rows.tsx | 4 ++-- test/engine-tree.test.ts | 6 +++--- test/rows.test.ts | 8 +++++--- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/hooks/rows.tsx b/hooks/rows.tsx index c53dde09..4d18960c 100644 --- a/hooks/rows.tsx +++ b/hooks/rows.tsx @@ -147,12 +147,12 @@ function draw(els: Els, look: Look, row: RowInput, engine: unknown, opts: { pref // where a divider overlay can still sit. return ( - {' ' + row.text} + {row.text} {row.turn ? {` PROMPT ${String(row.turn).padStart(2, '0')} `} : null} ) } - if (row.site === 'ToolResult') return {engine} + if (row.site === 'ToolResult') return {engine} if (row.site === 'AssistantMessage') { if (!row.isFirstOfReply) return engine return ( diff --git a/test/engine-tree.test.ts b/test/engine-tree.test.ts index 6b963a6b..c75388ab 100644 --- a/test/engine-tree.test.ts +++ b/test/engine-tree.test.ts @@ -2,7 +2,7 @@ import { expect, mock } from 'claude-code/testing' import { fakeFs, runGlowup, test } from './kit.ts' import { REFUSED_ABOVE_ENGINE, engineViolations } from './engine-tree.ts' import { styleRow, type RowInput } from '../hooks/rows.tsx' -import { resolveLook, type Look } from '../hooks/packs.ts' +import { ROW_STYLES, resolveLook, type Look } from '../hooks/packs.ts' import { PACKS } from '../hooks/packpresets.ts' const ENGINE = { type: 'engine', ref: 0 } @@ -58,7 +58,7 @@ test('no row style, site or pack puts a refused Box prop above the engine node', let checked = 0 for (const name of Object.keys(PACKS)) { const base = resolveLook({ colors: name, motion: name }, {}, {}).look - for (const rows of ['classic', 'cards', 'minimal', 'retro'] as const) { + for (const rows of ROW_STYLES) { const look: Look = { ...base, rows } for (const prefixCards of [false, true]) { for (const row of rowsFor()) { @@ -69,7 +69,7 @@ test('no row style, site or pack puts a refused Box prop above the engine node', } } } - expect(checked).toBe(Object.keys(PACKS).length * 4 * 2 * rowsFor().length) + expect(checked).toBe(Object.keys(PACKS).length * ROW_STYLES.length * 2 * rowsFor().length) }) // The engine's own validator over the whole hook: what the owner's session ran. diff --git a/test/rows.test.ts b/test/rows.test.ts index 1fcbd17e..59bdb94d 100644 --- a/test/rows.test.ts +++ b/test/rows.test.ts @@ -179,7 +179,7 @@ test('prefixCards leaves message rows as they are without it', async () => { test('non-classic styles indent every row one column, classic does not', async () => { const rows: RowInput[] = [user(), user({ own: false }), asst(), asst(false), tool(), tool({ isErrored: true }), { site: 'ToolResult' }] - for (const style of ['cards', 'minimal', 'retro'] as const) { + for (const style of ['cards', 'minimal', 'retro', 'slab'] as const) { const l: Look = { ...look('arcade'), rows: style } for (const r of rows) { const s = styleRow(els, l, r, ENGINE) as any @@ -226,7 +226,7 @@ function beside(n: any): number { test('width budget: ToolResult and UserMessage rows add at most 4 columns beside the engine', async () => { for (const name of ['cozy', 'arcade', 'crt']) { - for (const rows of ['cards', 'retro', 'minimal'] as const) { + for (const rows of ['cards', 'retro', 'minimal', 'slab'] as const) { const l: Look = { ...look(name), rows } for (const r of [user(), asst(), asst(false), { site: 'ToolResult' } as RowInput]) { const s = styleRow(els, l, r, ENGINE) @@ -277,6 +277,8 @@ test('slab: your prompt is an accent bar with its own text and the turn number', expect(text(u)).toContain('PROMPT 03') const prompt = walk(u).find(x => x.type === 'Text' && text(x).includes('fix the test')) expect(prompt.props.color).toBe(l.bg) + expect(text(prompt)).toBe('fix the test') + expect(walk(u).find(x => x.type === 'Box' && x.children?.includes(prompt)).props.paddingX).toBe(1) }) test('slab: a long prompt keeps all its text and is never truncated', async () => { @@ -312,7 +314,7 @@ test('slab: tool rows carry the number, the upper-case tool in its role color, t test('slab: tool results sit indented under their call', async () => { const r = inner(styleRow(els, slab(), { site: 'ToolResult' }, ENGINE)) as any - expect(r.props.paddingLeft).toBe(4) + expect(r.props.paddingLeft).toBe(3) expect(hasEngine(r)).toBe(true) }) From 792264ff6ba3922b172e7ecd2ec75d21f727527e Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 12:03:20 +0300 Subject: [PATCH 08/12] Glitch word: keep the line's cell width with wide and zero-width letters Signed-off-by: NovusEdge --- hooks/motion.ts | 10 ++++++---- test/motion.test.ts | 9 +++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/hooks/motion.ts b/hooks/motion.ts index d10104f0..5eb4f7eb 100644 --- a/hooks/motion.ts +++ b/hooks/motion.ts @@ -1,4 +1,5 @@ // JSX-free: the docs site and Client modules import it. +import { cellWidth } from './cells.ts' import { gradient, mix, wave, wave3, type Span } from './color.ts' export type Cell = { ch: string; fg: string; bg?: string } @@ -216,17 +217,18 @@ function scanWord(look: WordLook, text: string, t: number, st: OrbState): Span[] } // Seeded by the 70 ms frame, so every redraw within a frame draws the same glitch. A tear repeats -// the first letter in front and drops the trailing space: the line never changes length. +// the first letter in front and drops the trailing space: the line never changes width. Only +// width-1 letters become blocks or get torn; wide and zero-width ones take the background variant. function glitchWord(look: WordLook, text: string, t: number, st: OrbState): Span[] { const c = look.theme.colors, a = c.read ?? c.accent, b = c.agent ?? c.text let seed = (Math.floor(Math.max(0, t || 0) / 70) * 9301 + 49297) % 233280 const rnd = () => (seed = (seed * 9301 + 49297) % 233280) / 233280 const k = st === 'run' || st === 'agents' ? 2 : 1, chars = [...text], out: Span[] = [] - const tear = rnd() < 0.12 - if (tear) out.push({ text: chars[0] ?? ' ', color: b }) + const tear = rnd() < 0.12 && chars[0] !== undefined && cellWidth(chars[0].codePointAt(0)!) === 1 + if (tear) out.push({ text: chars[0]!, color: b }) for (const ch of chars) { const r = rnd(), pick = rnd() < 0.5 ? a : b, block = '▓▒░█▚'[Math.floor(rnd() * 5)]! - if (r < 0.10 * k) out.push({ text: block, color: pick }) + if (r < 0.10 * k && cellWidth(ch.codePointAt(0)!) === 1) out.push({ text: block, color: pick }) else if (r < 0.16 * k) out.push({ text: ch, color: look.bg, bg: pick }) else out.push({ text: ch, color: c.text, bold: true }) } diff --git a/test/motion.test.ts b/test/motion.test.ts index c2a320dc..08c27463 100644 --- a/test/motion.test.ts +++ b/test/motion.test.ts @@ -159,6 +159,15 @@ test('glitch word: never changes the line length and keeps letters in place', as expect(swapped).toBeGreaterThan(0) }) +test('glitch word: the line keeps its cell width with wide and zero-width letters', async () => { + const width = (spans: { text: string }[]) => [...joined(spans)].reduce((s, ch) => s + cellWidth(ch.codePointAt(0)!), 0) + for (const word of ['Thé漢字ing…', '漢字Thinking']) { + const widths = new Set() + for (let f = 0; f < 400; f++) widths.add(width(spinnerWordSpans(WL('glitch'), word, 0, { t: f * 70, st: 'run' }))) + expect([word, [...widths]]).toEqual([word, [width([{ text: word + ' ' }])]]) + } +}) + test('glitch word: a frame is the same on every redraw within its 70 ms', async () => { const at = (t: number) => JSON.stringify(spinnerWordSpans(WL('glitch'), 'Thinking…', 0, { t, st: 'think' })) expect(at(140)).toBe(at(209)) From b35d1c902b826739b54d59476ea4d0fe28385c0b Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 12:03:20 +0300 Subject: [PATCH 09/12] Landing gallery: drop glitch, whose effect lives in the word Signed-off-by: NovusEdge --- docs/web/app/landing/SpinnersSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/web/app/landing/SpinnersSection.tsx b/docs/web/app/landing/SpinnersSection.tsx index 1ec2a5b6..12a59d47 100644 --- a/docs/web/app/landing/SpinnersSection.tsx +++ b/docs/web/app/landing/SpinnersSection.tsx @@ -6,7 +6,7 @@ import { usePack } from './PackContext.tsx' import { SpinnerCanvas } from './SpinnerCanvas.tsx' const STATES: OrbState[] = ['think', 'search', 'work', 'run', 'agents'] -const IDS = (Object.keys(SPINNERS) as SpinnerId[]).filter(id => id !== 'shimmer') +const IDS = (Object.keys(SPINNERS) as SpinnerId[]).filter(id => id !== 'shimmer' && id !== 'glitch') export function SpinnersSection() { const { look } = usePack() From 5e15469bfbe9693a920db74d87adf02e547e5653 Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 12:03:20 +0300 Subject: [PATCH 10/12] Installer preview: export one whole period for scanline, ring and signal Signed-off-by: NovusEdge --- hooks/packexport.ts | 12 +- installer/internal/packs/packs.json | 6904 ++++++++++++++++++++++++--- test/packexport.test.ts | 18 + 3 files changed, 6326 insertions(+), 608 deletions(-) diff --git a/hooks/packexport.ts b/hooks/packexport.ts index 5db27e12..485a3b75 100644 --- a/hooks/packexport.ts +++ b/hooks/packexport.ts @@ -30,7 +30,12 @@ export type ClawdExport = { cols: number; rows: PetSpan[][] } export type Export = { packs: PackExport[]; themes: ThemeExport[]; spinners: SpinnerExport[]; clawd: ClawdExport } const FRAME_MS: Record = { stock: 120, clawd: 220 } -const FRAMES: Record = { stock: 6, clawd: 4 } +const FRAMES: Record = { stock: 6, clawd: 4, scanline: 29, ring: 16, signal: 67 } +// Spinners whose motion repeats on its own period get their frames spread over exactly that period, +// so the preview wraps without a jump. scanline: bar 10 + gap 1 + the default 12-letter word + pause 6 +// cells at 45 ms. ring: one turn. signal: 10 base cycles of 160*2pi ms, the first time its 2.7x +// harmonic lines up again; 67 frames land on a whole 150 ms, so ms * frames stays within 3 ms of it. +const PERIOD_MS: Record = { scanline: 29 * 45, ring: 2000, signal: 3200 * Math.PI } const MONO = { color: '#ffffff', bg: '#000000', fg: '#ffffff' } export function packExport(): PackExport[] { @@ -60,9 +65,10 @@ export const themeExport = (): ThemeExport[] => Object.keys(PRESETS).map(name => }) export const spinnerExport = (): SpinnerExport[] => SPINNER_IDS.map(id => { - const ms = FRAME_MS[id] ?? 150 + const n = FRAMES[id] ?? 12, period = PERIOD_MS[id] + const step = period === undefined ? FRAME_MS[id] ?? 150 : period / n const s = SPINNERS[id] - return { id, name: s.name, ms, text: 'useFg' in s && s.useFg === true, frames: Array.from({ length: FRAMES[id] ?? 12 }, (_, i) => spinnerCells(id, i * ms, MONO)) } + return { id, name: s.name, ms: Math.round(step), text: 'useFg' in s && s.useFg === true, frames: Array.from({ length: n }, (_, i) => spinnerCells(id, i * step, MONO)) } }) // The idle pose's first frame, as the band draws it: no outfit, not mirrored. diff --git a/installer/internal/packs/packs.json b/installer/internal/packs/packs.json index 0ea5647f..3ecf73c3 100644 --- a/installer/internal/packs/packs.json +++ b/installer/internal/packs/packs.json @@ -2593,7 +2593,7 @@ { "id": "scanline", "name": "scanline sweep", - "ms": 150, + "ms": 45, "text": false, "frames": [ [ @@ -2644,19 +2644,19 @@ [ { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" }, { - "ch": "▚", + "ch": "█", "fg": "#ffffff" }, { "ch": "▞", - "fg": "#ffffff" + "fg": "#404040" }, { - "ch": "█", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { "ch": "▞", @@ -2688,15 +2688,15 @@ [ { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" }, { - "ch": "▞", - "fg": "#404040" + "ch": "█", + "fg": "#ffffff" }, { "ch": "▚", @@ -2704,15 +2704,15 @@ }, { "ch": "▞", - "fg": "#ffffff" + "fg": "#404040" }, { "ch": "▚", - "fg": "#ffffff" + "fg": "#404040" }, { - "ch": "█", - "fg": "#ffffff" + "ch": "▞", + "fg": "#404040" }, { "ch": "▚", @@ -2731,9 +2731,21 @@ [ [ { - "ch": "▚", + "ch": "▞", "fg": "#404040" }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, { "ch": "▞", "fg": "#404040" @@ -2757,23 +2769,31 @@ { "ch": "▚", "fg": "#404040" - }, + } + ] + ], + [ + [ { "ch": "▞", "fg": "#404040" }, { "ch": "▚", - "fg": "#ffffff" + "fg": "#404040" }, { "ch": "▞", "fg": "#ffffff" - } - ] - ], - [ - [ + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" + }, { "ch": "▚", "fg": "#404040" @@ -2793,7 +2813,11 @@ { "ch": "▚", "fg": "#404040" - }, + } + ] + ], + [ + [ { "ch": "▞", "fg": "#404040" @@ -2808,16 +2832,20 @@ }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" + }, + { + "ch": "▞", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" }, { "ch": "▞", "fg": "#404040" - } - ] - ], - [ - [ + }, { "ch": "▚", "fg": "#404040" @@ -2829,7 +2857,11 @@ { "ch": "▚", "fg": "#404040" - }, + } + ] + ], + [ + [ { "ch": "▞", "fg": "#404040" @@ -2848,7 +2880,15 @@ }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" + }, + { + "ch": "▚", + "fg": "#ffffff" + }, + { + "ch": "█", + "fg": "#ffffff" }, { "ch": "▚", @@ -2857,6 +2897,10 @@ { "ch": "▞", "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" } ] ], @@ -2884,15 +2928,15 @@ }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" }, { - "ch": "▚", - "fg": "#404040" + "ch": "█", + "fg": "#ffffff" }, { "ch": "▞", @@ -2932,15 +2976,15 @@ }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" }, { - "ch": "▞", - "fg": "#404040" + "ch": "█", + "fg": "#ffffff" }, { "ch": "▚", @@ -2980,15 +3024,15 @@ }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" }, { - "ch": "▞", - "fg": "#404040" + "ch": "█", + "fg": "#ffffff" } ] ], @@ -2996,11 +3040,11 @@ [ { "ch": "▚", - "fg": "#ffffff" + "fg": "#404040" }, { - "ch": "█", - "fg": "#ffffff" + "ch": "▞", + "fg": "#404040" }, { "ch": "▚", @@ -3028,11 +3072,11 @@ }, { "ch": "▚", - "fg": "#404040" + "fg": "#ffffff" }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" } ] ], @@ -3048,15 +3092,15 @@ }, { "ch": "▚", - "fg": "#ffffff" + "fg": "#404040" }, { "ch": "▞", - "fg": "#ffffff" + "fg": "#404040" }, { - "ch": "█", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { "ch": "▞", @@ -3076,16 +3120,12 @@ }, { "ch": "▞", - "fg": "#404040" + "fg": "#ffffff" } ] ], [ [ - { - "ch": "▞", - "fg": "#404040" - }, { "ch": "▚", "fg": "#404040" @@ -3104,15 +3144,15 @@ }, { "ch": "▚", - "fg": "#ffffff" + "fg": "#404040" }, { "ch": "▞", - "fg": "#ffffff" + "fg": "#404040" }, { - "ch": "█", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { "ch": "▞", @@ -3121,111 +3161,5721 @@ { "ch": "▚", "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" } ] - ] - ] - }, - { - "id": "ring", - "name": "angel ring", - "ms": 150, - "text": false, - "frames": [ + ], [ [ { - "ch": "⠠", - "fg": "#7f7f7f" - }, - { - "ch": "⠂", - "fg": "#9f9f9f" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠈", - "fg": "#bfbfbf" + "ch": "▞", + "fg": "#404040" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠂", - "fg": "#bfbfbf" + "ch": "▞", + "fg": "#404040" }, { - "ch": "⠄", - "fg": "#dfdfdf" - } - ], - [ - { - "ch": "⠁", - "fg": "#5f5f5f" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠄", - "fg": "#5f5f5f" + "ch": "▞", + "fg": "#404040" }, { - "ch": "⡀", - "fg": "#3f3f3f" + "ch": "▚", + "fg": "#404040" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "▞", + "fg": "#404040" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠈", - "fg": "#ffffff" + "ch": "▞", + "fg": "#404040" } ] ], [ [ { - "ch": "⢀", - "fg": "#5f5f5f" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠂", - "fg": "#7f7f7f" + "ch": "▞", + "fg": "#404040" }, { - "ch": "⠈", - "fg": "#9f9f9f" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⠐", - "fg": "#bfbfbf" + "ch": "▞", + "fg": "#404040" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "▚", + "fg": "#404040" }, { - "ch": "⢄", - "fg": "#dfdfdf" - } - ], - [ - { - "ch": "⠁", - "fg": "#5f5f5f" + "ch": "▞", + "fg": "#404040" }, { - "ch": "⠄", + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ], + [ + [ + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + }, + { + "ch": "▚", + "fg": "#404040" + }, + { + "ch": "▞", + "fg": "#404040" + } + ] + ] + ] + }, + { + "id": "ring", + "name": "angel ring", + "ms": 125, + "text": false, + "frames": [ + [ + [ + { + "ch": "⠠", + "fg": "#7f7f7f" + }, + { + "ch": "⠂", + "fg": "#9f9f9f" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#dfdfdf" + } + ], + [ + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#5f5f5f" + }, + { + "ch": "⡀", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⠂", + "fg": "#7f7f7f" + }, + { + "ch": "⠁", + "fg": "#9f9f9f" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#dfdfdf" + } + ], + [ + { + "ch": "⠈", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#5f5f5f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#7f7f7f" + }, + { + "ch": "⠁", + "fg": "#9f9f9f" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + } + ], + [ + { + "ch": "⠐", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#dfdfdf" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#5f5f5f" + }, + { + "ch": "⠈", + "fg": "#7f7f7f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#9f9f9f" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + } + ], + [ + { + "ch": "⠁", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#dfdfdf" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#3f3f3f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⠈", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#7f7f7f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#dfdfdf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#3f3f3f" + }, + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠈", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#7f7f7f" + }, + { + "ch": "⢀", + "fg": "#9f9f9f" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#dfdfdf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠁", + "fg": "#5f5f5f" + }, + { + "ch": "⠐", + "fg": "#5f5f5f" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#dfdfdf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#9f9f9f" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + }, + { + "ch": "⢄", + "fg": "#7f7f7f" + } + ], + [ + { + "ch": "⠐", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#dfdfdf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#9f9f9f" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#3f3f3f" + }, + { + "ch": "⠐", + "fg": "#5f5f5f" + }, + { + "ch": "⢀", + "fg": "#5f5f5f" + } + ], + [ + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#9f9f9f" + }, + { + "ch": "⠂", + "fg": "#7f7f7f" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#3f3f3f" + }, + { + "ch": "⡀", + "fg": "#5f5f5f" + } + ], + [ + { + "ch": "⠁", + "fg": "#dfdfdf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#9f9f9f" + }, + { + "ch": "⠠", + "fg": "#7f7f7f" + }, + { + "ch": "⠈", + "fg": "#5f5f5f" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#dfdfdf" + }, + { + "ch": "⠂", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#3f3f3f" + } + ], + [ + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#9f9f9f" + }, + { + "ch": "⠠", + "fg": "#7f7f7f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#5f5f5f" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#dfdfdf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠂", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#3f3f3f" + } + ], + [ + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠠", + "fg": "#9f9f9f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#7f7f7f" + }, + { + "ch": "⠄", + "fg": "#5f5f5f" + }, + { + "ch": "⠂", + "fg": "#5f5f5f" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⠐", + "fg": "#dfdfdf" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + }, + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⠠", + "fg": "#5f5f5f" + }, + { + "ch": "⠁", + "fg": "#3f3f3f" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#dfdfdf" + }, + { + "ch": "⠐", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#9f9f9f" + }, + { + "ch": "⠄", + "fg": "#7f7f7f" + }, + { + "ch": "⡀", + "fg": "#5f5f5f" + }, + { + "ch": "⢀", + "fg": "#5f5f5f" + }, + { + "ch": "⠠", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡀", + "fg": "#9f9f9f" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#dfdfdf" + }, + { + "ch": "⠐", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠐", + "fg": "#7f7f7f" + }, + { + "ch": "⠄", + "fg": "#5f5f5f" + }, + { + "ch": "⢀", + "fg": "#5f5f5f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠄", + "fg": "#3f3f3f" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠠", + "fg": "#9f9f9f" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠐", + "fg": "#dfdfdf" + }, + { + "ch": "⠄", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#7f7f7f" + }, + { + "ch": "⠠", + "fg": "#5f5f5f" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", "fg": "#3f3f3f" }, { "ch": " ", "fg": "#ffffff" }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ] + ] + }, + { + "id": "glitch", + "name": "glitch word", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "◆", + "fg": "#ffffff" + } + ] + ] + ] + }, + { + "id": "signal", + "name": "signal trace", + "ms": 150, + "text": false, + "frames": [ + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#ffffff" + }, + { + "ch": "⣀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠐", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#ffffff" + }, + { + "ch": "⡀", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" + } + ] + ], + [ + [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, { "ch": " ", "fg": "#ffffff" @@ -3235,7 +8885,15 @@ "fg": "#ffffff" }, { - "ch": "⠂", + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": "⠉", "fg": "#ffffff" } ] @@ -3243,34 +8901,28 @@ [ [ { - "ch": "⡀", - "fg": "#5f5f5f" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#5f5f5f" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⠁", - "fg": "#7f7f7f" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⠈", - "fg": "#9f9f9f" + "ch": "⡠", + "fg": "#bfbfbf" }, { - "ch": "⠐", + "ch": "⢄", "fg": "#bfbfbf" }, { - "ch": "⢀", + "ch": "⣀", "fg": "#bfbfbf" - } - ], - [ - { - "ch": "⠈", - "fg": "#3f3f3f" }, { "ch": " ", @@ -3285,43 +8937,23 @@ "fg": "#ffffff" }, { - "ch": "⠠", + "ch": "⡠", "fg": "#ffffff" - }, - { - "ch": "⠂", - "fg": "#dfdfdf" } - ] - ], - [ + ], [ { - "ch": "⡀", - "fg": "#3f3f3f" - }, - { - "ch": "⠂", - "fg": "#5f5f5f" - }, - { - "ch": "⠁", - "fg": "#5f5f5f" - }, - { - "ch": "⠈", - "fg": "#7f7f7f" + "ch": "⠑", + "fg": "#bfbfbf" }, { - "ch": "⠐", - "fg": "#9f9f9f" + "ch": "⠉", + "fg": "#bfbfbf" }, { - "ch": "⢀", + "ch": "⠊", "fg": "#bfbfbf" - } - ], - [ + }, { "ch": " ", "fg": "#ffffff" @@ -3335,47 +8967,49 @@ "fg": "#ffffff" }, { - "ch": "⡀", - "fg": "#ffffff" + "ch": "⠑", + "fg": "#bfbfbf" }, { - "ch": "⠠", - "fg": "#dfdfdf" + "ch": "⠊", + "fg": "#bfbfbf" }, { - "ch": "⠁", - "fg": "#bfbfbf" + "ch": "⠉", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" } ] ], [ [ { - "ch": "⠠", - "fg": "#3f3f3f" + "ch": " ", + "fg": "#ffffff" }, { "ch": " ", "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#5f5f5f" + "ch": "⠠", + "fg": "#bfbfbf" }, { - "ch": "⠁", - "fg": "#5f5f5f" + "ch": "⢄", + "fg": "#bfbfbf" }, { - "ch": "⠐", - "fg": "#7f7f7f" + "ch": "⣀", + "fg": "#bfbfbf" }, { "ch": "⡀", - "fg": "#9f9f9f" - } - ], - [ + "fg": "#bfbfbf" + }, { "ch": " ", "fg": "#ffffff" @@ -3385,79 +9019,79 @@ "fg": "#ffffff" }, { - "ch": "⡀", + "ch": "⠠", "fg": "#ffffff" }, { - "ch": "⢀", - "fg": "#dfdfdf" + "ch": "⠤", + "fg": "#ffffff" + } + ], + [ + { + "ch": "⠊", + "fg": "#bfbfbf" }, { - "ch": "⠠", + "ch": "⠑", "fg": "#bfbfbf" }, { - "ch": "⠈", + "ch": "⠁", "fg": "#bfbfbf" - } - ] - ], - [ - [ + }, + { + "ch": " ", + "fg": "#ffffff" + }, { "ch": " ", "fg": "#ffffff" }, { "ch": "⠐", - "fg": "#3f3f3f" + "fg": "#bfbfbf" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⠊", + "fg": "#bfbfbf" }, { - "ch": "⠁", - "fg": "#5f5f5f" + "ch": "⠉", + "fg": "#bfbfbf" }, { - "ch": "⠐", - "fg": "#5f5f5f" + "ch": "⠁", + "fg": "#bfbfbf" }, { - "ch": "⠄", - "fg": "#7f7f7f" + "ch": " ", + "fg": "#ffffff" } - ], + ] + ], + [ [ { "ch": " ", "fg": "#ffffff" }, { - "ch": "⠄", - "fg": "#ffffff" + "ch": "⢀", + "fg": "#bfbfbf" }, { - "ch": "⡀", - "fg": "#dfdfdf" + "ch": "⢄", + "fg": "#bfbfbf" }, { - "ch": "⢀", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⠠", + "ch": "⡀", "fg": "#bfbfbf" }, - { - "ch": "⠈", - "fg": "#9f9f9f" - } - ] - ], - [ - [ { "ch": " ", "fg": "#ffffff" @@ -3467,37 +9101,49 @@ "fg": "#ffffff" }, { - "ch": "⠈", - "fg": "#3f3f3f" + "ch": "⢀", + "fg": "#bfbfbf" }, { - "ch": " ", + "ch": "⠤", "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#5f5f5f" - }, - { - "ch": "⠄", - "fg": "#5f5f5f" + "ch": "⡀", + "fg": "#ffffff" } ], [ { - "ch": "⠈", + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" }, { - "ch": "⠄", - "fg": "#dfdfdf" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⢀", + "ch": "⠈", "fg": "#bfbfbf" }, { - "ch": "⠠", + "ch": "⠒", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠁", "fg": "#bfbfbf" }, { @@ -3505,20 +9151,58 @@ "fg": "#ffffff" }, { - "ch": "⠊", - "fg": "#9f9f9f" + "ch": "⠈", + "fg": "#ffffff" } ] ], [ [ + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⡀", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⠤", + "fg": "#bfbfbf" + }, { "ch": "⡀", - "fg": "#ffffff" + "fg": "#bfbfbf" }, { "ch": " ", "fg": "#ffffff" + } + ], + [ + { + "ch": "⠁", + "fg": "#bfbfbf" }, { "ch": " ", @@ -3529,25 +9213,19 @@ "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#3f3f3f" + "ch": "⠈", + "fg": "#bfbfbf" }, { - "ch": "⢄", - "fg": "#5f5f5f" - } - ], - [ - { - "ch": "⠐", - "fg": "#dfdfdf" + "ch": "⠒", + "fg": "#bfbfbf" }, { - "ch": "⠄", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": "⢀", + "ch": "⠁", "fg": "#bfbfbf" }, { @@ -3555,24 +9233,28 @@ "fg": "#ffffff" }, { - "ch": "⠄", - "fg": "#9f9f9f" + "ch": "⠈", + "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#7f7f7f" + "ch": "⠉", + "fg": "#ffffff" } ] ], [ [ { - "ch": "⡠", - "fg": "#ffffff" + "ch": "⠤", + "fg": "#bfbfbf" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", + "fg": "#bfbfbf" }, { "ch": " ", @@ -3587,17 +9269,11 @@ "fg": "#ffffff" }, { - "ch": "⢄", - "fg": "#5f5f5f" - } - ], - [ - { - "ch": "⠐", + "ch": "⠤", "fg": "#bfbfbf" }, { - "ch": "⠠", + "ch": "⡀", "fg": "#bfbfbf" }, { @@ -3605,29 +9281,11 @@ "fg": "#ffffff" }, { - "ch": "⡀", - "fg": "#9f9f9f" - }, - { - "ch": "⠄", - "fg": "#7f7f7f" - }, - { - "ch": "⠂", - "fg": "#5f5f5f" + "ch": " ", + "fg": "#ffffff" } - ] - ], - [ + ], [ - { - "ch": "⠠", - "fg": "#dfdfdf" - }, - { - "ch": "⠐", - "fg": "#ffffff" - }, { "ch": " ", "fg": "#ffffff" @@ -3641,49 +9299,47 @@ "fg": "#ffffff" }, { - "ch": "⢀", - "fg": "#3f3f3f" - } - ], - [ + "ch": "⠒", + "fg": "#bfbfbf" + }, { - "ch": "⠑", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": "⠠", - "fg": "#9f9f9f" + "ch": "⠉", + "fg": "#bfbfbf" }, { "ch": " ", "fg": "#ffffff" }, { - "ch": "⡀", - "fg": "#7f7f7f" + "ch": "⠈", + "fg": "#bfbfbf" }, { - "ch": "⠠", - "fg": "#5f5f5f" + "ch": "⠉", + "fg": "#ffffff" }, { - "ch": "⠂", - "fg": "#5f5f5f" + "ch": "⠑", + "fg": "#ffffff" } ] ], [ [ { - "ch": "⠠", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⠐", - "fg": "#dfdfdf" + "ch": "⣀", + "fg": "#bfbfbf" }, { - "ch": "⠈", + "ch": " ", "fg": "#ffffff" }, { @@ -3695,13 +9351,15 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" - } - ], - [ + "ch": "⡠", + "fg": "#bfbfbf" + }, { - "ch": "⠑", + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": "⢀", "fg": "#bfbfbf" }, { @@ -3709,89 +9367,49 @@ "fg": "#ffffff" }, { - "ch": "⠄", - "fg": "#7f7f7f" - }, + "ch": " ", + "fg": "#ffffff" + } + ], + [ { - "ch": "⡀", - "fg": "#5f5f5f" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⠠", - "fg": "#5f5f5f" + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⠁", - "fg": "#3f3f3f" - } - ] - ], - [ - [ - { - "ch": "⠠", + "ch": "⠒", "fg": "#bfbfbf" }, { - "ch": "⠂", + "ch": "⠊", "fg": "#bfbfbf" }, { - "ch": "⠈", - "fg": "#dfdfdf" + "ch": "⠉", + "fg": "#bfbfbf" }, { "ch": " ", "fg": "#ffffff" }, - { - "ch": "⠂", - "fg": "#ffffff" - }, { "ch": " ", "fg": "#ffffff" - } - ], - [ - { - "ch": "⠁", - "fg": "#9f9f9f" }, { - "ch": "⠄", - "fg": "#7f7f7f" - }, - { - "ch": "⡀", - "fg": "#5f5f5f" - }, - { - "ch": "⢀", - "fg": "#5f5f5f" - }, - { - "ch": "⠠", - "fg": "#3f3f3f" + "ch": "⠁", + "fg": "#bfbfbf" }, { - "ch": " ", + "ch": "⠑", "fg": "#ffffff" - } - ] - ] - ] - }, - { - "id": "glitch", - "name": "glitch word", - "ms": 150, - "text": false, - "frames": [ - [ - [ + }, { - "ch": "◆", + "ch": "⠊", "fg": "#ffffff" } ] @@ -3799,99 +9417,89 @@ [ [ { - "ch": "◆", + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, + { + "ch": " ", + "fg": "#ffffff" + }, { - "ch": "◆", + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": "⣀", "fg": "#ffffff" } - ] - ], - [ + ], [ { - "ch": "◆", + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": " ", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", "fg": "#ffffff" - } - ] - ], - [ - [ + }, { - "ch": "◆", + "ch": " ", "fg": "#ffffff" } ] - ] - ] - }, - { - "id": "signal", - "name": "signal trace", - "ms": 150, - "text": false, - "frames": [ + ], [ [ { @@ -3911,12 +9519,12 @@ "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⢄", "fg": "#bfbfbf" }, { - "ch": "⣀", - "fg": "#bfbfbf" + "ch": " ", + "fg": "#ffffff" }, { "ch": " ", @@ -3927,11 +9535,11 @@ "fg": "#ffffff" }, { - "ch": " ", + "ch": "⣀", "fg": "#ffffff" }, { - "ch": "⡠", + "ch": "⣀", "fg": "#ffffff" } ], @@ -3941,11 +9549,11 @@ "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠊", "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠉", "fg": "#bfbfbf" }, { @@ -3957,19 +9565,19 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⠉", + "fg": "#bfbfbf" }, { "ch": "⠑", "fg": "#bfbfbf" }, { - "ch": "⠊", + "ch": "⠒", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": " ", "fg": "#ffffff" }, { @@ -3989,15 +9597,11 @@ "fg": "#ffffff" }, { - "ch": "⡠", - "fg": "#bfbfbf" - }, - { - "ch": "⢄", + "ch": "⢀", "fg": "#bfbfbf" }, { - "ch": "⡠", + "ch": "⠤", "fg": "#bfbfbf" }, { @@ -4013,51 +9617,55 @@ "fg": "#ffffff" }, { - "ch": "⠠", + "ch": "⢀", + "fg": "#bfbfbf" + }, + { + "ch": "⣀", "fg": "#ffffff" }, { - "ch": "⢄", + "ch": "⠤", "fg": "#ffffff" } ], [ { - "ch": "⠉", + "ch": "⠊", "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": " ", - "fg": "#ffffff" - }, - { - "ch": " ", - "fg": "#ffffff" + "ch": "⠁", + "fg": "#bfbfbf" }, { "ch": " ", "fg": "#ffffff" }, { - "ch": "⠐", + "ch": "⠈", "fg": "#bfbfbf" }, { - "ch": "⠊", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠒", "fg": "#bfbfbf" }, { "ch": "⠁", "fg": "#bfbfbf" }, + { + "ch": " ", + "fg": "#ffffff" + }, { "ch": " ", "fg": "#ffffff" @@ -4071,15 +9679,15 @@ "fg": "#ffffff" }, { - "ch": "⠠", + "ch": "⢀", "fg": "#bfbfbf" }, { - "ch": "⢄", + "ch": "⠤", "fg": "#bfbfbf" }, { - "ch": "⡠", + "ch": "⡀", "fg": "#bfbfbf" }, { @@ -4091,11 +9699,11 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⢀", + "fg": "#bfbfbf" }, { - "ch": "⢀", + "ch": "⣀", "fg": "#bfbfbf" }, { @@ -4103,13 +9711,13 @@ "fg": "#ffffff" }, { - "ch": "⣀", + "ch": "⡀", "fg": "#ffffff" } ], [ { - "ch": "⠒", + "ch": "⠉", "fg": "#bfbfbf" }, { @@ -4120,20 +9728,16 @@ "ch": " ", "fg": "#ffffff" }, - { - "ch": " ", - "fg": "#ffffff" - }, { "ch": "⠈", "fg": "#bfbfbf" }, { - "ch": "⠊", + "ch": "⠈", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠒", "fg": "#bfbfbf" }, { @@ -4147,6 +9751,10 @@ { "ch": " ", "fg": "#ffffff" + }, + { + "ch": "⠈", + "fg": "#ffffff" } ] ], @@ -4157,11 +9765,7 @@ "fg": "#bfbfbf" }, { - "ch": "⢄", - "fg": "#bfbfbf" - }, - { - "ch": "⡠", + "ch": "⠤", "fg": "#bfbfbf" }, { @@ -4181,15 +9785,19 @@ "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⣀", - "fg": "#ffffff" + "ch": "⡠", + "fg": "#bfbfbf" }, { - "ch": "⡀", + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" } ], @@ -4202,20 +9810,16 @@ "ch": " ", "fg": "#ffffff" }, - { - "ch": " ", - "fg": "#ffffff" - }, { "ch": "⠈", "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠒", "fg": "#bfbfbf" }, { @@ -4233,6 +9837,10 @@ { "ch": "⠈", "fg": "#ffffff" + }, + { + "ch": "⠊", + "fg": "#ffffff" } ] ], @@ -4242,10 +9850,6 @@ "ch": "⠤", "fg": "#bfbfbf" }, - { - "ch": "⣀", - "fg": "#bfbfbf" - }, { "ch": "⠄", "fg": "#bfbfbf" @@ -4259,19 +9863,23 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⢀", + "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⡠", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠄", + "fg": "#bfbfbf" + }, + { + "ch": " ", "fg": "#ffffff" }, { @@ -4280,10 +9888,6 @@ } ], [ - { - "ch": " ", - "fg": "#ffffff" - }, { "ch": " ", "fg": "#ffffff" @@ -4293,15 +9897,15 @@ "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠉", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠑", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠂", "fg": "#bfbfbf" }, { @@ -4313,11 +9917,15 @@ "fg": "#ffffff" }, { - "ch": " ", + "ch": "⠈", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", "fg": "#ffffff" }, { - "ch": "⠒", + "ch": "⠑", "fg": "#ffffff" } ] @@ -4325,11 +9933,11 @@ [ [ { - "ch": "⣀", + "ch": "⢄", "fg": "#bfbfbf" }, { - "ch": "⢄", + "ch": "⢀", "fg": "#bfbfbf" }, { @@ -4341,8 +9949,8 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⣀", + "fg": "#bfbfbf" }, { "ch": "⡠", @@ -4353,8 +9961,8 @@ "fg": "#bfbfbf" }, { - "ch": "⣀", - "fg": "#bfbfbf" + "ch": " ", + "fg": "#ffffff" }, { "ch": " ", @@ -4371,19 +9979,15 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" - }, - { - "ch": "⠒", + "ch": "⠁", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠑", "fg": "#bfbfbf" }, { - "ch": "⠉", + "ch": "⠊", "fg": "#bfbfbf" }, { @@ -4399,7 +10003,11 @@ "fg": "#ffffff" }, { - "ch": "⠑", + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠉", "fg": "#ffffff" }, { @@ -4410,10 +10018,6 @@ ], [ [ - { - "ch": "⢄", - "fg": "#bfbfbf" - }, { "ch": " ", "fg": "#ffffff" @@ -4426,6 +10030,10 @@ "ch": " ", "fg": "#ffffff" }, + { + "ch": "⣀", + "fg": "#bfbfbf" + }, { "ch": "⡠", "fg": "#bfbfbf" @@ -4435,8 +10043,8 @@ "fg": "#bfbfbf" }, { - "ch": "⣀", - "fg": "#bfbfbf" + "ch": " ", + "fg": "#ffffff" }, { "ch": " ", @@ -4447,14 +10055,14 @@ "fg": "#ffffff" }, { - "ch": "⣀", + "ch": "⡠", "fg": "#ffffff" } ], [ { - "ch": " ", - "fg": "#ffffff" + "ch": "⠉", + "fg": "#bfbfbf" }, { "ch": "⠑", @@ -4464,10 +10072,6 @@ "ch": "⠊", "fg": "#bfbfbf" }, - { - "ch": "⠉", - "fg": "#bfbfbf" - }, { "ch": " ", "fg": "#ffffff" @@ -4484,6 +10088,10 @@ "ch": "⠑", "fg": "#bfbfbf" }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, { "ch": "⠊", "fg": "#ffffff" @@ -4505,19 +10113,15 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" - }, - { - "ch": "⡠", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⢄", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠤", "fg": "#bfbfbf" }, { @@ -4529,12 +10133,16 @@ "fg": "#ffffff" }, { - "ch": "⣀", + "ch": " ", "fg": "#ffffff" }, { "ch": "⡠", "fg": "#ffffff" + }, + { + "ch": "⢄", + "fg": "#ffffff" } ], [ @@ -4543,11 +10151,7 @@ "fg": "#bfbfbf" }, { - "ch": "⠊", - "fg": "#bfbfbf" - }, - { - "ch": "⠉", + "ch": "⠒", "fg": "#bfbfbf" }, { @@ -4567,7 +10171,11 @@ "fg": "#bfbfbf" }, { - "ch": "⠊", + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠒", "fg": "#bfbfbf" }, { @@ -4587,19 +10195,15 @@ "fg": "#ffffff" }, { - "ch": " ", - "fg": "#ffffff" - }, - { - "ch": "⠠", + "ch": "⢀", "fg": "#bfbfbf" }, { - "ch": "⢄", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠤", "fg": "#bfbfbf" }, { @@ -4611,25 +10215,25 @@ "fg": "#ffffff" }, { - "ch": "⣀", + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠠", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⢄", "fg": "#ffffff" }, { - "ch": "⠤", + "ch": "⡠", "fg": "#ffffff" } ], [ { - "ch": "⠊", - "fg": "#bfbfbf" - }, - { - "ch": "⠉", + "ch": "⠒", "fg": "#bfbfbf" }, { @@ -4648,13 +10252,17 @@ "ch": "⠈", "fg": "#bfbfbf" }, + { + "ch": "⠉", + "fg": "#bfbfbf" + }, { "ch": "⠒", "fg": "#bfbfbf" }, { - "ch": " ", - "fg": "#ffffff" + "ch": "⠁", + "fg": "#bfbfbf" }, { "ch": " ", @@ -4668,20 +10276,16 @@ ], [ [ - { - "ch": " ", - "fg": "#ffffff" - }, { "ch": "⢀", "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠤", "fg": "#bfbfbf" }, { @@ -4692,16 +10296,20 @@ "ch": " ", "fg": "#ffffff" }, + { + "ch": " ", + "fg": "#ffffff" + }, { "ch": "⢀", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⢄", "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⡠", "fg": "#ffffff" }, { @@ -4710,10 +10318,6 @@ } ], [ - { - "ch": "⠉", - "fg": "#bfbfbf" - }, { "ch": "⠁", "fg": "#bfbfbf" @@ -4731,7 +10335,11 @@ "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠑", "fg": "#bfbfbf" }, { @@ -4755,20 +10363,20 @@ [ [ { - "ch": "⢀", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⡠", "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠄", "fg": "#bfbfbf" }, { - "ch": "⡀", - "fg": "#bfbfbf" + "ch": " ", + "fg": "#ffffff" }, { "ch": " ", @@ -4779,15 +10387,15 @@ "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⠤", "fg": "#bfbfbf" }, { - "ch": "⠤", + "ch": "⣀", "fg": "#bfbfbf" }, { - "ch": "⡀", + "ch": "⠄", "fg": "#bfbfbf" }, { @@ -4796,10 +10404,6 @@ } ], [ - { - "ch": "⠁", - "fg": "#bfbfbf" - }, { "ch": " ", "fg": "#ffffff" @@ -4813,11 +10417,15 @@ "fg": "#bfbfbf" }, { - "ch": "⠒", + "ch": "⠊", "fg": "#bfbfbf" }, { - "ch": "⠁", + "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠂", "fg": "#bfbfbf" }, { @@ -4833,13 +10441,33 @@ "fg": "#ffffff" }, { - "ch": "⠉", + "ch": "⠒", "fg": "#ffffff" } ] ], [ [ + { + "ch": "⡠", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, { "ch": "⠤", "fg": "#bfbfbf" @@ -4849,7 +10477,7 @@ "fg": "#bfbfbf" }, { - "ch": "⣀", + "ch": "⢄", "fg": "#bfbfbf" }, { @@ -4857,19 +10485,29 @@ "fg": "#ffffff" }, { - "ch": "⢀", - "fg": "#bfbfbf" + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" }, { - "ch": "⣀", + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠊", "fg": "#bfbfbf" }, { - "ch": "⡠", + "ch": "⠑", "fg": "#bfbfbf" }, { - "ch": "⠄", + "ch": "⠊", "fg": "#bfbfbf" }, { @@ -4879,9 +10517,27 @@ { "ch": " ", "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠒", + "fg": "#ffffff" + }, + { + "ch": "⠉", + "fg": "#ffffff" } - ], + ] + ], + [ [ + { + "ch": "⢄", + "fg": "#bfbfbf" + }, { "ch": " ", "fg": "#ffffff" @@ -4895,11 +10551,15 @@ "fg": "#ffffff" }, { - "ch": "⠒", + "ch": "⡠", "fg": "#bfbfbf" }, { - "ch": "⠂", + "ch": "⣀", + "fg": "#bfbfbf" + }, + { + "ch": "⢄", "fg": "#bfbfbf" }, { @@ -4911,15 +10571,49 @@ "fg": "#ffffff" }, { - "ch": "⠈", + "ch": " ", + "fg": "#ffffff" + } + ], + [ + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": "⠉", "fg": "#bfbfbf" }, { "ch": "⠉", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#bfbfbf" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", + "fg": "#ffffff" + }, + { + "ch": " ", "fg": "#ffffff" }, { "ch": "⠑", + "fg": "#bfbfbf" + }, + { + "ch": "⠊", + "fg": "#ffffff" + }, + { + "ch": "⠉", "fg": "#ffffff" } ] diff --git a/test/packexport.test.ts b/test/packexport.test.ts index 8691962d..ce39616a 100644 --- a/test/packexport.test.ts +++ b/test/packexport.test.ts @@ -2,6 +2,7 @@ import { test, expect } from 'claude-code/testing' import { PRESETS } from '../hooks/themes.ts' import { SPINNER_IDS } from '../hooks/packs.ts' import { CLAWD_SHEET, PET_ROWS } from '../hooks/pets.ts' +import { spinnerCells, type Cell } from '../hooks/motion.ts' import { packExport, packsJson, exportAll, themeExport, spinnerExport, clawdExport } from '../hooks/packexport.ts' test('exports the four built-in packs in preset order', async () => { @@ -58,6 +59,23 @@ test('every spinner is exported with frames in white on black', async () => { expect(sp.find(s => s.id === 'clawd')!.frames[0]![0]![0]!.fg).toBe('#d77757') }) +// The cycle the preview loops is the spinner's real period: moving a whole cycle on changes at +// most a few cells (rounding ms to an integer and float edges cost a cell or two). The scanline's +// background checker flips every 400 ms regardless, so only its sweep head is compared. +test('the looping spinners export one whole period, so the preview wraps without a jump', async () => { + const mono = { color: '#ffffff', bg: '#000000', fg: '#ffffff' } + const head = (f: Cell[][]) => f[0]!.findIndex(c => c.ch === '█') + const diff = (a: Cell[][], b: Cell[][]) => a.flat().filter((c, i) => c.ch !== b.flat()[i]!.ch).length + for (const s of spinnerExport().filter(s => ['scanline', 'ring', 'signal'].includes(s.id))) { + const cycle = s.ms * s.frames.length + const ts = Array.from({ length: 40 }, (_, i) => i * 97) + const off = ts.map(t => s.id === 'scanline' + ? Math.abs(head(spinnerCells('scanline', t, mono)) - head(spinnerCells('scanline', t + cycle, mono))) + : diff(spinnerCells(s.id, t, mono), spinnerCells(s.id, t + cycle, mono))) + expect([s.id, off.reduce((a, b) => a + b, 0) / ts.length < 1.5]).toEqual([s.id, true]) + } +}) + test('Clawd is exported as his idle half-block rows in his own palette', async () => { const c = clawdExport() expect(c.cols).toBe(CLAWD_SHEET.w) From 862bed80cd9041cf3cdc94f7f5bb9c27ebebc83b Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 12:03:20 +0300 Subject: [PATCH 11/12] Docs: slab tool row example shows its three trailing spaces Signed-off-by: NovusEdge --- docs/pack-reference.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/pack-reference.md b/docs/pack-reference.md index 34f6220e..89d66cd2 100644 --- a/docs/pack-reference.md +++ b/docs/pack-reference.md @@ -82,7 +82,7 @@ Styles wrap or prefix a transcript row; the content Claude Code draws inside it | `cards` | An accent side bar `▎` and a `you` label | A dim side bar, and a `claude` label on the first block of a reply | A dim bordered card with a mark: `✓`, `✗`, `■` for interrupted, `…` while running | | `minimal` | A `› text` line | Claude Code's | A dim one-line `· Read src/auth.ts` once it finishes cleanly | | `retro` | A `[YOU]` tag on its own line above your prompt | A `[CLAUDE]` tag on the first block | A `[READ ]`-style tag, then `[ OK ]`, `[FAIL]`, `[STOP]` or `[....]` | -| `slab` | A full-width bar in `accent` with your prompt in the background color and `PROMPT 03` on the right | An 8-cell `─` rule in `accent` above the first block | `01 READ ` before the row, then `OK`, `FAIL`, `STOP` or `…` on the right; results indented under their call | +| `slab` | A full-width bar in `accent` with your prompt in the background color and `PROMPT 03` on the right | An 8-cell `─` rule in `accent` above the first block | `01 READ ` before the row, then `OK`, `FAIL`, `STOP` or `…` on the right; results indented under their call | Not styled: notifications, messages from other agents, prompts expanded with ctrl+o or `--verbose`, the folded "Read 3 files" line, and tool progress. Outside fullscreen, rows already in scrollback keep the style they were printed with. From 4e5809d574905ae2df77d6d1d0c41c2a9e55d1fe Mon Sep 17 00:00:00 2001 From: NovusEdge Date: Wed, 7 Oct 2026 16:00:26 +0300 Subject: [PATCH 12/12] chore(ledger): migrate to per-kind ids docket 0.26.0 renumbers claims, decisions and questions each from 1; every record keeps its old id in migrated_from. Signed-off-by: NovusEdge --- .docket/ledger.jsonl | 230 +++++++++++++++++++++---------------------- 1 file changed, 115 insertions(+), 115 deletions(-) diff --git a/.docket/ledger.jsonl b/.docket/ledger.jsonl index 6dcd3df8..bba3d65e 100644 --- a/.docket/ledger.jsonl +++ b/.docket/ledger.jsonl @@ -1,115 +1,115 @@ -{"schema":2,"kind":"decision","id":"d1","text":"Project name","state":"adopted","ts":"2026-10-03T22:34:01+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"npm name free, no Claude Code project uses it, strongest headline hook","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"gh search + npm view, 2026-10-04"}],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"glowup","alternatives":["zhuzh","jazzhands","eyecandy"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q2","text":"What does the launch ship?","state":"open","ts":"2026-10-03T22:34:08+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"question","id":"q3","text":"How do users customize the look?","state":"open","ts":"2026-10-03T22:34:08+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d4","text":"Launch ships the live session visualizer plus a written plan for a full skin","state":"adopted","ts":"2026-10-03T22:34:09+00:00","author":"claude-code","session":"","branch":"main","scope":["launch"],"rationale":"One clear demo clip; the full skin follows on the same theme format","supports":[],"depends_on":[],"answers":["q2"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"If the clip doesn't land alone, the skin plan has to be pulled forward","pinned":false,"choice":"visualizer + skin plan","alternatives":["full skin at launch","visualizer alone"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d5","text":"Users customize through theme files plus built-in presets, switched live by a slash command","state":"adopted","ts":"2026-10-03T22:34:09+00:00","author":"claude-code","session":"","branch":"main","scope":["theming"],"rationale":"Anyone can edit a file; shared themes keep the launch spreading; the full skin extends the same file","supports":[],"depends_on":[],"answers":["q3"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Theme schema becomes a public contract; breaking it breaks shared themes","pinned":false,"choice":"theme files + presets","alternatives":["presets only","scriptable scenes"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q6","text":"What is the hero piece and where does it live?","state":"open","ts":"2026-10-03T22:47:19+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d7","text":"Hero piece is a 4-row companion band above the prompt, shown only during a turn; the transcript keeps Claude Code's layout with a light restyle","state":"adopted","ts":"2026-10-03T22:47:19+00:00","author":"claude-code","session":"","branch":"main","scope":["hero"],"rationale":"Panels were spectacle without use; restyle alone read as a themed Claude Code; 9 rows took too much space","supports":[],"depends_on":[],"answers":["q6"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"If the band reads as clutter, the hero shrinks to restyle + moments","pinned":false,"choice":"companion band","alternatives":["full-panel visualizer (constellation/skyline/lanes)","restyle only","9-row stage"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q8","text":"Where does the character art come from?","state":"open","ts":"2026-10-03T22:47:19+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d9","text":"Ship the hand-drawn block-character sprites as a built-in set that works in every terminal; spritesheet art is an additional set for terminals that show images","state":"adopted","ts":"2026-10-03T22:47:19+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"User likes the hand-drawn look; block art is the only option in terminals without image support","supports":[],"depends_on":[],"answers":["q8"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Two art pipelines to keep in sync per action","pinned":false,"choice":"hand-drawn blocks + optional spritesheet","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q10","text":"Is the pet required, and can it talk?","state":"open","ts":"2026-10-03T22:52:08+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d11","text":"Pet and speech bubbles are both optional toggles; with the pet off the band collapses to one text line","state":"adopted","ts":"2026-10-03T22:52:09+00:00","author":"claude-code","session":"","branch":"main","scope":["hero"],"rationale":"Not everyone wants a character; the text line keeps the useful part","supports":[],"depends_on":[],"answers":["q10"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Every pet feature needs a text-only equivalent in the one-line band","pinned":false,"choice":"pet toggle + bubble toggle","alternatives":["pet always on"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d12","text":"Bubble text comes from per-event templates in the theme file (with variants); an opt-in chatty mode has Haiku write quips","state":"adopted","ts":"2026-10-03T22:53:32+00:00","author":"claude-code","session":"","branch":"main","scope":["bubbles"],"rationale":"Free, instant and accurate by default; themes give the pet a voice","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Template sets per theme are content work; chatty mode adds token cost and can misstate facts","pinned":false,"choice":"templates + opt-in LLM","alternatives":["templates only","Claude's own words","always LLM"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d13","text":"Use the hand-drawn Clawd block sprites for now; the user is still looking for shippable spritesheet alternatives","state":"adopted","ts":"2026-10-03T22:53:32+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"Unblocks the build; art can be swapped through the theme file","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"before public launch: Clawd is Anthropic's mascot","cost_if_wrong":"If Clawd can't ship as default, the default sprite set changes before launch","pinned":false,"choice":"Clawd blocks for now","alternatives":["Pixel Adventure 1 (CC0)","Puny Characters (CC0)"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q14","text":"What happens to the user's existing status line?","state":"open","ts":"2026-10-03T22:55:05+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d15","text":"glowup replaces the user's status line on first run, backs up the old setting, and a command switches back","state":"adopted","ts":"2026-10-03T22:55:05+00:00","author":"claude-code","session":"","branch":"main","scope":["statusline"],"rationale":"Full look on install; the old one is one command away","supports":[],"depends_on":[],"answers":["q14"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Changing user settings unasked can draw backlash; an uninstall that leaves a dead statusLine path would break their status line","pinned":false,"choice":"replace + restore command","alternatives":["coexist + opt-in","wrap theirs","never offer one"],"decided_by":"user"} -{"schema":2,"kind":"claim","id":"c16","text":"Mods can read settings ($.settings.read, read-only) and write any file with $.fs.write, so changing statusLine means rewriting ~/.claude/settings.json; $.ui.status only adds a line beside the engine's notices","state":"unassessed","ts":"2026-10-03T22:55:06+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"types/claude-code.d.ts lines 2263-2273, 3015-3041, 3355-3370 (plugin-authoring 2.1.288)"}],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d17","text":"Status line takeover is opt-in via /glowup statusline on (backup + restore); by default glowup adds its own $.ui.status entry beside the user's status line","state":"adopted","ts":"2026-10-03T23:05:21+00:00","author":"claude-code","session":"","branch":"main","scope":["statusline"],"rationale":"settings.json is also written by Claude Code (race), project statusLine overrides user-level, concurrent sessions would share one file, and silent config changes draw backlash","supports":[],"depends_on":[],"answers":[],"supersedes":["d15"],"evidence":[{"ref":"Fable review 2026-10-04; d.ts:2263"}],"revisit":"","cost_if_wrong":"Users of the stock status line won't get a matching one unless they opt in","pinned":false,"supersede_reason":"revise","choice":"opt-in takeover","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d18","text":"Design an original mascot in the hand-drawn block style for launch; Clawd stays in local development only","state":"adopted","ts":"2026-10-03T23:05:21+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"A mascot-based theme risks a trademark takedown on launch day","supports":[],"depends_on":[],"answers":[],"supersedes":["d13"],"evidence":[],"revisit":"","cost_if_wrong":"Mascot design work before launch","pinned":false,"supersede_reason":"revise","choice":"original mascot","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d19","text":"Adopt review fixes: text-tree fallback off-terminal, blit on change + 4-8 fps idle tick, route subagent tool calls by agentId, flag only with a task list, test stars only for recognized test commands, 1.5 s linger, yield to survey, bubbles default to done/fail/needs-you","state":"adopted","ts":"2026-10-03T23:05:22+00:00","author":"claude-code","session":"","branch":"main","scope":["architecture"],"rationale":"Verified against the mod API types","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"Fable review 2026-10-04; d.ts:8614, 8579, 174-181, 9580-9586"}],"revisit":"","cost_if_wrong":"Each is small; none blocks the clip","pinned":false,"choice":"adopt review fixes","alternatives":[],"decided_by":"agent"} -{"schema":2,"kind":"decision","id":"d20","text":"Clawd is the default pet; Kit (cat) ships as an option; a third option is still being chosen (Quack the rubber duck proposed)","state":"adopted","ts":"2026-10-03T23:14:30+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"User prefers Clawd's familiarity; Kit gives broad appeal","supports":[],"depends_on":[],"answers":[],"supersedes":["d18"],"evidence":[],"revisit":"before public launch: trademark risk of Clawd as default","cost_if_wrong":"A takedown request would force a default swap after launch","pinned":false,"supersede_reason":"reverse","choice":"Clawd default + Kit","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d21","text":"Reduced motion turns the pet off: the band shows the one-line text version","state":"adopted","ts":"2026-10-03T23:14:31+00:00","author":"claude-code","session":"","branch":"main","scope":["accessibility"],"rationale":"A pet that cannot move adds nothing; the text line carries the same information","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"None significant","pinned":false,"choice":"reduced motion = pet off","alternatives":["static pet frames"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d22","text":"Pet sprites use half blocks (1x2 near-square pixels per cell, any color per pixel), feet on the track row, so an 8-pixel sprite fills the 4-row band; Clawd keeps quadrant blocks like the Claude Code logo","state":"adopted","ts":"2026-10-03T23:14:31+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"Quadrant pixels are about 2.3x taller than wide and allow only 2 colors per 2x2 block, which stretched and broke multi-color sprites","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Half the horizontal resolution of quadrants","pinned":false,"choice":"half blocks","alternatives":["quadrant blocks"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d23","text":"Third built-in pet is a non-animal (Blip proposed); a hidden Egg pet is unlocked by the Konami code, cracks with each passing test run, and hatches into a per-user secret creature","state":"adopted","ts":"2026-10-03T23:15:27+00:00","author":"claude-code","session":"","branch":"main","scope":["easter-egg"],"rationale":"Classic HN-friendly easter egg; hatch results are shareable","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Hatch sprites are extra art; unlock may need a fallback input","pinned":false,"choice":"Konami-code egg","alternatives":["appears after 100th passing test run","secret command","date-based"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q24","text":"Do arrow keys pressed in the prompt reach a mod? prompt.edit carries the key only for keys that change the draft, so left/right (cursor moves) may never arrive. Spike before building the unlock; fallback is typing uuddlrlrba as a prompt.","state":"open","ts":"2026-10-03T23:15:28+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d25","text":"The pet keeps its own colors under every theme; themes style everything else; only monochrome greys it out","state":"adopted","ts":"2026-10-03T23:16:09+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"The pet's identity should stay recognizable across themes","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A pet can clash with a theme's palette","pinned":false,"choice":"fixed pet colors","alternatives":["theme recolors the pet"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d26","text":"Ship several easter eggs, not one: the Konami-code Egg plus more (candidates: a surprise after the 100th passing test run, an unlisted command, seasonal pet outfits)","state":"adopted","ts":"2026-10-03T23:16:09+00:00","author":"claude-code","session":"","branch":"main","scope":["easter-egg"],"rationale":"Hidden extras give people reasons to post discoveries after launch","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Each egg is extra art and triggers to test","pinned":false,"choice":"multiple easter eggs","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d27","text":"Easter eggs at launch: Konami-code Egg, shiny pet after 100th passing test run, Friday-deploy sweat + sign, night-owl nightcap (2-5 a.m.), seasonal outfits (late Dec, Halloween, install anniversary)","state":"adopted","ts":"2026-10-03T23:16:44+00:00","author":"claude-code","session":"","branch":"main","scope":["easter-egg"],"rationale":"Each one is a screenshot moment after launch","supports":[],"depends_on":[],"answers":[],"supersedes":["d26"],"evidence":[],"revisit":"","cost_if_wrong":"Overlay sprites (hat, nightcap, sign, sweat) per pet: 3 pets x 5 overlays","pinned":false,"supersede_reason":"restate","choice":"all five","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d28","text":"Third built-in pet is Blip (tiny terminal on legs); built-ins are Clawd (default), Kit, Blip","state":"adopted","ts":"2026-10-03T23:17:39+00:00","author":"claude-code","session":"","branch":"main","scope":["sprites"],"rationale":"Non-animal, reads as a dev tool, face-on-screen expressions stay legible at band size","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"None beyond its art","pinned":false,"choice":"Blip","alternatives":["Lumo","Quack","Glim"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d29","text":"Two data-only JSON (comments allowed) file kinds: themes (colors, glyphs, spinner, band; extends) and pets (palette, frames, say lines); pet choice and toggles live in mod settings; /glowup theme add installs shared files","state":"adopted","ts":"2026-10-03T23:18:08+00:00","author":"claude-code","session":"","branch":"main","scope":["theming"],"rationale":"JSON parses natively (no npm imports in mods); splitting pets from themes lets any pet pair with any theme since pets keep fixed colors","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Theme schema is a public contract; changes need versioning","pinned":false,"choice":"theme + pet JSON files","alternatives":["single combined file","YAML/TOML"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q30","text":"What should the install flow and the first launch after install look like (welcome moment, theme and pet picker)? Follow-up after the launch spec.","state":"open","ts":"2026-10-03T23:20:14+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d31","text":"Build order: the non-pet core first (event translation, state, text band, restyle, themes, status entry, /glowup); pets, bubbles and easter eggs come in a later plan","state":"adopted","ts":"2026-10-03T23:22:54+00:00","author":"claude-code","session":"","branch":"main","scope":["plan"],"rationale":"User wants the actual product working before the pet layer","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"The launch clip needs the pet, so the core alone isn't launchable","pinned":false,"choice":"core first, pets later","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q32","text":"Should glowup change Claude Code's layout beyond the band (panes, panels, drawers)?","state":"open","ts":"2026-10-03T23:30:51+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d33","text":"Add a cockpit layout: a tabbed side pane docked beside the transcript (Changes, Agents, Plan & context, Timeline & map); on narrow terminals each tab opens as a drawer (focused pane, closes on Esc)","state":"adopted","ts":"2026-10-03T23:30:52+00:00","author":"claude-code","session":"","branch":"main","scope":["layout"],"rationale":"Panels can be useful, not just decorative: what changed, what subagents are doing, the plan, what fills context","supports":[],"depends_on":[],"answers":["q32"],"supersedes":[],"evidence":[{"ref":"d.ts 2275-2296 (ui.open placement), 9622-9672 (Pane: dock|inline, tabs, view)"}],"revisit":"","cost_if_wrong":"Panes dock only in fullscreen from 110 columns; below that the cockpit is drawers only","pinned":false,"choice":"cockpit with four tabs","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d34","text":"Cockpit tabs are Changes, Agents, and Plan & context; Timeline and Map are dropped","state":"adopted","ts":"2026-10-03T23:40:25+00:00","author":"claude-code","session":"","branch":"main","scope":["layout"],"rationale":"User found timeline and map meh after seeing the mockup","supports":[],"depends_on":[],"answers":[],"supersedes":["d33"],"evidence":[],"revisit":"","cost_if_wrong":"No eye-candy tab in the pane; the band and pets carry the eye candy","pinned":false,"supersede_reason":"revise","choice":"three tabs","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d35","text":"Layout by width: wide fullscreen (110+) docks the pane with the pet at its bottom and no band; medium (80-109) uses the 4-row band plus a drawer; compact (<80) uses a one-line status band starting with a one-row Clawd plus a compact drawer","state":"adopted","ts":"2026-10-03T23:53:17+00:00","author":"claude-code","session":"","branch":"main","scope":["layout"],"rationale":"Docked pane already shows the band's content; narrow terminals had too much on screen","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Three render paths for band and pane to test","pinned":false,"choice":"three width tiers","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"claim","id":"c36","text":"In Claude Code 2.1.288 the subagent tool is named Agent (input: description, prompt, subagent_type?, name?), and both TodoWrite and TaskCreate/TaskUpdate exist; $.session.usage({breakdown:'summary'}) returns context.breakdown.categories {name,tokens} and maxTokens","state":"unassessed","ts":"2026-10-04T00:01:18+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"types/vendor d.ts: BuiltinToolInputs Agent/TaskCreate/TaskUpdate/TodoWrite; SessionContextUsage 10265, SessionContextBreakdown 10181, ContextCategory 1940"}],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"claim","id":"c37","text":"A Button's action prop binds an existing engine keybinding action (unknown names refused), so a mod cannot invent a new global shortcut like ctrl+g; the drawer opens from /glowup pane","state":"unassessed","ts":"2026-10-04T00:01:19+00:00","author":"claude-code","session":"","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"d.ts ButtonProps 919-927"}],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d38","text":"Docs live in docs/ (guides, theme reference); the GitHub Pages site lives in docs/web and deploys with a pages workflow; built after the core so it documents real behavior","state":"adopted","ts":"2026-10-04T00:13:32+00:00","author":"claude-code","session":"","branch":"main","scope":["docs"],"rationale":"User's layout convention, matching a2a-mod's direction","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Site work waits on the core build","pinned":false,"choice":"docs/ + docs/web","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d39","text":"Releases are cut from version tags (vX.Y.Z) by a release workflow modeled on a2a-mod's (tag must match plugin.json version, run check/test, notes from CHANGELOG, plugin archive); ship v0.1.0 once the core build, README and docs are done","state":"adopted","ts":"2026-10-04T00:20:33+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["release"],"rationale":"User's release convention","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A bad tag publishes a broken release; the workflow's version check and tests guard it","pinned":false,"choice":"tag-driven release, first v0.1.0","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q40","text":"What does glowup look like right after install?","state":"open","ts":"2026-10-04T09:36:42+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d41","text":"Ship a signature 'glowup' preset as the default theme (classic stays available), chosen from mocked palettes","state":"adopted","ts":"2026-10-04T09:36:42+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["theming"],"rationale":"classic made the first impression 'Claude Code with a sidebar'","supports":[],"depends_on":[],"answers":["q40"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A bold default may clash with some terminal backgrounds; classic is one command away","pinned":false,"choice":"signature default theme","alternatives":["classic default"],"decided_by":"user"} -{"schema":2,"kind":"question","id":"q42","text":"How does a new user get glowup's status line?","state":"open","ts":"2026-10-04T09:36:43+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":[],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d43","text":"Next release asks once, on the first launch after install, whether glowup should draw the status line (undo with /glowup statusline restore)","state":"adopted","ts":"2026-10-04T09:36:43+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["statusline"],"rationale":"Opt-in-only left the user's status line under the pane, repeating context; asking keeps consent","supports":[],"depends_on":[],"answers":["q42"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"One prompt on first launch","pinned":false,"choice":"first-run question","alternatives":["auto takeover on install","opt-in command only"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d44","text":"Classic stays the default theme; glowup (ember), aurora and dusk ship as switchable presets","state":"adopted","ts":"2026-10-04T09:45:36+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":[],"rationale":"User liked all three mocked palettes and wants classic kept as the default","supports":[],"depends_on":[],"answers":["q40"],"supersedes":["d41"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"keep classic default, add three presets","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d45","text":"Packs bundle colors + row styles and motion; sound and voice layers wait for later patches","state":"adopted","ts":"2026-10-04T09:45:36+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":[],"rationale":"User picked all four layers, then deferred sound and voice","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"after the packs release","cost_if_wrong":"","pinned":false,"choice":"two layers first","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d46","text":"Clawd moves into the next release: Clawd at the bottom of the docked pane plus a Clawd spinner option; Kit, Blip, bubbles and easter eggs stay in the later pets release","state":"adopted","ts":"2026-10-04T10:09:55+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":["pets"],"rationale":"User wants Clawd now","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"next release grows by one sprite renderer","pinned":false,"choice":"pull Clawd forward","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d47","text":"Next release ships the whole pet system with Clawd as the only built-in pet: pet is its own configurable layer (pick a pet, on/off, independent of packs), reactions, optional bubbles; Kit and Blip follow as extra pets","state":"adopted","ts":"2026-10-04T10:10:58+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":["pets"],"rationale":"User wants pets mix-and-match and Clawd now","supports":[],"depends_on":[],"answers":[],"supersedes":["d46"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"full pet system, Clawd only","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d48","text":"Pet placement follows the layout tiers: wide fullscreen = bottom of the docked pane; medium = band above the prompt; compact = one-row Clawd at the start of the status line; and on medium/compact the pet steps aside (text band only) while Claude Code's own task list is showing","state":"adopted","ts":"2026-10-04T10:13:27+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":["pets"],"rationale":"User wants the narrow vs full conditional, and the task list + pet + spinner overcrowds the working area","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"tiered pet that yields to the task list","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"claim","id":"c49","text":"A mod cannot hide or move Claude Code's own task list: no ui.render component covers it (checked 2.1.288 types); glowup's Plan & context tab already mirrors TodoWrite/TaskCreate/TaskUpdate","state":"unassessed","ts":"2026-10-04T10:13:28+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"feat/signature-themes","scope":["pets"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d50","text":"glowup gets a Go installer with a Charm-based TUI, built after 0.2","state":"adopted","ts":"2026-10-04T10:42:27+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["install"],"rationale":"User wants a fancy install flow","supports":[],"depends_on":[],"answers":["q30"],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Go + Charm installer","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d51","text":"Clawd lives only at the bottom of the glowup pane (docked or inline), at every width; the band and status line never show the pet","state":"adopted","ts":"2026-10-04T10:47:55+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"Keeps the working area above the prompt clear; removes the clash with Claude Code's task list","supports":[],"depends_on":[],"answers":[],"supersedes":["d48"],"evidence":[],"revisit":"","cost_if_wrong":"no pet when the pane is closed on narrow terminals","pinned":false,"supersede_reason":"revise","choice":"pet in pane only","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d52","text":"Animation uses Client surface modules at the Spinner, AbovePrompt and Pane sites (option A); Raster+blit is not needed","state":"adopted","ts":"2026-10-04T10:58:25+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["motion"],"rationale":"Spike on 2.1.289: Client drew and counted at all three sites and from an imported module; two-hop relative imports load; state.d.ts must be self-contained with a types key; bordered ToolUse rows wrap inside the border","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"docs/superpowers/plans/0.2-spike-report.md"}],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"option A","alternatives":[],"decided_by":"spike"} -{"schema":2,"kind":"decision","id":"d53","text":"Clawd is hand-drawn half-block pixel art, 24x12 px (6 rows), at least 8 frames per animation; movement may run ~12 fps, only color/brightness flashes are held to 400 ms","state":"adopted","ts":"2026-10-04T10:58:25+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"Owner rejected the logo-glyph poses and the procedural sprite as too static","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"hand-authored sprite sheets","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d54","text":"Clawd ships a hybrid: hand-drawn walk, alert, sleep and fail frames plus the artist's outfits, procedural idle, hop and done; one character, the procedural body is the reference","state":"adopted","ts":"2026-10-04T11:14:22+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"Owner preferred the procedural idle/hop/done and the artist's walk/alert/sleep/fail and outfits","supports":[],"depends_on":[],"answers":[],"supersedes":["d53"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"hybrid sprite set","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d55","text":"Clawd uses the artist's hand-drawn sprite sheet for every animation (idle, walk, hop, alert, done, sleep, working, fail) plus its outfits","state":"adopted","ts":"2026-10-04T11:15:45+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"Owner's final pick","supports":[],"depends_on":[],"answers":[],"supersedes":["d54"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"all hand-drawn","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d56","text":"Clawd animations transition: each starts at frame 0, pose changes wait for the next marked exit frame (≤ 300 ms) except alert which cuts in, hand-drawn lie-down/wake-up/stop/startle clips, walking position carries over","state":"adopted","ts":"2026-10-04T11:21:31+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"exit frames + transition clips","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d57","text":"Clawd's sprite sheet is locked as approved: docs/superpowers/mockups/clawd-final.json (source art in mockups/clawd-src)","state":"adopted","ts":"2026-10-04T11:33:53+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["pets"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"lock the sheet","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d58","text":"Release 0.2.0 now; owner does the live check after; Konami egg and iTerm2 import move to 0.2.x","state":"adopted","ts":"2026-10-04T12:38:53+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["release"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"ship without 4C/4D","alternatives":[],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d59","text":"Park the Konami egg until a mod can read arrow keys","state":"adopted","ts":"2026-10-04T14:18:14+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":[],"rationale":"Spike: in a focused pane, up/down step the focus ring linearly, left/right and Client onKey deliver nothing, letters return focus to the prompt","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"https://github.com/NovusEdge/glowup/issues/5"}],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Park it; tracked in GitHub issue #5","alternatives":["Build an ENTER CODE dialog with a typed uuddlrlrba input"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d60","text":"/glowup config is a question wizard, not a pane view","state":"adopted","ts":"2026-10-04T15:03:18+00:00","author":"claude-code","session":"session_01NShjxWatJ5wM4vP8ondpPW","branch":"main","scope":["glowup config"],"rationale":"Owner preferred the native look; ask() gives labels only, 2-4 options, no defaults","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"If the wizard feels too limited, the boxed layout must be rebuilt (branch feat/config-layout was deleted)","pinned":false,"choice":"Three native AskUserQuestion steps (pack, pet, extras toggles), each applied on pick, ending in a summary card","alternatives":["Boxed form in the pane or in the band above the prompt, with live preview"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d61","text":"The docs site at docs/web renders guides with Fumadocs v16 on its existing React Router 8 prerender setup","state":"adopted","ts":"2026-10-04T17:26:46+00:00","author":"claude-code","session":"","branch":"feat/docs-fumadocs","scope":["docs/web"],"rationale":"fumadocs-core peers react-router 7||8; the site's interactive pieces import hooks/*.ts through Vite, which a Next build handles worse","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"npm view fumadocs-core peerDependencies (16.16.1)"}],"revisit":"","cost_if_wrong":"Swapping frameworks later means re-porting the landing components and search","pinned":false,"choice":"Fumadocs on React Router 8","alternatives":["Fumadocs on Next.js static export","keep the hand-built renderer"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d62","text":"The landing page replaces the /spinners and /gallery pages; their content lives in landing sections built from glowup's real modules","state":"adopted","ts":"2026-10-04T17:26:46+00:00","author":"claude-code","session":"","branch":"feat/docs-fumadocs","scope":["docs/web/app"],"rationale":"docs/*.md stay plain Markdown so GitHub renders them, and Markdown cannot embed components","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"delete /spinners and /gallery, landing sections import hooks/sprites/clawd.ts, motion.ts, presets.ts, packpresets.ts","alternatives":["fold the lab and gallery into the guides as embedded components"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d63","text":"The landing page follows the approved A+C mockup: dithered WebGL background in muted pack colors, floating nav, Clawd over the headline, full-width scripted terminal, then Clawd, spinners, packs/themes and theme-maker sections","state":"adopted","ts":"2026-10-04T17:26:47+00:00","author":"claude-code","session":"","branch":"feat/docs-fumadocs","scope":["docs/web/app"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"A+C mockup, docs/superpowers/mockups/landing/hero.html","alternatives":["A stage only","B split layout","C mascot only"],"decided_by":"user"} -{"schema":2,"kind":"correction","id":"d63.1","corrects":"d63","fields":{"rationale":"user: the landing page sells it as what it is, a mod that makes Claude look nicer"},"reason":"subtitle reworded: glowup is pitched as a mod that makes Claude Code look nicer, not as a way to see what Claude does","ts":"2026-10-04T17:28:36+00:00","author":"claude-code","session":"","branch":"feat/docs-fumadocs"} -{"schema":2,"kind":"decision","id":"d64","text":"The Pages install skips the one-day pnpm release-age gate for fumadocs-core/ui 16.16.1 and approves esbuild's build script, so the site ships before the gate clears","state":"adopted","ts":"2026-10-04T19:25:46+00:00","author":"claude-code","session":"","branch":"feat/docs-fumadocs","scope":[".github/workflows/pages.yml","docs/web"],"rationale":"owner chose to ship now; esbuild via fumadocs-mdx needs build approval under pnpm 12 regardless","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"--config.minimum-release-age-exclude for both packages plus --allow-build=esbuild in pages.yml","alternatives":["wait until 2026-10-05 14:04Z"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d65","text":"glowup's status line is one line of fields the person picks and orders, in theme colors, including 5-hour and weekly usage.","state":"adopted","ts":"2026-10-04T21:30:01+00:00","author":"claude-code","session":"","branch":"feat/statusline-fields","scope":["hooks/statusline.ts","hooks/fields.ts","hooks/register.tsx","hooks/command.ts","docs/statusline.md"],"rationale":"The pane already shows plan and context detail, so one line suffices; picking fields beats a template that is easy to break","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Ordered field list (activity, ctx, 5h, week, cost, model, agents, plan, branch, changes, cwd) in $.store 'statusline' over a userConfig default, set by /glowup statusline fields and a /glowup config question","alternatives":["Multi-line layout like ccstatusline","Format-string template","Status line defined per pack"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d66","text":"Clawd gains juggle, scrunch, pant and pant-walk, a stretch wake-up, and a waving alert marked with a question mark","state":"adopted","ts":"2026-10-04T22:29:12+00:00","author":"claude-code","session":"","branch":"worktree-pets-animations","scope":["hooks/pets.ts, hooks/sprites/clawd.ts"],"rationale":"Owner picked these from the roadmap ideas and approved the art in the clawd-next viewer; every trigger uses a signal the model already tracks","supports":[],"depends_on":[],"answers":[],"supersedes":["d57"],"evidence":[{"ref":"docs/superpowers/mockups/clawd-next.html (gitignored)"}],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"Juggle at 3+ running subagents; scrunch for 1.5 s after any compaction; pant and pant-walk replace idle and walk at 80%+ context; wake-up is a stretch and yawn; alert waves with a red question mark so it no longer repeats the startle jump","alternatives":[],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d67","text":"Docked pane tabs are text-drawn boxed sections whose glyphs follow the pack's border, in the theme's faint color; the status box keeps borderColor; the compact drawer is unchanged","state":"adopted","ts":"2026-10-04T20:45:03+00:00","author":"claude-code","session":"","branch":"feat/pane-sections","scope":["hooks/pane.tsx, hooks/ctxchart.ts"],"rationale":"Owner picked boxed sections from mockups. Faint borders stop arcade and crt from stacking four loud frames. Text-drawn boxes fit a title in the edge and avoid box props the engine refuses.","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"boxed sections, faint, border-driven","alternatives":["A rule line styled by border and row style","One fixed layout, colors only","All boxes in borderColor"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d68","text":"The pet's status row in the pane shows usage left (the tighter of the 5-hour and weekly windows), session spend on API keys, and context only before the first usage reading; the band's hearts stay on context","state":"adopted","ts":"2026-10-04T22:08:57+00:00","author":"claude-code","session":"","branch":"main","scope":["pane"],"rationale":"Context is already charted in the Plan & context tab; usage left is what the person needs at a glance. The band stays on context because it is the only context view when the pane is closed.","supports":[],"depends_on":["d65"],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A few lines in lifeRow to revert","pinned":false,"choice":"usage, then spend, then context","alternatives":["context left (previous)","usage in the band too"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d69","text":"User guides are prose with rendered images; tables and code values live in reference pages","state":"adopted","ts":"2026-10-05T08:27:27+00:00","author":"claude-code","session":"","branch":"docs/plain-prose","scope":["README.md, docs/*.md"],"rationale":"Owner found the docs AI-sounding, table-heavy and full of obvious statements; tables are for technical reference and llms.txt only","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Guides use prose plus demo stills per pack and Clawd GIFs; tables, code values and file formats go in reference pages (pack-reference, theme-reference, commands, install options), which also feed llms-full.txt","alternatives":["Keep tables and code values throughout the guides"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d70","text":"The CONTEXT box shows the auto-compact point, growth per turn and turns left, the heaviest trimmable sources, and cache hit, drawn with a braille trend chart","state":"adopted","ts":"2026-10-05T08:37:22+00:00","author":"claude-code","session":"","branch":"feat/context-chart","scope":["hooks/ctxchart.ts","hooks/pane.tsx","hooks/register.tsx","docs/layout.md"],"rationale":"The breakdown already carries autoCompactThreshold, mcpTools, memoryFiles, skills, agents and apiUsage; the tab only used the categories, so it showed what fills the window but nothing to act on","supports":[["d68"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Taller CONTEXT box; on a short pane the tab falls back to whole-tab scrolling sooner","pinned":false,"choice":"Bar gets a ┊ auto-compact mark; a two-row braille area chart with a dashed auto-compact rule replaces the sparkline; growth line, top three heavy sources (MCP server, memory file, skill or agent listing), cache hit / peak / compactions line; all from the breakdown feedContext already fetches","alternatives":["Keep the one-row sparkline and add only the compaction marker and turns-left line"],"decided_by":"user"} -{"schema":2,"kind":"decision","id":"d71","text":"/glowup config opens a dedicated glowup pane with swatches and a live preview, replacing the question wizard","state":"adopted","ts":"2026-10-05T08:55:39+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/command.ts","hooks/pane.tsx"],"rationale":"Owner found the dialog wizard janky; dialogs take 2-4 text options and cannot show color","supports":[],"depends_on":[],"answers":[],"supersedes":["d60"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"reverse","choice":"Pane driven by up/down + enter: value rows cycle and apply at once, color rows are 5-swatch strips with role labels, Band/Tabs sub-lists toggle items, a Fine-tune on web link","alternatives":["Keep the native question wizard"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d72","text":"glowup.khimani.dev gains a /studio page for building a look, setup and pet with live preview","state":"adopted","ts":"2026-10-05T08:55:40+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["docs/web"],"rationale":"Colors were shown only as hex; a picker plus preview explains each role","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Per-role color pickers with plain labels that highlight in a terminal preview, setup controls, PNG pet upload; the landing's JSON textarea becomes a teaser","alternatives":["Extend the landing's JSON textarea"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d73","text":"Looks travel between the studio and Claude Code as a studio link with the data in its fragment","state":"adopted","ts":"2026-10-05T08:55:40+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/link.ts"],"rationale":"One link opens in a browser, installs in Claude, and shares without hosting","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"deflate+base64url JSON in #p= (look), s= (setup), pet= (small pet); /glowup pack decodes locally with the file validators and asks before applying a setup","alternatives":["Paste code with a new pack load command","Download JSON only"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d74","text":"Band items, pane tabs, meter thresholds, bubble timing and pet sleep become a personal setup kept out of packs","state":"adopted","ts":"2026-10-05T08:55:41+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/setup.ts"],"rationale":"Installing someone's look must not rearrange your layout","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Setup object in $.store (band items and order, tabs and order, meter warn/danger, bubble cadence and duration, pet sleep); share links carry only the look","alternatives":["Put them in the pack format"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d75","text":"Custom pets come from a PNG sprite sheet that follows a published spec","state":"adopted","ts":"2026-10-05T08:55:42+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/userpets.ts","docs/pet-sprites.md"],"rationale":"A fixed layout lets the studio take a PNG with no mapping screen; mods cannot decode images","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"24x12 px frames, one fixed-order row per animation, idle required, <=60 colors, auto-mirrored left walks, template PNG; studio converts PNG to PetSheet; pets live in ~/.claude/glowup/pets and install with /glowup pet add","alternatives":["Studio upload with free-form row mapping","Hand-written pet JSON only"],"decided_by":"owner"} -{"schema":2,"kind":"correction","id":"d73.1","corrects":"d73","fields":{"cost_if_wrong":"The link fragment is a public format; changing it breaks links people have shared, so it needs a version field"},"reason":"","ts":"2026-10-05T08:55:47+00:00","author":"claude-code","session":"","branch":"feat/config-studio"} -{"schema":2,"kind":"correction","id":"d75.1","corrects":"d75","fields":{"cost_if_wrong":"The sprite layout is a public contract; reordering rows breaks every sheet made from the template"},"reason":"","ts":"2026-10-05T08:55:48+00:00","author":"claude-code","session":"","branch":"feat/config-studio"} -{"schema":2,"kind":"decision","id":"d76","text":"Studio link parts are plain base64url JSON, encoded by a codec in link.ts, with a required v=1","state":"adopted","ts":"2026-10-05T09:03:21+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/link.ts"],"rationale":"The mod runtime's CompressionStream and atob are unverified; a pack is under 1 KB","supports":[],"depends_on":[],"answers":[],"supersedes":["d73"],"evidence":[],"revisit":"","cost_if_wrong":"The fragment is a public format; v lets it change later","pinned":false,"supersede_reason":"revise","choice":"No compression; hand-written base64url over UTF-8; #v=1&p=&s=&pet=; pet rides only under 4 KB encoded","alternatives":["deflate-raw via CompressionStream"],"decided_by":"claude"} -{"schema":2,"kind":"decision","id":"d77","text":"Setup meter defaults to warn 50 / danger 80, bubbles pick moods, and /glowup setup edits it","state":"adopted","ts":"2026-10-05T09:03:21+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/setup.ts"],"rationale":"The code's thresholds are 50/80 tones, a 70 pane warning and an 80 pant, not 60/80; bubbles fire on moods with no timer to tune","supports":[],"depends_on":[],"answers":[],"supersedes":["d74"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise","choice":"meter.warn/danger drive status-line tones, the pane's context warning (70 -> danger) and the pet's pant; bubbles.moods is a subset of needs-you, fail, done; /glowup setup [ |reset]","alternatives":["warn 60 / danger 80 and a bubble cadence setting"],"decided_by":"claude"} -{"schema":2,"kind":"decision","id":"d78","text":"A studio-link setup with unknown ids or bad fields is applied after the confirm, naming what it dropped; only a non-object setup part is refused","state":"adopted","ts":"2026-10-05T12:54:17+00:00","author":"claude-code","session":"session_01XHDkeQ4Bf8Ua9nABU9BiBt","branch":"feat/config-studio","scope":["hooks/command.ts","hooks/setup.ts"],"rationale":"The spec says a setup from a later glowup still loads; link text is untrusted, so notices are filtered before reaching the terminal","supports":[],"depends_on":["d77"],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"A bad field in a link resets that field to its default","pinned":false,"choice":"packFromLink applies parseSetup's result with notices listed as ' dropped: …'; notices pass through shown()","alternatives":["Refuse the whole setup on any notice"],"decided_by":"claude"} -{"schema":2,"kind":"decision","id":"d79","text":"Packs get custom drawing through renderer plugins that hook glowup.field, glowup.meter and glowup.divider","state":"adopted","ts":"2026-10-05T09:14:43+00:00","author":"claude-code","session":"session_01CJ5TH6CtY5ysoqEkjnQWq6","branch":"feat/render-events","scope":["hooks/register.tsx, hooks/pane.tsx, hooks/rows.tsx"],"rationale":"The mod runtime refuses import() and runs mods isolated, so glowup cannot run downloaded code; plugin dependencies are the sanctioned path and Claude Code's /plugin trust covers the code. A Client node a guest returns runs as the plugin that renders the tree, so animation has to cross as frames, not as the guest's Client.","supports":[],"depends_on":["d29","d52","d45"],"answers":[],"supersedes":[],"evidence":[{"ref":"Spike 2026-10-05 with --plugin-dir hostx+guestx on Claude Code 2.1.289: guest hook on hostx.field ran; returning a guest Client failed ('the plugin loaded no surface module'); returning 48 warp frames (48,481 chars of props) played at 83 ms in the host's Client."}],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"glowup adds a $.glowup noun whose methods field, meter and divider are events; a separate Claude Code plugin that depends on glowup hooks them and answers with data (colored text segments; the field as a looping list of frames). glowup plays field frames in its own Client on its own timer and falls back to its own drawing when nothing answers.","alternatives":["Generic data-only pack fields in glowup (meters: dither, field: warp)","glowup downloads and runs a pack's renderer code"],"decided_by":"user"} -{"schema":2,"kind":"claim","id":"c80","text":"On 2.1.289 a focused pane takes Input and Select: an Input takes typing, backspace and Enter, and up/down move past it; a Select opens when focused, holds up/down inside its options until Enter picks or Tab/Shift+Tab leave, and Escape there closes a closeOnEscape pane","state":"accepted","ts":"2026-10-05T13:23:12+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/pane.tsx"],"rationale":"tmux spike with a throwaway mod (scratchpad/spike) driving Button, Select, Input in one focused pane","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"pane rows built on a control that misbehaves in a live session","pinned":false} -{"schema":2,"kind":"decision","id":"d81","text":"/glowup config opens a dedicated glowup pane with swatches and a live preview, replacing the question wizard","state":"adopted","ts":"2026-10-05T13:23:33+00:00","author":"claude-code","session":"","branch":"feat/config-studio","scope":["hooks/pane.tsx"],"rationale":"Input works in a focused pane on 2.1.289 (c80), so exact hex and list order no longer need the studio","supports":[],"depends_on":["c80"],"answers":[],"supersedes":["d71"],"evidence":[],"revisit":"","cost_if_wrong":"pane rows rebuilt if Input behaves differently on desktop","pinned":false,"supersede_reason":"revise","choice":"Value rows (pack, spinner, pet, meter, bubbles) are Buttons Enter cycles; each color row is a swatch plus a hex Input; Band and Tabs are comma-list Inputs, so the pane can reorder them; no Select, so up/down walk the whole pane; a Fine-tune on web link","alternatives":["Selects for value rows: they hold up/down while focused, so rows would need Tab","Buttons only with 5-swatch color strips (the original choice)"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d82","text":"The built-in pack field is computed each tick in the pane's Client instead of played from precomputed frames","state":"adopted","ts":"2026-10-05T14:05:03+00:00","author":"claude-code","session":"session_01CJ5TH6CtY5ysoqEkjnQWq6","branch":"feat/live-field","scope":["hooks/client/field.tsx","hooks/effects.ts","hooks/packs.ts"],"rationale":"Frames cost about half a second per resize and stuttered past ~60 columns; live drawing costs a few ms a frame","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Packs using the object form need glowup 0.8.0; 0.7.0 refuses it","pinned":false,"choice":"client/field.tsx samples the field every tick (lattice every 4 dots, one colour per field so each row is one segment); motion.field takes none|warp|simplex or an object with Paper's dithering knobs","alternatives":["precompute a looping frame list per pane size, as renderer plugins answer"],"decided_by":""} -{"schema":2,"kind":"claim","id":"c83","text":"In the mod runtime the field's cost tracks rendered element count, not compute: per-cell shading (~40 runs a row) took 84% of a core, one segment a row costs about 20 points at fps 10 on 88x24","state":"accepted","ts":"2026-10-05T14:05:04+00:00","author":"claude-code","session":"session_01CJ5TH6CtY5ysoqEkjnQWq6","branch":"feat/live-field","scope":["hooks/client/field.tsx"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[{"ref":"live session A/B with and without the field, load avg 10-16"}],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"correction","id":"c83.1","corrects":"c83","fields":{"text":"In the mod runtime the field's cost tracks rendered element count, not compute: per-cell shading (~40 runs a row) put the whole process at 84% of a core, while one segment a row adds about 25 points over the same pack without a field at fps 10 on 88x24"},"reason":"match the 25-point figure in hooks/effects.ts and the docs","ts":"2026-10-05T14:12:43+00:00","author":"claude-code","session":"session_01CJ5TH6CtY5ysoqEkjnQWq6","branch":"feat/live-field"} -{"schema":2,"kind":"decision","id":"d84","text":"The simplex field reproduces Paper's dithering shader so Paper settings copy into a pack unchanged","state":"adopted","ts":"2026-10-05T14:37:56+00:00","author":"claude-code","session":"session_01CJ5TH6CtY5ysoqEkjnQWq6","branch":"fix/paper-field","scope":["hooks/effects.ts","docs/pack-reference.md"],"rationale":"Owner tunes looks in Paper's browser tool; a 24-row pane is 96 dots tall against Paper's ~257 dither pixels at size 2.8, so one dot per pixel is the closest the pane can come","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Packs written for 0.8.0's simplex framing (offsets in pane heights, size 3) look different after the change","pinned":false,"choice":"Port Paper's snoise, smoothstep(0.3,0.9) contrast, vertical drift, framing (y up, offsets in the short side, before scale and rotation) and Bayer lighting order; the pane height stands for Paper's 720 px canvas; size stays braille dots per dither pixel","alternatives":["keep glowup's value-noise simplex and tune it by eye","map Paper's size in pixels onto dots"],"decided_by":""} -{"schema":2,"kind":"decision","id":"d85","text":"The status line shows the effort level as a field, on by default","state":"adopted","ts":"2026-10-05T13:45:23+00:00","author":"claude-code","session":"","branch":"feat/config-pane","scope":["hooks/fields.ts"],"rationale":"glowup's status line replaces Claude Code's footer, which showed the effort level; owner noticed it missing","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"one more field in every default status line","pinned":false,"choice":"Field id effort, drawn as '◐ ', read from main-loop turn.step requests (subagent steps ignored); default fields become activity, ctx, effort, 5h, week","alternatives":[],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d86","text":"The crt pack's mascot is a hand-drawn CRT-monitor robot at 32x16, front-facing, with a dark plum outline","state":"adopted","ts":"2026-10-06T11:15:00+00:00","author":"claude-code","session":"","branch":"art/crt","scope":["art/crt/","hooks/sprites/crt.ts"],"rationale":"A cream case vanishes on light terminals (#f4efe6) without an outline; a 2-px shadow between planted feet reads as a dark block, not a shadow","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"owner rejects the style test","cost_if_wrong":"","pinned":false,"choice":"Cream case with plum outline (#3a2e3f), phosphor-green screen face with scanline rows, amber antenna tip, metal limbs; feet on row 15; no ground shadow under planted feet, shadow only under lifted feet and when airborne","alternatives":["No outline, like Clawd"],"decided_by":""} -{"schema":2,"kind":"decision","id":"d87","text":"art/build.py also writes -sheet.png (committed) and -sheet@8x.png (gitignored) in the public PNG pet layout","state":"adopted","ts":"2026-10-06T11:15:00+00:00","author":"claude-code","session":"","branch":"art/crt","scope":["art/build.py",".gitignore"],"rationale":"A gamma or color-profile chunk shifts colors so palette hexes stop round-tripping through the studio","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Rows in order idle, walk, working, hop, alert, done, sleep, fail, juggle, pant, pant-walk, scrunch; cells are the sheet's own WxH; RGBA with only IHDR/IDAT/IEND chunks; stdlib zlib+struct only","alternatives":[],"decided_by":""} -{"schema":2,"kind":"decision","id":"d88","text":"No pet animation other than idle may contain idle's base frame; art/check_distinct.py enforces it on the generated sheet","state":"adopted","ts":"2026-10-06T11:46:19+00:00","author":"claude-code","session":"","branch":"art/crt","scope":["art/crt/","art/check_distinct.py"],"rationale":"The owner rejected rows that read as idle with a face swap","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Clawd fails it today (hop, fail and scrunch end on his base frame), so it cannot gate every pet without a Clawd exemption","pinned":false,"choice":"Every crt animation is restaged with its own silhouette and props; art/check_distinct.py exits non-zero when a non-idle frame equals idle[0]","alternatives":[],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d89","text":"The studio page's background dither follows the look being built instead of having its own settings","state":"adopted","ts":"2026-10-06T09:06:02+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/web/"],"rationale":"motion.field already carries Paper's dithering knobs, so the background can show them at no UI cost","supports":[["d72","d84"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Redraw the background in the edited pack's colors and motion.field settings, so it previews the pane's field; no separate control","alternatives":["Fixed decorative background","Own sidebar controls for the background dither","Fixed background with an on/off toggle"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d90","text":"glowup ships a fox and a white stoat as built-in pets beside Clawd","state":"adopted","ts":"2026-10-06T09:24:56+00:00","author":"claude-code","session":"","branch":"main","scope":["art/","hooks/sprites/","hooks/pets.ts"],"rationale":"Owner asked for a playful red/orange fox and a white stoat in the custom-pets release","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"Hand-drawn in the clawd.art text format by an art agent, built by a generalized art/build.py into hooks/sprites/.ts; side-on facing right, the 12 spec animations only, no outfits, transitions or -left animations, so they also satisfy the user-pet rules","alternatives":["Ship them as example user pet files instead of built-ins"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d91","text":"The pet file format lives in a JSX-free hooks/petfile.ts and disk handling in hooks/userpets.ts, split like packs.ts and userpacks.ts","state":"adopted","ts":"2026-10-06T09:24:56+00:00","author":"claude-code","session":"","branch":"main","scope":["hooks/petfile.ts","hooks/userpets.ts"],"rationale":"The studio imports the validator, and userpets.ts needs the Host; fewer knobs keeps hand-written files simple","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Pet files written before a later format change would need format 2","pinned":false,"choice":"PetFile = {format 1, name, description?, palette, animations: {: frames[]}}; loop comes from the animation name (hop, fail, scrunch once); frames carry px, ms 80-10000, optional dx and exit; all frames share the first idle frame's size, at most 24x12","alternatives":["Validation inside userpets.ts as the spec names it","Let files carry loop, head and outfits"],"decided_by":""} -{"schema":2,"kind":"decision","id":"d92","text":"The studio decodes pet PNGs with the browser canvas and converts pixels with a pure function tested against a template PNG","state":"adopted","ts":"2026-10-06T09:24:56+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/web/app/studio/petpng.ts","docs/web/app/studio/PetSection.tsx","scripts/pet-template.ts"],"rationale":"The canvas reads every PNG variant (indexed, interlaced, 16-bit) for free; only our own template needs the test decoder","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"createImageBitmap with colorSpaceConversion none, then getImageData; petFromPixels in docs/web/app/studio/petpng.ts; node tests decode docs/assets/pet-template.png with a test-only decoder","alternatives":["A hand-written PNG decoder shared by browser and tests"],"decided_by":""} -{"schema":2,"kind":"decision","id":"d93","text":"A studio pet rides only in the Send to my Claude command and the page hash, never in the share link","state":"adopted","ts":"2026-10-06T09:24:56+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/web/app/studio/model.ts","docs/web/app/studio/Actions.tsx"],"rationale":"The share link is look-only per the spec and goes to chat apps; the send command is pasted into Claude Code, where length is not cut","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"If Claude Code mangles a 5-7 KB pasted command, pets would have to travel as files only","pinned":false,"choice":"Pet part included when it validates and encodes to at most PET_LINK_MAX (4096); the 2048-character long-link note applies to the share link only","alternatives":[],"decided_by":""} -{"schema":2,"kind":"decision","id":"d94","text":"The studio stage backdrop draws the edited look's own motion.field with PaneField, and nothing when the field is none","state":"adopted","ts":"2026-10-06T09:24:57+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/web/app/studio/Studio.tsx","docs/web/app/landing/PaneField.tsx"],"rationale":"The landing shader is not Paper's field, so only PaneField shows what the pane would draw","supports":[["d89"]],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":"PaneField with raised maxCols/maxRows behind .studio-preview","alternatives":["Recolor the landing's WebGL dither from the look"],"decided_by":""} -{"schema":2,"kind":"claim","id":"c95","text":"The pet sprite sheet is 144 px tall (12 rows of 12 px frames); the config-studio spec's 288 px is an arithmetic slip","state":"accepted","ts":"2026-10-06T09:24:57+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/pet-sprites.md","scripts/pet-template.ts"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false} -{"schema":2,"kind":"decision","id":"d96","text":"Pet frames go up to 32x16 px and the pane's pet strip is as tall as the pet in use","state":"adopted","ts":"2026-10-06T09:56:53+00:00","author":"claude-code","session":"","branch":"main","scope":["hooks/petfile.ts","hooks/pets.ts","hooks/client/pet.tsx","docs/web/app/studio/petpng.ts"],"rationale":"The 24x12 fox and stoat had 1-px eyes and legs, no room to jump, and poses that read alike; 40x20 would leave 8 columns to walk in a 48-column pane","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"Pet files and PNG sheets made for 32x16 would need redrawing if the cap ever shrinks","pinned":false,"choice":"FRAME_W 32, FRAME_H 16; PNG cells 32x16, converter crops to the box all frames share; strip rows = ceil(h/2), plus 2 while Clawd wears a hat; Clawd stays 24x12","alternatives":["Keep 24x12 and redesign the fox and stoat as front-facing chibis","40x20 frames"],"decided_by":"owner"} -{"schema":2,"kind":"claim","id":"c97","text":"The pet sprite sheet is 192 px tall: 12 rows of 32x16 cells","state":"accepted","ts":"2026-10-06T09:56:53+00:00","author":"claude-code","session":"","branch":"main","scope":["docs/pet-sprites.md","scripts/pet-template.ts"],"rationale":"","supports":[],"depends_on":[],"answers":[],"supersedes":["c95"],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"supersede_reason":"revise"} -{"schema":2,"kind":"decision","id":"d98","text":"glowup ships the CRT robot as a built-in pet beside Clawd; the fox and stoat are dropped","state":"adopted","ts":"2026-10-06T12:05:31+00:00","author":"claude-code","session":"","branch":"main","scope":["hooks/petfile.ts","hooks/pets.ts"],"rationale":"Owner rejected two fox/stoat rounds and the duck, then approved the robot's full sheet; robot is the word a person types after /glowup pet","supports":[],"depends_on":[],"answers":[],"supersedes":["d90"],"evidence":[],"revisit":"","cost_if_wrong":"A stored pet setting of robot would need migrating if renamed","pinned":false,"supersede_reason":"reverse","choice":"Pet id robot, drawn from hooks/sprites/crt.ts (CRT_SHEET); front-facing, the 12 spec animations, mirrored for walking left","alternatives":["Pet id crt, matching the sprite file"],"decided_by":"owner"} -{"schema":2,"kind":"decision","id":"d99","text":"The studio's per-animation speed control is a range slider over fixed speed steps","state":"adopted","ts":"2026-10-06T12:32:34+00:00","author":"claude-code","session":"","branch":"feat/custom-pets","scope":["docs/web/app/studio/PetSection.tsx"],"rationale":"The spec's constraint names a slider; the steps stay the same data","supports":[],"depends_on":[],"answers":[],"supersedes":[],"evidence":[],"revisit":"","cost_if_wrong":"","pinned":false,"choice":" over SPEEDS indices, showing the current multiplier","alternatives":["A of the six steps"], "answers": [], "author": "claude-code", "branch": "feat/custom-pets", "choice": " over SPEEDS indices, showing the current multiplier", "cost_if_wrong": "", "decided_by": "", "depends_on": [], "evidence": [], "id": "d80", "kind": "decision", "migrated_from": "d99", "pinned": false, "rationale": "The spec's constraint names a slider; the steps stay the same data", "revisit": "", "schema": 3, "scope": ["docs/web/app/studio/PetSection.tsx"], "session": "", "state": "adopted", "supersedes": [], "supports": [], "text": "The studio's per-animation speed control is a range slider over fixed speed steps", "ts": "2026-10-06T12:32:34+00:00"} +{"alternatives": ["Each task edits its own docs"], "answers": [], "author": "claude-code", "branch": "feat/custom-pets", "choice": "Task 9 carries every doc change; the branch merges with docs complete", "cost_if_wrong": "", "decided_by": "", "depends_on": [], "evidence": [], "id": "d81", "kind": "decision", "migrated_from": "d100", "pinned": false, "rationale": "Intermediate commits never ship alone; splitting doc edits across tasks churns the same pages five times", "revisit": "", "schema": 3, "scope": ["docs/"], "session": "", "state": "adopted", "supersedes": [], "supports": [], "text": "Custom-pets docs land in one docs task on the feature branch instead of with each code task", "ts": "2026-10-06T12:32:34+00:00"} +{"alternatives": ["Lazy import() of the robot in the studio"], "answers": [], "author": "claude-code", "branch": "feat/custom-pets", "choice": "Static import of hooks/sprites/crt.ts from pets.ts", "cost_if_wrong": "Move the import behind import() if the landing bundle budget matters", "decided_by": "", "depends_on": [], "evidence": [], "id": "d82", "kind": "decision", "migrated_from": "d101", "pinned": false, "rationale": "A few KB gzipped; a lazy split adds async loading to a JSX-free shared module", "revisit": "", "schema": 3, "scope": ["hooks/pets.ts", "docs/web/app/landing/data.ts"], "session": "", "state": "adopted", "supersedes": [], "supports": [], "text": "The site bundle carries the robot's sheet because pets.ts imports it", "ts": "2026-10-06T12:32:34+00:00"} +{"alternatives": ["Run the live session check first"], "answers": [], "author": "claude-code", "branch": "feat/custom-pets", "choice": "Release after the final branch review and its fix wave; automated suites and browser checks stand in for the live paste/add/cycle/fallback run", "cost_if_wrong": "A wiring bug only a real session shows reaches users in 0.9.0 and needs a patch release", "decided_by": "owner", "depends_on": [], "evidence": [], "id": "d83", "kind": "decision", "migrated_from": "d102", "pinned": false, "rationale": "Owner chose to release once the reviewer completes; the live check was blocked by the environment", "revisit": "", "schema": 3, "scope": [], "session": "", "state": "adopted", "supersedes": [], "supports": [], "text": "Custom pets ship as 0.9.0 without the live Claude Code session check", "ts": "2026-10-06T13:55:57+00:00"} +{"answers": [], "author": "claude-code", "branch": "feat/custom-pets", "cost_if_wrong": "A user pet restarts its animation at x=0 on every pane redraw, or a real pet change is missed", "depends_on": [], "evidence": [{"ref": "types/vendor/claude-code.d.ts"}], "id": "c9", "kind": "claim", "migrated_from": "c103", "pinned": false, "rationale": "types/vendor/claude-code.d.ts JsonValue: plain data crosses between the hooks module and the surface module whole, and the engine drops non-plain values where it clones; an identity check reset the player on every redraw in the cloned-props test", "revisit": "The live session shows a user pet freezing or not switching", "schema": 3, "scope": ["hooks/client/pet.tsx"], "session": "", "state": "accepted", "supersedes": [], "supports": [], "text": "A Client's props reach its surface module as a clone, so a user pet's sheet is a new object on every pane redraw and the pet Client must compare sheet content, not identity, to know the pet changed", "ts": "2026-10-06T20:58:14+00:00"} +{"answers": [], "author": "claude-code", "branch": "feat/custom-pets", "cost_if_wrong": "Studio links that carry a pet or a large pack go to the model and install nothing", "depends_on": [], "evidence": [], "id": "c10", "kind": "claim", "migrated_from": "c104", "pinned": false, "rationale": "tmux bracketed-paste probe: 600 chars ran as a command, 800 collapsed; the full send command with a pet reached the model; prompt.submit received the wrapped text with origin composer", "revisit": "Claude Code expands pastes before reading slash commands", "schema": 3, "scope": ["hooks/command.ts", "hooks/register.tsx"], "session": "", "state": "accepted", "supersedes": [], "supports": [], "text": "On Claude Code 2.1.291 a paste over about 700 characters sits in the prompt as [Pasted text #N] and is sent to the model wrapped in instead of running as a slash command, so a pasted studio send command never reached /glowup", "ts": "2026-10-06T21:14:27+00:00"} +{"alternatives": ["Studio copies only the link and tells people to type /glowup pack first"], "answers": [], "author": "claude-code", "branch": "feat/custom-pets", "choice": "glowup runs a prompt that is one /glowup line, bare or as one pasted block from the person's own Enter, through prompt.submit and drops it with the command's output as the reason", "cost_if_wrong": "A real prompt that is only a /glowup line is swallowed instead of reaching the model", "decided_by": "", "depends_on": [], "evidence": [], "id": "d84", "kind": "decision", "migrated_from": "d105", "pinned": false, "rationale": "The studio's Send to my Claude copies a command past the paste-collapse length whenever it carries a pet; typing /glowup pack and pasting only the link is a step studio users would miss", "revisit": "", "schema": 3, "scope": ["hooks/register.tsx"], "session": "", "state": "adopted", "supersedes": [], "supports": [["c10"]], "text": "A pasted /glowup command runs as the command", "ts": "2026-10-06T21:53:49+00:00"} +{"answers": [], "author": "claude-code", "branch": "feat/custom-pets", "cost_if_wrong": "", "depends_on": [], "evidence": [], "id": "c11", "kind": "claim", "migrated_from": "c106", "pinned": false, "rationale": "tmux run with --plugin-dir on feat/custom-pets at 7ab5216, screenshots via aha and headless chromium", "revisit": "", "schema": 3, "scope": [], "session": "", "state": "accepted", "supersedes": [], "supports": [["d83"]], "text": "The custom-pets live session check passed on Claude Code 2.1.291: pasted send command (pet; pack+setup+pet with the confirm), pet switches with the pane open, a user pet walking through a turn, pet add --force over the pet in use, the config Pet row skipping a broken file, and the deleted-pet fallback notice", "ts": "2026-10-06T21:54:05+00:00"} +{"answers": [], "author": "claude-code", "branch": "main", "cost_if_wrong": "", "depends_on": [], "evidence": [], "id": "c12", "kind": "claim", "migrated_from": "c107", "pinned": false, "rationale": "Timestamped render asks against 30 ms screen samples on 2.1.291: the hook gets the new bodyRows at once; with its state reads capped at 40 ms the box still lost its bottom for 1.4 s, and sizing the field Client changed nothing", "revisit": "a Claude Code release that re-asks pane hooks before drawing a resized pane", "schema": 3, "scope": ["hooks/pane.tsx", "hooks/register.tsx"], "session": "", "state": "accepted", "supersedes": [], "supports": [], "text": "When the agent list opens under the prompt, Claude Code draws the docked pane's previous tree in the shrunken room for 0.2-1.4 s, cutting off the status box; glowup's hook answering late is not the cause and glowup cannot prevent it", "ts": "2026-10-06T16:57:02+00:00"} +{"author": "claude-code", "branch": "main", "corrects": "c12", "fields": {"cost_if_wrong": "a glowup-side fix exists and the pane keeps flashing a cut status box whenever subagents start"}, "id": "c12.1", "kind": "correction", "reason": "", "schema": 3, "session": "", "ts": "2026-10-06T17:00:30+00:00"} +{"answers": ["q7"], "author": "claude-code", "branch": "main", "cost_if_wrong": "The Konami egg is built on a key path that does not reach the mod", "depends_on": [], "evidence": [], "id": "c13", "kind": "claim", "migrated_from": "c108", "pinned": false, "rationale": "tmux spike with a throwaway mod (scratchpad/keyspike): after an SGR mouse click on the Client, ↑↑↓↓←→←→BA arrived as all ten keys; without the click, b and a landed in the prompt", "revisit": "A Claude Code release changes Client focus or onKey", "schema": 3, "scope": [], "session": "", "state": "accepted", "supersedes": [], "supports": [], "text": "On Claude Code 2.1.291 a Client's surface.onKey receives arrow keys (up, down, left, right) and letters once the person clicks the Client; before a click the keys go to the prompt, and Escape returns the focus", "ts": "2026-10-07T06:40:08+00:00"} +{"alternatives": ["Crack overlays on one sheet", "Typing konami at the prompt to open a code dialog"], "answers": [], "author": "claude-code", "branch": "main", "choice": "Click Clawd's pet Client (it hops), type up up down down left right left right b a; the Client matches locally and posts once; the egg is a hidden built-in pet with four hand-drawn sheets, one per crack stage, cracking every 10 passing runs to stage 3", "cost_if_wrong": "Four sheets of art for one egg, and the code needs a terminal with mouse events", "decided_by": "owner", "depends_on": [], "evidence": [], "id": "d85", "kind": "decision", "migrated_from": "d109", "pinned": false, "rationale": "Owner picked 3 stages of 10 runs, full sheets per stage so cracks tilt with the wobble, and a hop on click with a quiet code", "revisit": "", "schema": 3, "scope": ["hooks/client/pet.tsx", "hooks/eggs.ts"], "session": "", "state": "adopted", "supersedes": [], "supports": [["c13"]], "text": "The Konami egg unlocks from a click on Clawd and the code typed into his Client", "ts": "2026-10-07T07:02:03+00:00"} +{"answers": [], "author": "claude-code", "branch": "feat/konami-egg", "cost_if_wrong": "", "depends_on": [], "evidence": [], "id": "q12", "kind": "question", "migrated_from": "q110", "pinned": false, "rationale": "", "revisit": "", "schema": 3, "scope": ["hooks/eggs.ts"], "session": "", "state": "open", "supersedes": [], "supports": [], "text": "What does the Konami egg hatch into, and when?", "ts": "2026-10-07T08:54:10+00:00"}