# even-toolkit

Design system & component library for **Even Realities G2** smart glasses apps.

55+ web components, 191 pixel-art icons, glasses SDK bridge with per-screen architecture, pixel-accurate G2 text measurement, speech-to-text module, light/dark themes, and design tokens — all following the Even Realities 2025 UIUX Design Guidelines.

**[Live Demo → even-demo.vercel.app](https://even-demo.vercel.app/)**

## Install

```
npm install even-toolkit
```

Scaffold a new app instantly:

```
npx @even-toolkit/create-even-app my-app
# or
npx even-toolkit my-app
```

Choose from 6 templates: minimal, dashboard, notes, chat, tracker, media.

## What's Inside

### `/web` — Web Component Library

55+ React components with Tailwind CSS, designed for mobile-first companion apps.

```
import { Button, Card, NavBar, ListItem, Toggle, AppShell } from 'even-toolkit/web';
```

**Primitives:** Button, Card, Badge, Input, Textarea, Select, MultiSelect, Checkbox, RadioGroup, Slider, InputGroup, Skeleton, Progress, StatusDot, Pill, Toggle, SegmentedControl, Table, Kbd, Divider

**Layout:** AppShell, Page, NavBar, NavHeader, SideDrawer, DrawerShell, DrawerTrigger, ScreenHeader, SectionHeader, SettingsGroup, CategoryFilter, ListItem (swipe-to-delete), SearchBar, Tag, TagCarousel, TagCard, PagedCarousel, CardCarousel, SliderIndicator, PageIndicator, StepIndicator, Timeline, StatGrid, StatusProgress

**Feedback:** TimerRing, Dialog, ConfirmDialog, Toast, EmptyState, Loading, BottomSheet, CTAGroup, ScrollPicker, DatePicker, TimePicker, SelectionPicker

**Charts (recharts):** Sparkline, LineChart, BarChart, PieChart, StatCard

**Media:** ChatContainer, ChatInput, Calendar, FileUpload, VoiceInput, ImageGrid, ImageViewer, AudioPlayer

### `/web/icons` — 191 Pixel-Art Icons

Official Even Realities icon set: 32x32 grid, 2x2px units, 6 categories.

```
import { IcChevronBack, IcTrash, IcSettings } from 'even-toolkit/web/icons/svg-icons';

<IcChevronBack width={20} height={20} />
```

**Categories:** Edit & Settings (32), Feature & Function (50), Guide System (20), Menu Bar (8), Navigate (23), Status (54), Health (12)

## Glasses SDK

Everything needed to build G2 glasses apps with a clean, per-screen architecture.

### Per-Screen Architecture (v1.4)

Each glasses screen lives in its own file with co-located display + action logic:

```
src/glass/
  shared.ts              — Snapshot type + actions interface
  selectors.ts           — Screen router (3 lines of wiring)
  splash.ts              — Splash image + loading text
  AppGlasses.tsx         — useGlasses hook setup
  screens/
    home.ts              — { display, action }
    detail.ts            — { display, action }
    active.ts            — { display, action }
```

#### Define a screen

```
import type { GlassScreen } from 'even-toolkit/glass-screen-router';
import { buildScrollableList } from 'even-toolkit/glass-display-builders';
import { moveHighlight } from 'even-toolkit/glass-nav';

export const homeScreen: GlassScreen<MySnapshot, MyActions> = {
  display(snapshot, nav) {
    return {
      lines: buildScrollableList({
        items: snapshot.items,
        highlightedIndex: nav.highlightedIndex,
        maxVisible: 5,
        formatter: (item) => item.title,
      }),
    };
  },

action(action, nav, snapshot, ctx) {
    if (action.type === 'HIGHLIGHT_MOVE') {
      return { ...nav, highlightedIndex: moveHighlight(nav.highlightedIndex, action.direction, snapshot.items.length - 1) };
    }
    if (action.type === 'SELECT_HIGHLIGHTED') {
      ctx.navigate(`/item/${snapshot.items[nav.highlightedIndex].id}`);
      return nav;
    }
    return nav;
  },
};
```

#### Wire screens together

```
import { createGlassScreenRouter } from 'even-toolkit/glass-screen-router';
import { homeScreen } from './screens/home';
import { detailScreen } from './screens/detail';

export const { toDisplayData, onGlassAction } = createGlassScreenRouter({
  'home': homeScreen,
  'detail': detailScreen,
}, 'home');
```

### Navigation Helpers (`glass-nav`)

```
import { moveHighlight, clampIndex, calcMaxScroll, wrapIndex } from 'even-toolkit/glass-nav';

// Clamped movement (0 to max)
moveHighlight(current, 'up', max)    // Math.max(0, Math.min(max, current - 1))
moveHighlight(current, 'down', max)  // Math.max(0, Math.min(max, current + 1))

// Clamp index to button count
clampIndex(index, buttonCount)       // Math.min(Math.max(0, index), count - 1)

// Max scroll offset
calcMaxScroll(totalLines, slots)     // Math.max(0, totalLines - slots)

// Wrapping movement (loops around)
wrapIndex(current, 'down', count)    // (current + 1) % count
```

### Display Builders (`glass-display-builders`)

```
import {
  buildScrollableList,
  buildScrollableContent,
  slidingWindowStart,
  G2_TEXT_LINES,          // 10
  DEFAULT_CONTENT_SLOTS,  // 7 (below glassHeader)
} from 'even-toolkit/glass-display-builders';

// Scrollable highlighted list with scroll indicators
const lines = buildScrollableList({
  items: recipes,
  highlightedIndex: nav.highlightedIndex,
  maxVisible: 5,
  formatter: (r) => r.title,
});

// Header + scrollable content with indicators
const display = buildScrollableContent({
  title: 'Recipe Detail',
  actionBar: buildStaticActionBar(['Start'], 0),
  contentLines: ['Line 1', 'Line 2', ...],
  scrollPos: nav.highlightedIndex,
});
```

### Pixel-Accurate Text Measurement (`pretext`)

Use `even-toolkit/pretext` when character-count wrapping is not precise enough. It wraps `@evenrealities/pretext` and uses the same LVGL font metrics as Even Hub for G2 text width, truncation, and wrapped-height prediction.

```
import {
  getTextWidth,
  measureGlassText,
  truncateGlassText,
  G2_TEXT_MAX_WIDTH,
} from 'even-toolkit/pretext';

const title = truncateGlassText('A long glasses title', { width: G2_TEXT_MAX_WIDTH, paddingX: 12 });

const measured = measureGlassText('The quick brown fox jumps over the lazy dog', {
  width: 300,
  paddingX: 8,
  borderWidth: 2,
});

console.log(measured.lineCount, measured.height, measured.maxLineWidth);
console.log(getTextWidth(title));
```

## Speech-to-Text (STT)

Provider-agnostic speech-to-text module for voice input in G2 glasses apps.

### Quick Start

```
import { useSTT } from 'even-toolkit/stt/react';

function VoiceInput() {
  const { transcript, isListening, start, stop } = useSTT({
    provider: 'soniox',
    language: 'en-US',
    apiKey: 'your-soniox-key',
  });

return (
    <div>
      <button onClick={isListening ? stop : start}>
        {isListening ? 'Stop' : 'Record'}
      </button>
      <p>{transcript}</p>
    </div>
  );
}
```

### Configuration

```
useSTT({
  provider: 'soniox',
  language: 'en-US',        // BCP-47 language tag
  apiKey: 'your-key',       // Required
  vad: { silenceMs: 2500 }, // Auto-stop after silence
  chunkIntervalMs: 4000,    // Progressive transcription interval
  continuous: false,         // Don't auto-stop on silence
})
```

## Design Tokens

Light theme following Even Realities 2025 guidelines:

```
@import "even-toolkit/web/theme-light.css";
@import "even-toolkit/web/typography.css";
@import "even-toolkit/web/utilities.css";
```

| Token | Value | Usage |
| --- | --- | --- |
| `--color-text` | #232323 | Primary text (TC-1st) |
| `--color-text-dim` | #7B7B7B | Secondary text (TC-2nd) |
| `--color-bg` | #EEEEEE | Page background (BC-3rd) |
| `--color-surface` | #FFFFFF | Card/component background (BC-1st) |
| `--color-accent` | #232323 | Accent/highlight (BC-Highlight) |
| `--color-positive` | #4BB956 | Success/connected (TC-Green) |
| `--color-negative` | #FF453A | Error/warning (TC-Red) |
| `--color-accent-warning` | #FEF991 | Active/toast (BC-Accent) |
| `--radius-default` | 6px | Default border radius |
| `--font-display` | FK Grotesk Neue | Display & body font |

## Typography

| Style | Size | Weight | Tracking |
| --- | --- | --- | --- |
| Very Large Title | 24px | 400 | -0.72px |
| Large Title | 20px | 400 | -0.6px |
| Medium Title | 17px | 400 | -0.17px |
| Medium Body | 17px | 300 | -0.17px |
| Normal Title | 15px | 400 | -0.15px |
| Normal Body | 15px | 300 | -0.15px |
| Normal Subtitle | 13px | 400 | -0.13px |
| Normal Detail | 11px | 400 | -0.11px |
