import { Box, Text } from '@hermes/ink'
import { memo } from 'react'
// A cell is [tr,tg,tb,ta, br,bg,bb,ba] — the top + bottom pixel of one
// half-block, as produced by the `pet.cells` gateway RPC.
export type PetCell = number[]
export type PetGrid = PetCell[][]
const UPPER_HALF = '▀'
const LOWER_HALF = '▄'
const hex = (r: number, g: number, b: number) =>
`#${[r, g, b]
.map(v =>
Math.max(0, Math.min(255, v | 0))
.toString(16)
.padStart(2, '0')
)
.join('')}`
/**
* Renders one petdex frame as truecolor half-blocks using native Ink color
* props (no raw ANSI, so width measurement stays correct). The engine
* (`agent/pet/render.py`) does the decode + downscale; this is a thin painter.
*/
export const PetSprite = memo(function PetSprite({ grid }: { grid: PetGrid }) {
if (!grid.length) {
return null
}
return (
{grid.map((row, y) => (
{row.map((cell, x) => {
const [tr, tg, tb, ta, br, bg, bb, ba] = cell
const top = (ta ?? 0) >= 32
const bot = (ba ?? 0) >= 32
if (!top && !bot) {
return
}
// Both halves opaque → fg=top over bg=bottom. One half opaque →
// draw it fg-only so the other stays the terminal bg (no black
// boxes bleeding around transparent sprite edges).
if (top && bot) {
return (
{UPPER_HALF}
)
}
return top ? (
{UPPER_HALF}
) : (
{LOWER_HALF}
)
})}
))}
)
})
/**
* Renders a kitty Unicode-placeholder grid: each line is a row of U+10EEEE
* cells whose foreground color encodes the image id. The actual pixels are
* drawn by the terminal (the frame image is transmitted out-of-band by
* `usePet`); this only emits the placeholder text Ink can measure as width-1
* cells. Truecolor-only — the color must reach the terminal verbatim for the
* id to decode, which Ghostty/kitty support.
*/
export const PetKitty = memo(function PetKitty({ color, placeholder }: { color: string; placeholder: string[] }) {
if (!placeholder.length) {
return null
}
return (
{placeholder.map((row, y) => (
{row}
))}
)
})