From everything-evenhub
Builds UI for Even Hub G2 glasses displays using text containers, lists, images, page lifecycle, and layout patterns on 576x288 greyscale canvas. For creating or updating glasses app content.
npx claudepluginhub even-realities/everything-evenhub --plugin everything-evenhubThis skill is limited to using the following tools:
The G2 glasses display is a **576x288 px** canvas with the following characteristics:
Provides UI/UX design guidelines for Even Hub G2 smart glasses including display constraints, container limits, layout patterns, icons, Unicode characters, and resources. Use when designing glasses app interfaces or layouts.
Designs UI/UX for AI assistants with glass-morphism, spatial layouts, HUDs, attention management, visual hierarchy, and accessibility. Provides CSS color palettes and typography scales.
Generates pixel-precise ASCII TUI Swiper components with TUI_RENDER, COMPONENT_SPEC, PENCIL_SPEC, and PENCIL_BATCH_DESIGN outputs for Pencil MCP terminal UI workflows. Use for text-based swiper creation.
Share bugs, ideas, or general feedback.
The G2 glasses display is a 576x288 px canvas with the following characteristics:
Every page is composed of containers. The following limits apply per page:
isEventCapture: 1 — this container receives user input eventscontainerID must be unique per page (number)containerName must be unique per page (string, max 16 characters)All container types share these properties:
| Property | Type | Range | Notes |
|---|---|---|---|
xPosition | number | 0–576 | Left edge of container |
yPosition | number | 0–288 | Top edge of container |
width | number | 0–576 | Container width in pixels |
height | number | 0–288 | Container height in pixels |
containerID | number | — | Unique per page |
containerName | string | max 16 chars | Unique per page |
isEventCapture | number | 0 or 1 | Exactly one container must be 1 |
Border properties apply to text and list containers only (not image containers):
| Property | Type | Range | Notes |
|---|---|---|---|
borderWidth | number | 0–5 | 0 = no border |
borderColor | number | 0–15 | Greyscale level (0 = black, 15 = white/bright green) |
borderRadius | number | 0–10 | Rounded corner radius |
paddingLength | number | 0–32 | Uniform padding applied to all sides |
Text containers display scrollable or static text content.
| Property | Type | Range | Notes |
|---|---|---|---|
xPosition | number | 0–576 | Left edge |
yPosition | number | 0–288 | Top edge |
width | number | 0–576 | Container width |
height | number | 0–288 | Container height |
containerID | number | — | Unique per page |
containerName | string | max 16 chars | Unique per page |
isEventCapture | number | 0 or 1 | Exactly one must be 1 |
borderWidth | number | 0–5 | 0 = no border |
borderColor | number | 0–15 | Greyscale level |
borderRadius | number | 0–10 | Rounded corners |
paddingLength | number | 0–32 | Uniform padding |
content | string | — | Text to display |
Content limits:
createStartUpPageContainer: max 1000 characterstextContainerUpgrade: max 2000 charactersrebuildPageContainer: max 1000 charactersText behavior:
\n for explicit line breaksList containers display a scrollable list of selectable items managed by the native firmware.
ListItemContainerProperty defines the list items:
| Property | Type | Range | Notes |
|---|---|---|---|
itemCount | number | 1–20 | Number of items in the list |
itemWidth | number | 0–576 | Item width; 0 = auto fill container width |
isItemSelectBorderEn | number | 0 or 1 | Enable selection border highlight |
itemName | string[] | max 20 items, max 64 chars each | Array of item label strings |
List container behavior:
Image containers render 4-bit greyscale bitmap images.
| Property | Type | Range | Notes |
|---|---|---|---|
xPosition | number | 0–576 | Left edge |
yPosition | number | 0–288 | Top edge |
width | number | 20–288 | Container width in pixels |
height | number | 20–144 | Container height in pixels |
containerID | number | — | Unique per page |
containerName | string | max 16 chars | Unique per page |
Image containers do not support isEventCapture. Use a text container as the event-capture layer when combining with images (see Image-Based App Pattern below).
Image data format:
number[] | Uint8Array | ArrayBuffer | base64 stringPreprocessing is optional. You don't need to pre-grayscale or dither before sending — the SDK converts common formats internally and only returns imageToGray4Failed if it can't. Line art, icons, and QR codes usually render fine raw; photos and gradients benefit from a client-side contrast boost + Floyd–Steinberg dither pass on a 16-shade display, but try the naive path first.
Critical behavior:
updateImageRawData is calledupdateImageRawData after creating an image container to display contentcreateStartUpPageContainer(container)Called once at app startup to create the initial page layout.
createStartUpPageContainer(container: CreateStartUpPageContainer): Promise<StartUpPageCreateResult>
Return codes:
0 — success1 — invalid parameters2 — oversize (content too large)3 — out of memoryrebuildPageContainer(container)Fully redraws a page. Use when changing layout structure (adding/removing containers, switching container types).
rebuildPageContainer(container: RebuildPageContainer): Promise<boolean>
true on successtextContainerUpgrade(container)Updates text content in-place without rebuilding the page. Flicker-free.
textContainerUpgrade(container: TextContainerUpgrade): Promise<boolean>
true on successcontainerID and containerName must exactly match the existing containercontentOffset: 0, contentLength: 0 for full content replacementupdateImageRawData({ containerID, containerName, imageData })Sends pixel data to an image container.
updateImageRawData(params: {
containerID: number
containerName: string
imageData: number[] | Uint8Array | ArrayBuffer | string
}): Promise<string>
Return status strings:
'success' — image displayed'imageException' — general image error'imageSizeInvalid' — image dimensions do not match container'imageToGray4Failed' — color conversion failed'sendFailed' — transmission errorDo not call concurrently — wait for the previous call to resolve before sending another image.
shutDownPageContainer(exitMode?)Exits the current page.
shutDownPageContainer(exitMode?: number): Promise<boolean>
exitMode: 0 — immediate exit with no confirmationexitMode: 1 — show exit confirmation dialogtextContainerUpgrade for frequent updates (counters, status lines, live data feeds) — it updates in-place with no flickerrebuildPageContainer when changing layout — adding or removing containers, switching container types, or updating list itemscontainerID and containerName exactly when calling textContainerUpgrade — mismatches silently failupdateImageRawData concurrently — queue updates and await each before sending the nextrebuildPageContainer on scroll eventsawait each before starting the next; concurrent render + storage calls can crash the connectionPromise.race with a few-second capsetLocalStorage shares the same BLE link; debounce on tick/page-turn and flush on exitcreateStartUpPageContainer exactly once — every subsequent render uses rebuildPageContainer or a *Upgrade callPrefix text items with > as a cursor indicator to simulate a button or selection state:
> Start
Settings
Exit
Toggle borderWidth on individual text containers between 0 (unselected) and a nonzero value (selected) to indicate focus. Requires rebuildPageContainer.
Stack multiple text containers vertically. For three equal rows on a 288px canvas:
// Row 1: y=0, height=96
// Row 2: y=96, height=96
// Row 3: y=192, height=96
Use Unicode block characters to draw progress bars within a text container:
const filled = Math.round((progress / 100) * barWidth)
const bar = '━'.repeat(filled) + '─'.repeat(barWidth - filled)
content = `Progress: ${bar} ${progress}%`
Pre-paginate content at ~400–500 character boundaries. On scroll/navigation events, call rebuildPageContainer with the new page slice:
const pages = paginateText(fullText, 450)
let currentPage = 0
async function showPage(index: number) {
currentPage = index
await bridge.rebuildPageContainer({
containerTotalNum: 1,
textObject: [{ ...containerConfig, content: pages[index] }],
})
}
import {
waitForEvenAppBridge,
type TextContainerProperty,
type ImageContainerProperty,
} from '@evenrealities/even_hub_sdk'
const bridge = await waitForEvenAppBridge()
// Create initial page with text and image containers
const textContainer: TextContainerProperty = {
xPosition: 0, yPosition: 0, width: 576, height: 200,
borderWidth: 0, borderColor: 5, paddingLength: 4,
containerID: 1, containerName: 'main',
content: 'Hello from G2!',
isEventCapture: 1,
}
const imageContainer: ImageContainerProperty = {
xPosition: 200, yPosition: 210, width: 100, height: 60,
containerID: 2, containerName: 'icon',
}
const result = await bridge.createStartUpPageContainer({
containerTotalNum: 2,
textObject: [textContainer],
imageObject: [imageContainer],
})
if (result === 0) {
// Update image content after creation
await bridge.updateImageRawData({
containerID: 2,
containerName: 'icon',
imageData: [/* pixel data */],
})
// Flicker-free text update
await bridge.textContainerUpgrade({
containerID: 1,
containerName: 'main',
content: 'Updated text!',
contentOffset: 0,
contentLength: 0,
})
}
When building an image-first app (e.g., rendering a canvas or bitmap as the primary display), use a full-screen text container as the event capture layer behind the image container:
// Full-screen transparent text container — receives events, invisible to user
const eventLayer: TextContainerProperty = {
xPosition: 0, yPosition: 0, width: 576, height: 288,
containerID: 1, containerName: 'eventLayer',
content: ' ', // single space — required, cannot be empty
isEventCapture: 1, // this layer catches all input events
borderWidth: 0, borderColor: 0, paddingLength: 0,
}
// Image container renders on top of the event layer
const imageLayer: ImageContainerProperty = {
xPosition: 0, yPosition: 0, width: 200, height: 100,
containerID: 2, containerName: 'display',
// isEventCapture: 0 (default) — image containers do not capture events
}
await bridge.createStartUpPageContainer({
containerTotalNum: 2,
textObject: [eventLayer],
imageObject: [imageLayer],
})
// Always send image data after creation
await bridge.updateImageRawData({
containerID: 2,
containerName: 'display',
imageData: pixelData,
})
The text container receives events; the image container draws on top.
$ARGUMENTS