Skill: peetzweg/trmnl (version 1.0.3)

--- name: trmnl description: Generate content for TRMNL e-ink display devices using the TRMNL CSS framework and send via the trmnl CLI. Use when the user wants to display information on their TRMNL device, send messages to an e-ink display, create dashboard content, show notifications, or update their terminal display. Supports rich layouts with the TRMNL framework (flexbox, grid, tables, progress bars, typography utilities). --- # TRMNL Content Generator Generate HTML content for TRMNL e-ink display devices. ## Prerequisites **Install the `trmnl` CLI to the latest version:** ```bash npm install -g trmnl-cli@latest ``` **Configure a webhook plugin (one-time setup):** ```bash # Add a plugin trmnl plugin add home "https://trmnl.com/api/custom_plugins/{uuid}" # Verify it's configured trmnl plugin ``` ## Quick Start Workflow 1. **Install/update CLI:** Run `npm install -g trmnl-cli@latest` 2. **Check plugins:** Run `trmnl plugin` - if none, prompt user to add one 3. Confirm device type (default: TRMNL OG, 2-bit, 800x480) 4. Read relevant reference docs based on content needs 5. Generate HTML using TRMNL framework classes 6. Write HTML to a temp file and send: ```bash trmnl send --file /tmp/trmnl-content.html # Or to a specific plugin: trmnl send --file /tmp/trmnl-content.html --plugin office ``` 7. **Minimal confirmation only** - Do NOT echo content back to chat ## Sending Content **From file (recommended):** ```bash # Write HTML content to file first cat > /tmp/trmnl-content.html << 'EOF' <div class="layout layout--col gap--space-between"> <div class="item"> <span class="value value--xlarge value--tnums">Hello TRMNL!</span> </div> </div> <div class="title_bar"> <span class="title">My Plugin</span> </div> EOF # Send to display trmnl send --file /tmp/trmnl-content.html ``` **Validate before sending:** ```bash trmnl validate --file /tmp/trmnl-content.html ``` **View send history:** ```bash trmnl history trmnl history --today trmnl history --failed ``` ## Webhook Limits | Tier | Payload Size | Rate Limit | |------|--------------|------------| | Free | **2 KB** (2,048 bytes) | 12 requests/hour | | TRMNL+ | **5 KB** (5,120 bytes) | 30 requests/hour | Set tier globally for accurate validation: ```bash trmnl tier plus # or "free" ``` ## Reference Documentation Read these files as needed: | File | When to Read | |------|--------------| | `references/patterns.md` | **Start here** - Common plugin patterns | | `references/framework-overview.md` | Device specs, e-ink constraints | | `references/css-utilities.md` | Colors, typography, sizing, spacing | | `references/layout-systems.md` | Flexbox, grid, overflow engines | | `references/components.md` | Title bar, dividers, items, tables | | `references/webhook-api.md` | Payload format, troubleshooting | | `assets/anti-patterns.md` | Common mistakes to avoid | ## Standard Plugin Structure **Every plugin follows this pattern:** ```html <div class="layout layout--col gap--space-between"> <!-- Content sections separated by dividers --> </div> <div class="title_bar"> <img class="image" src="icon.svg"> <span class="title">Plugin Name</span> <span class="instance">Context</span> </div> ``` - `layout` + `layout--col` = vertical flex container - `gap--space-between` = push sections to edges - `title_bar` = always at bottom, outside layout - `divider` = separate major sections - **CRITICAL:** Only ONE `.layout` element per view ## Quick Reference ### Grid System (10-Column) ```html <div class="grid"> <div class="col--span-3">30%</div> <div class="col--span-7">70%</div> </div> ``` ### Item Component ```html <div class="item"> <div class="content"> <span class="value value--xlarge value--tnums">$159,022</span> <span class="label">Total Sales</span> </div> </div> ``` ### Value Typography **Always use `value--tnums` for numbers.** | Class | Usage | |-------|-------| | `value--xxxlarge` | Hero KPIs | | `value--xxlarge` | Large prices | | `value--xlarge` | Secondary metrics | | `value--tnums` | **Required for numbers** | ### Grayscale Classes Use dithered classes, not inline colors: - `bg--black`, `bg--gray-60`, `bg--gray-30`, `bg--gray-10`, `bg--white` - `text--black`, `text--gray-50` ### Data Attributes | Attribute | Purpose | |-----------|---------| | `data-fit-value="true"` | Auto-resize text to fit | | `data-clamp="N"` | Limit to N lines | | `data-overflow="true"` | Enable overflow management | ## Best Practices 1. Use `layout` + `title_bar` structure 2. Always `value--tnums` for numbers 3. Use `data-fit-value` on primary metrics 4. Use `bg--gray-*` dithered classes 5. Keep payload under tier limit 6. **Minimal confirmations** - just "Sent to TRMNL" ## Troubleshooting | Problem | Solution | |---------|----------| | `trmnl: command not found` | Run `npm install -g trmnl-cli@latest` | | No plugins configured | Run `trmnl plugin add <name> <url>` | | Webhook fails | `trmnl config` - verify plugin URL | | Payload too large | `trmnl validate --file` - check size | | Numbers misaligned | Add `value--tnums` class | | Send history | `trmnl history --failed` |

path: assets/anti-patterns.md
content: # Anti-Patterns to Avoid

## 1. Excessive Inline Styles

**BAD:**
```html
<div style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 20px; padding: 40px; height: 480px; width: 800px;">
  <span style="font-size: 48px; font-weight: bold; text-align: center;">Title</span>
</div>
```

**GOOD:**
```html
<div class="layout layout--col layout--center gap--large" style="height: 100%; padding: 40px;">
  <span class="value value--xxlarge text--center">Title</span>
</div>
```

**Why:** Framework classes are optimized for e-ink, responsive, and token-efficient.

## 2. Hard-Coded Pixel Dimensions

**BAD:**
```html
<div style="width: 800px; height: 480px;">
  <div style="width: 400px;">Left Column</div>
  <div style="width: 400px;">Right Column</div>
</div>
```

**GOOD:**
```html
<div class="grid grid--cols-2 gap--large" style="height: 100%;">
  <div class="col">Left Column</div>
  <div class="col">Right Column</div>
</div>
```

**Why:** Different devices have different resolutions. Use responsive utilities and percentage-based sizing.

## 3. Missing Text Wrappers in Tables

**BAD:**
```html
<table class="table">
  <thead>
    <tr>
      <th>Header</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Data</td>
    </tr>
  </tbody>
</table>
```

**GOOD:**
```html
<table class="table">
  <thead>
    <tr>
      <th><span class="title">Header</span></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><span class="label">Data</span></td>
    </tr>
  </tbody>
</table>
```

**Why:** Framework expects text wrapped in semantic span elements for proper styling.

## 4. Color Values Instead of Framework Classes

**BAD:**
```html
<div style="background-color: #CCCCCC; color: #000000;">
  <span style="color: #666666;">Subtitle</span>
</div>
```

**GOOD:**
```html
<div class="bg--gray-50">
  <span class="text--black">Title</span>
  <span class="text--gray-40">Subtitle</span>
</div>
```

**Why:** Framework colors use dithering patterns optimized for e-ink displays.

## 5. Ignoring E-ink Constraints

**BAD:**
```html
<img src="color-photo.jpg" style="filter: blur(5px); transform: rotate(15deg);">
<div style="animation: pulse 2s infinite;">Animated content</div>
```

**GOOD:**
```html
<img src="photo.jpg" class="image-dither rounded--large">
<div>Static content optimized for e-ink</div>
```

**Why:** E-ink displays don't support animations, transformations, or color. Use `image-dither` for grayscale optimization.

## 6. Unescaped Quotes in cURL

**BAD:**
```bash
curl "URL" -d '{"merge_variables": {"content": "<div class="layout">Text</div>"}}'
```

**GOOD:**
```bash
curl "URL" -d '{"merge_variables": {"content": "<div class=\"layout\">Text</div>"}}'
```

**Why:** Unescaped quotes break JSON syntax.

## 7. Oversized Payloads

**BAD:**
```json
{
  "merge_variables": {
    "content": "<div class=\"layout layout--col layout--center gap--large\">\n    <span class=\"value value--xxlarge text--center\">Very long title with lots of text that makes the payload huge</span>\n    <span class=\"description text--center\">And even more verbose description text that could be much more concise</span>\n    <!-- Hundreds more lines of verbose HTML -->\n  </div>"
}
```

**GOOD:**
```json
{"merge_variables":{"content":"<div class=\"layout layout--col layout--center gap--large\"><span class=\"value value--xxlarge text--center\">Concise Title</span><span class=\"description text--center\">Brief description</span></div>"}}
```

**Why:** 2KB payload limit. Remove whitespace, keep content concise.

## 8. Missing data-clamp on Long Text

**BAD:**
```html
<span class="description">This is a very long description that will wrap to many lines and potentially break the layout or overflow the available space on the e-ink display making it unreadable.</span>
```

**GOOD:**
```html
<span class="description" data-clamp="2">This is a very long description that will wrap to many lines and potentially break the layout or overflow the available space on the e-ink display making it unreadable.</span>
```

**Why:** E-ink displays have limited space. Use `data-clamp` to truncate with ellipsis.

## 9. Generic Semantic HTML Without Framework Classes

**BAD:**
```html
<div>
  <h1>Title</h1>
  <p>Description text</p>
  <ul>
    <li>Item 1</li>
    <li>Item 2</li>
  </ul>
</div>
```

**GOOD:**
```html
<div class="layout layout--col gap--medium">
  <span class="title">Title</span>
  <span class="description">Description text</span>
  <div class="layout layout--col gap--small">
    <div class="item">
      <span class="label">Item 1</span>
    </div>
    <div class="item">
      <span class="label">Item 2</span>
    </div>
  </div>
</div>
```

**Why:** Framework components are styled specifically for e-ink displays. Generic HTML tags may not render as expected.

## 10. Forgetting Image URLs Must Be Public

**BAD:**
```html
<img src="/local/path/image.png">
<img src="file:///Users/name/image.png">
<img src="relative/path/image.png">
```

**GOOD:**
```html
<img src="https://example.com/image.png" class="image-dither">
```

**Why:** TRMNL device needs publicly accessible URLs to fetch images. Local/relative paths won't work.

## 11. Not Using Layout Engines

**BAD:**
```html
<div>
  <div>Item 1</div>
  <div>Item 2</div>
  <!-- 50 more items that overflow the screen -->
</div>
```

**GOOD:**
```html
<div data-overflow="true"
     data-overflow-max-height="400"
     data-overflow-max-cols="3"
     data-overflow-counter="true">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <!-- Items automatically distributed across columns with overflow indicator -->
</div>
```

**Why:** Overflow engine handles space constraints gracefully with "and X more" indicators.

## 12. Mixing Framework Classes with Conflicting Inline Styles

**BAD:**
```html
<div class="layout layout--center" style="display: block; text-align: left;">
  Content
</div>
```

**GOOD:**
```html
<div class="layout layout--center">
  Content
</div>
```

OR

```html
<div style="display: flex; align-items: center; justify-content: center;">
  Content
</div>
```

**Why:** Choose framework classes OR inline styles, don't mix conflicting properties. Framework classes are preferred.


path: assets/good-examples/task-list.html
content: <div class="layout layout--col gap--large" style="padding: 32px;">
  <div class="title_bar">
    <span class="title">Today's Tasks</span>
    <span class="instance">5 items</span>
  </div>

  <div class="divider--on-white"></div>

  <div class="layout layout--col gap--medium">
    <div class="item">
      <div class="meta">
        <span class="index">1</span>
      </div>
      <div class="content">
        <span class="label">Review PR #42</span>
        <span class="description" data-clamp="1">Frontend authentication refactor</span>
      </div>
    </div>

    <div class="item">
      <div class="meta">
        <span class="index">2</span>
      </div>
      <div class="content">
        <span class="label">Update documentation</span>
        <span class="description" data-clamp="1">API endpoints changelog</span>
      </div>
    </div>

    <div class="item">
      <div class="meta">
        <span class="index">3</span>
      </div>
      <div class="content">
        <span class="label">Team standup</span>
        <span class="description" data-clamp="1">10:00 AM - Conference Room A</span>
      </div>
    </div>
  </div>
</div>


path: assets/good-examples/table-data.html
content: <div class="layout layout--col gap--medium" style="padding: 32px;">
  <span class="title">Server Status</span>

  <table class="table table--small" data-table-limit="true">
    <thead>
      <tr>
        <th><span class="title">Server</span></th>
        <th><span class="title">Status</span></th>
        <th><span class="title">Load</span></th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td><span class="label">api-prod-01</span></td>
        <td><span class="label">Online</span></td>
        <td><span class="label">45%</span></td>
      </tr>
      <tr>
        <td><span class="label">api-prod-02</span></td>
        <td><span class="label">Online</span></td>
        <td><span class="label">62%</span></td>
      </tr>
      <tr>
        <td><span class="label">db-primary</span></td>
        <td><span class="label">Online</span></td>
        <td><span class="label">38%</span></td>
      </tr>
      <tr>
        <td><span class="label">cache-redis</span></td>
        <td><span class="label">Online</span></td>
        <td><span class="label">21%</span></td>
      </tr>
    </tbody>
  </table>

  <span class="description text--center text--gray-40">All systems operational</span>
</div>


path: assets/good-examples/stats-dashboard.html
content: <div class="layout layout--col gap--large" style="padding: 32px;">
  <span class="title">Daily Stats</span>

  <div class="grid grid--cols-2 gap--large">
    <div class="item item--emphasis-1">
      <div class="content">
        <span class="value value--xlarge">1,247</span>
        <span class="label">Active Users</span>
      </div>
    </div>

    <div class="item item--emphasis-1">
      <div class="content">
        <span class="value value--xlarge">89%</span>
        <span class="label">Uptime</span>
      </div>
    </div>

    <div class="item item--emphasis-1">
      <div class="content">
        <span class="value value--xlarge">342</span>
        <span class="label">API Calls</span>
      </div>
    </div>

    <div class="item item--emphasis-1">
      <div class="content">
        <span class="value value--xlarge">$2,450</span>
        <span class="label">Revenue</span>
      </div>
    </div>
  </div>

  <div class="progress-bar progress-bar--large">
    <div class="content">
      <span class="label">Monthly Goal</span>
      <span class="value value--xxsmall">68%</span>
    </div>
    <div class="track">
      <div class="fill" style="width: 68%"></div>
    </div>
  </div>
</div>


path: assets/good-examples/two-column-image.html
content: <div class="grid grid--cols-2 gap--xlarge" style="padding: 32px; height: 100%;">
  <div class="col col--center">
    <div class="layout layout--col gap--medium">
      <span class="title">Weather Update</span>
      <span class="value value--xxxlarge">72°F</span>
      <span class="label label--large">Sunny and Clear</span>
      <div class="divider--on-white"></div>
      <span class="description">Perfect day for outdoor activities</span>
    </div>
  </div>
  <div class="col col--center row--end">
    <img src="https://example.com/weather-icon.png" class="image-dither rounded--large" style="max-width: 100%; max-height: 100%;" />
  </div>
</div>


path: assets/good-examples/simple-message.html
content: <div class="layout layout--col layout--center gap--large" style="height: 100%; padding: 40px;">
  <span class="value value--xxlarge text--center">Meeting in 5 Minutes</span>
  <span class="label label--large text--center text--gray-40">Conference Room B</span>
  <div class="divider" style="width: 60%;"></div>
  <span class="description text--center">Join the standup meeting</span>
</div>


path: references/css-utilities.md
content: # CSS Utilities Reference

## Color System

### Background Colors (16 Shades)
Uses dither patterns on 1-bit displays:
- `bg--black` - Pure black
- `bg--gray-10` through `bg--gray-75` (5-point increments)
- `bg--white` - Pure white

### Text Colors (17 Shades)
- `text--black`
- `text--gray-10` through `text--gray-75`
- `text--white`

**Note:** Dark mode inverts entire screen except images.

## Typography

### Title Element
- `.title` or `.title--base` - Default heading
- `.title--small` - Compact variant

Example: `<span class="title">Heading</span>`

### Value Element (8 Sizes)
- `value--xxsmall` - Smallest
- `value--xsmall` - Secondary info
- `value--small` - Body text
- `value` or `value--base` - Standard
- `value--large` - Emphasized
- `value--xlarge` - Section headers
- `value--xxlarge` - Major headings
- `value--xxxlarge` - Hero text

**Special:** `value--tnums` - Tabular numbers (consistent width)

Example: `<span class="value value--large">$1,234.56</span>`

### Value Formatting (data-value-format)

Auto-format numbers with locale support:
```html
<span class="value" data-value-format="true">2345678</span>
```

**Locales:**
- `en-US` → 123,456.78
- `de-DE` → 123.456,78
- `fr-FR` → 123 456,78

**Currencies:** `$` `€` `£` `¥` `₴` `₹` `₪` `₩` `₫` `₱` `₽` `₿`

### Fit Value (Auto-resize)

Automatically resize text to fit container:
```html
<span class="value value--xxxlarge" data-fit-value="true">12345</span>
```

For text content, specify max height:
```html
<span class="value" data-fit-value="true" data-fit-value-max-height="340">
  Long text here
</span>
```

Adjusts font size, weight, and line height to fit.

### Label Element
- Default - Standard size
- `label--small` - Compact
- `label--outline` - Outline style
- `label--underline` - Underline style
- `label--gray` - Gray variant
- `label--inverted` - Inverted style

Supports `data-clamp="N"` for line limiting.

### Description Element
- `.description` - Standardized descriptive text

### Text Alignment
- `text--left` (default)
- `text--center`
- `text--right`
- `text--justify`

## Sizing Utilities

### Fixed Sizes
Values: 0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 72, 80, 96

Pixel equivalents: 0px - 384px (multiply by 4)

**Width/Height**
- `w--{size}`, `h--{size}`
- Example: `w--48` = 192px width

### Arbitrary Sizes
Custom 0-800px (no responsive support):
- `w--[150px]`, `h--[225px]`

### Min/Max Dimensions
- `w--min-{size}`, `w--max-{size}`
- `h--min-{size}`, `h--max-{size}`
- Arbitrary: `w--min-[100px]`, `w--max-[400px]`

### Dynamic Sizing
- `w--full`, `h--full` - 100% width/height
- `w--auto`, `h--auto` - Automatic

## Spacing Utilities

### Margin Classes
- `m--{size}` - All sides
- `mt--{size}`, `mr--{size}`, `mb--{size}`, `ml--{size}` - Individual sides
- `mx--{size}`, `my--{size}` - Horizontal/vertical

### Padding Classes
- `p--{size}` - All sides
- `pt--{size}`, `pr--{size}`, `pb--{size}`, `pl--{size}` - Individual sides
- `px--{size}`, `py--{size}` - Horizontal/vertical

**Responsive:** All support `sm:`, `md:`, `lg:`, `portrait:` prefixes.

Example: `<div class="p--4 md:p--8 lg:p--12">`

## Gap Utilities

### Predefined Gaps
- `gap--none` - No spacing
- `gap--xsmall`, `gap--small`, `gap` or `gap--base`, `gap--medium`, `gap--large`, `gap--xlarge`, `gap--xxlarge`
- `gap--auto` - Distributes space evenly

### Arbitrary Gaps
- `gap--[10px]` - Custom 0-50px (no responsive support)

Works with `grid` and `flex` containers.

## Border Utilities

Uses grayscale dithering (1=black, 7=white, 2-6=dithered grays):
- `border--h-1` through `border--h-7` - Horizontal
- `border--v-1` through `border--v-7` - Vertical

**Note:** v2 borders NOT backward compatible with v1.

## Rounded Corners

### Base Sizes
- `rounded--none` (0px)
- `rounded--xsmall` (5px)
- `rounded--small` (7px)
- `rounded` or `rounded--base` (10px)
- `rounded--medium` (15px)
- `rounded--large` (20px)
- `rounded--xlarge` (25px)
- `rounded--xxlarge` (30px)
- `rounded--full` (9999px - pill shape)

### Arbitrary Values
- `rounded--[15px]` - Custom 0-50px (no responsive support)

### Corner-Specific
- `rounded-tl--{size}` (top-left)
- `rounded-tr--{size}` (top-right)
- `rounded-br--{size}` (bottom-right)
- `rounded-bl--{size}` (bottom-left)

### Side-Specific
- `rounded-t--{size}` (top corners)
- `rounded-r--{size}` (right corners)
- `rounded-b--{size}` (bottom corners)
- `rounded-l--{size}` (left corners)

## Visibility Utilities

### Display Classes
- `hidden` - display: none
- `visible` - display: block
- `block`, `inline`, `inline-block`
- `flex`, `inline-flex`
- `grid`
- `table`, `table-row`

**Responsive:** All support `sm:`, `md:`, `lg:` breakpoints.
**Bit-depth:** Support `1bit:`, `2bit:`, `4bit:` variants.

Example: `<div class="hidden md:block 1bit:hidden 4bit:flex">`

## Aspect Ratio (Beta)

- `aspect--auto` - No constraints
- `aspect--1/1` - Square
- `aspect--4/3`, `aspect--3/2`, `aspect--16/9`, `aspect--21/9`
- `aspect--3/4`, `aspect--2/3`, `aspect--9/16`, `aspect--9/21`

## Image Utilities

### Dithering
- `image-dither` - Grayscale illusion on 1-bit displays

### Object Fit
- `image--fill` - Stretch to fill
- `image--contain` - Maintain aspect, fit within
- `image--cover` - Maintain aspect, fill space (clips)

### Image Stroke
Widths: `image-stroke` (default 1.5px), `image-stroke--small` (1px), `image-stroke--base` (1.5px), `image-stroke--medium` (2px), `image-stroke--large` (2.5px), XL (3px)

Colors: White (default), `image-stroke--black`

## Text Stroke

**Limitation:** Works ONLY on pure black or white text.

### Width Modifiers
- `text-stroke` - Default 3.5px white
- `text-stroke--small` (2px)
- `text-stroke--base` (3.5px)
- `text-stroke--medium` (4.5px)
- `text-stroke--large` (6px)
- `text-stroke--xlarge` (7.5px)

### Colors
16 shades: black, gray-10 through gray-75, white.

## Outline Utility

- `outline` - Pixel-perfect rounded border
  - 1-bit: Dithered 9-slice PNG
  - 2-bit/4-bit: 1px solid border with 10px radius

- `screen--backdrop` - Alternative for patterned backgrounds

## Scale Utilities (Beta)

Applied to `screen` element via `--ui-scale` CSS variable:

- `screen--scale-xsmall` (0.75) - Maximum density
- `screen--scale-small` (0.875) - Increased density
- `screen--scale-regular` (1.0) - Default
- `screen--scale-large` (1.125) - Enhanced readability
- `screen--scale-xlarge` (1.25) - Large scale
- `screen--scale-xxlarge` (1.5) - Accessibility-focused

**Scales:** Font sizes, line heights, component dimensions, spacing.
**Doesn't scale:** Fixed pixel values, screen dimensions.


path: references/components.md
content: # Components Reference

## Screen Component

Base container for TRMNL displays.

**Base:**
```html
<div class="screen">
  <!-- Content -->
</div>
```

**Device Classes:**
- `screen--og` - TRMNL OG (800x480)
- `screen--v2` - TRMNL V2 (1040x780)
- `screen--amazon_kindle_2024` - Kindle 2024 (718x540)

**Bit Depth:**
- `screen--1bit` - Monochrome
- `screen--2bit` - 4 grayscale levels
- `screen--4bit` - 16 grayscale levels

**Modifiers:**
- `screen--portrait` - Portrait orientation (swaps dimensions)
- `screen--no-bleed` - Remove default padding
- `screen--dark-mode` - Invert colors (except images)
- `screen--backdrop` - Patterned/gray backgrounds

**Scale Variants (4-bit only):**
- `screen--scale-xsmall` (0.75)
- `screen--scale-small` (0.875)
- `screen--scale-regular` (1.0)
- `screen--scale-large` (1.125)
- `screen--scale-xlarge` (1.25)
- `screen--scale-xxlarge` (1.5)

**Example:**
```html
<div class="screen screen--og screen--2bit">
  <div class="layout">...</div>
</div>
```

## View Component

Container structure for plugin layouts.

**Sizes:**
- `view view--full` - Full-width view
- `view--half_horizontal` - Horizontal half
- `view--half_vertical` - Vertical half
- `view--quadrant` - Quarter-section

**Note:** In markup editor, do NOT manually wrap with view classes (system applies automatically).

## Layout Component

Semantic Flexbox container (see layout-systems.md for full details).

**Base Pattern:**
```html
<div class="layout layout--col layout--center gap--large">
  <span class="title">Title</span>
  <span class="description">Description</span>
</div>
```

## Title Bar Component

Standardized header with icon, title, and optional instance label.

**Structure:**
```html
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Title Text</span>
  <span class="instance">Optional Instance</span>
</div>
```

**Elements:**
- `image` - Icon/logo (left side)
- `title` - Main heading
- `instance` - Optional label (right side)

## Divider Component

Visual separator line.

**Horizontal (default):**
```html
<div class="divider"></div>
<div class="divider--h"></div>
```

**Vertical:**
```html
<div class="divider--v"></div>
```

**Background-Specific:**
- `divider--on-white` - White/very light backgrounds
- `divider--on-light` - Light gray (gray-50 to gray-65)
- `divider--on-dark` - Dark gray (gray-30 to gray-45)
- `divider--on-black` - Black/very dark backgrounds

**Feature:** Automatic background detection.

## Rich Text Component

Flexible container for formatted text.

**Structure:**
```html
<div class="richtext richtext--center">
  <div class="content">
    <p>Formatted text content here.</p>
  </div>
</div>
```

**Alignment:**
- `richtext--left` (default)
- `richtext--center`
- `richtext--right`

**Content Sizes:**
- `content--large` - Headings/emphasis
- `content` or `content--base` - Default body text
- `content--small` - Captions/secondary

**Enhanced Features:**
- `data-content-limiter="true"` - Auto-adjust text size when overflow
- `data-pixel-perfect="true"` - Crisp 1-bit rendering

**Example:**
```html
<div class="richtext richtext--center" data-content-limiter="true">
  <div class="content content--large">
    <h1>Heading</h1>
    <p>Body text that adapts to available space.</p>
  </div>
</div>
```

## Item Component

Container for structured content with optional metadata.

**Variant 1: With Meta**
```html
<div class="item">
  <div class="meta">
    <span class="label">Meta</span>
  </div>
  <div class="content">
    <span class="title">Item Title</span>
    <span class="description">Description text</span>
  </div>
</div>
```

**Variant 2: With Meta and Index**
```html
<div class="item">
  <div class="meta">
    <span class="index">1</span>
  </div>
  <div class="content">
    <span class="title">Task Name</span>
    <span class="label">Status</span>
  </div>
</div>
```

**Variant 3: With Emphasis**
```html
<div class="item item--emphasis-2">
  <div class="meta">
    <span class="label">Tag</span>
  </div>
  <div class="content">
    <span class="title">Highlighted Item</span>
  </div>
</div>
```

**Emphasis Levels:**
- `item--emphasis-1` - Light emphasis
- `item--emphasis-2` - Medium emphasis
- `item--emphasis-3` - Strong emphasis

**Variant 4: Simple (Content Only)**
```html
<div class="item">
  <span class="title">Simple Item</span>
  <span class="description">No meta section</span>
</div>
```

## Table Component

Structured data display with multiple size variants.

**Base Structure:**
```html
<table class="table">
  <thead>
    <tr>
      <th><span class="title">Header 1</span></th>
      <th><span class="title">Header 2</span></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><span class="label">Data 1</span></td>
      <td><span class="label">Data 2</span></td>
    </tr>
  </tbody>
</table>
```

**Size Variants:**
- `table` or `table--base` - Default size
- `table--large` - Increased row heights
- `table--small` - Compact rows
- `table--xsmall` - Most compact

**Additional Features:**
- `table--indexed` - Add index column
- `data-table-limit="true"` - Overflow with "and X more"
- `data-clamp="1"` - Single-line truncation in cells

**Cell Markup Requirements:**
- Headers must use: `<th><span class="title">...</span></th>`
- Data must use: `<td><span class="label">...</span></td>`

**Example with Features:**
```html
<table class="table table--small" data-table-limit="true">
  <thead>
    <tr>
      <th><span class="title">Name</span></th>
      <th><span class="title">Value</span></th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><span class="label" data-clamp="1">Long name that truncates</span></td>
      <td><span class="label">123</span></td>
    </tr>
  </tbody>
</table>
```

## Progress Bar Component

Visual progress indicator with label and percentage.

**Base Sizes:**
- `progress-bar` or `progress-bar--base` - Default
- `progress-bar--small` - Compact
- `progress-bar--large` - Prominent

**Emphasis Levels:**
- Default - Standard appearance
- `progress-bar--emphasis-2` - Medium emphasis
- `progress-bar--emphasis-3` - Strong emphasis

**Structure:**
```html
<div class="progress-bar progress-bar--large">
  <div class="content">
    <span class="label">Loading</span>
    <span class="value value--xxsmall">75%</span>
  </div>
  <div class="track">
    <div class="fill" style="width: 75%"></div>
  </div>
</div>
```

**Key Elements:**
- `content` - Label and percentage container
- `track` - Background bar
- `fill` - Filled portion (set width via inline style)

## Progress Dots Component

Step indicator with filled/current/empty states.

**Base Sizes:**
- `progress-dots` or `progress-dots--base` - Default
- `progress-dots--small` - Compact
- `progress-dots--large` - Prominent

**Structure:**
```html
<div class="progress-dots progress-dots--large">
  <div class="track">
    <div class="dot dot--filled"></div>
    <div class="dot dot--current"></div>
    <div class="dot"></div>
    <div class="dot"></div>
  </div>
</div>
```

**Dot States:**
- `dot--filled` - Completed step
- `dot--current` - Active step
- `dot` (no modifier) - Upcoming step

## Chart Component

Supports Line, Multi-Series Line, Bar, and Gauge charts.

**Requirements:**
- Highcharts v12.3.0+
- Chartkick v5.0.1+ (optional)
- Pattern-fill module for multi-series

**Critical Configuration (REQUIRED for e-ink):**
```javascript
var chart = new Chartkick.LineChart("chart-id", data, {
  animation: false,           // REQUIRED
  enableMouseTracking: false, // Disable interactions
  hover: { enabled: false }   // No hover states
});
```

**Data Formats:**
```javascript
// Single series
var data = [["2024-06-09", 975], ["2024-06-10", 840]];

// Multi-series
var data = { name: "Current", data: [["2024-06-09", 975]] };
```

**Container:**
```html
<div id="chart-id" class="w--full h--[200px]"></div>
```

**Gauge Charts:**
- Range: 0-100
- Arc angles: `startAngle: -150`, `endAngle: 150`
- Transparent backgrounds for pivot elements

**Library Detection:**
```javascript
if ("Chartkick" in window) {
  createChart();
} else {
  window.addEventListener("chartkick:load", createChart, true);
}
```

**E-ink Considerations:**
- Disable ALL animations
- Use pattern fills for multi-series (1-bit displays)
- Set explicit dimensions
- Use dithered backgrounds: `bg--gray-30`, `bg--gray-60`
- Configure axes for readability


path: references/layout-systems.md
content: # Layout Systems Reference

## Flexbox Utilities

### Direction
- `flex flex--row` - Horizontal layout (default)
- `flex flex--col` - Vertical layout
- `flex flex--row-reverse` - Reversed horizontal
- `flex flex--col-reverse` - Reversed vertical

### Horizontal Alignment (X-axis)
- `flex--left` - Align left
- `flex--center-x` - Center horizontally
- `flex--right` - Align right

### Vertical Alignment (Y-axis)
- `flex--top` - Align top
- `flex--center-y` - Center vertically
- `flex--bottom` - Align bottom

### Container Stretch
- `flex--stretch` - All children fill container
- `flex--stretch-x` - Horizontal stretch
- `flex--stretch-y` - Vertical stretch

### Item-Level Controls
- `stretch` - Stretch in cross-axis
- `stretch-x` - Horizontal stretch
- `stretch-y` - Vertical stretch
- `no-shrink` - Prevent shrinking
- `grow` - Grow to fill space
- `shrink-0` - Prevent shrinking
- `flex-none` / `flex-initial` - Flex behavior
- `basis--[value]` - Set flex basis
- `order--[value]` - Control visual order

### Wrapping
- `flex--wrap` - Items wrap to multiple lines
- `flex--nowrap` - Single line (default)
- `flex--wrap-reverse` - Reverse wrap direction

### Main Axis Distribution
- `flex--between` - Space between items
- `flex--around` - Space around items
- `flex--evenly` - Even spacing

### Multi-Line Alignment
- `flex--content-start` - Pack to start
- `flex--content-center` - Pack to center
- `flex--content-end` - Pack to end
- `flex--content-between` - Distribute with space between
- `flex--content-around` - Distribute with space around
- `flex--content-evenly` - Distribute evenly
- `flex--content-stretch` - Stretch to fill

### Item Alignment
- `self--start` - Align item to start
- `self--center` - Center item
- `self--end` - Align item to end
- `self--stretch` - Stretch item

### Example
```html
<div class="flex flex--col flex--center-x gap--large">
  <span class="value value--xlarge">Title</span>
  <span class="description">Subtitle</span>
</div>
```

## Grid Utilities

### Column Count
- `grid--cols-{number}` - Equal-width columns
- Example: `grid--cols-2`, `grid--cols-3`, `grid--cols-4`

### Column Span
- `col--span-{number}` - Individual column width
- Example: `col--span-2` spans 2 columns

### Layout Base
- `grid` - Grid container
- `col` - Vertical layout
- `row` - Horizontal layout

### Column Positioning
- `col--start` - Align to top
- `col--center` - Center vertically
- `col--end` - Align to bottom

### Row Positioning
- `row--start` - Align to left
- `row--center` - Center horizontally
- `row--end` - Align to right

### Responsive Wrapping
- `grid--wrap` - Enable responsive wrapping
- `grid--min-{size}` - Minimum track size
  - Example: `grid--min-32`, `grid--min-56`

### Example
```html
<div class="grid grid--cols-2 gap--large">
  <div class="col col--start">
    <span class="title">Left Column</span>
  </div>
  <div class="col col--center">
    <span class="value">Right Column</span>
  </div>
</div>
```

## Columns Component

Simple balanced column layouts:

```html
<div class="columns">
  <div class="column">Content 1</div>
  <div class="column">Content 2</div>
</div>
```

Automatically distributes space evenly across columns.

## Mashup Layouts

Multi-plugin dashboard configurations using CSS Grid.

**Container:** `mashup mashup--{layout}`

| Layout | Description |
|--------|-------------|
| `mashup--1Lx1R` | 2 equal vertical columns (left/right) |
| `mashup--1Tx1B` | 2 equal horizontal rows (top/bottom) |
| `mashup--1Lx2R` | 1 full-height left + 2 stacked right |
| `mashup--2Lx1R` | 2 stacked left + 1 full-height right |
| `mashup--2Tx1B` | 2 side-by-side top + 1 full-width bottom |
| `mashup--1Tx2B` | 1 full-width top + 2 side-by-side bottom |
| `mashup--2x2` | 2x2 grid (4 equal quadrants) |

**View sizes:**
- `view--half_vertical` - 50% vertical
- `view--half_horizontal` - 50% horizontal
- `view--quadrant` - 25% (quarter)

**Example:**
```html
<div class="mashup mashup--1Lx2R">
  <div class="view view--half_vertical">
    <div class="layout">Left content</div>
  </div>
  <div class="view view--quadrant">
    <div class="layout">Top right</div>
  </div>
  <div class="view view--quadrant">
    <div class="layout">Bottom right</div>
  </div>
</div>
```

**Rules:**
- View count must match layout configuration
- All views must contain a `.layout` div

## Layout Component

Flexible container using Flexbox with semantic naming:

### Base Structure
```html
<div class="layout layout--row">
  {{ Content }}
</div>
```

### Direction
- `layout--row` - Horizontal arrangement (default)
- `layout--col` - Vertical arrangement

### Alignment
- `layout--left` - Align left
- `layout--center-x` - Center horizontally
- `layout--right` - Align right
- `layout--top` - Align top
- `layout--center-y` - Center vertically
- `layout--bottom` - Align bottom
- `layout--center` - Center both axes

### Stretch
- `layout--stretch` - Stretch both axes
- `layout--stretch-x` - Horizontal stretch
- `layout--stretch-y` - Vertical stretch

### Child Utilities
- `stretch-x` - Child stretches horizontally
- `stretch-y` - Child stretches vertically

### Example
```html
<div class="layout layout--col layout--center gap--xlarge" style="height: 100%;">
  <span class="value value--xxlarge">Centered Content</span>
  <span class="label">Vertically and horizontally centered</span>
</div>
```

## Dynamic Engines

### Overflow Engine

Automatically distributes items across columns while respecting height constraints.

**Attributes:**
- `data-overflow="true"` - Activate engine
- `data-overflow-max-height="400"` - Pixel height budget
- `data-overflow-counter="true"` - Show "and X more" label
- `data-overflow-max-cols="3"` - Maximum columns
- `data-overflow-cols="2"` - Force exact column count

**Features:**
- Smart column distribution
- Group header handling (never orphaned)
- Works with Clamp engine

**Example:**
```html
<div data-overflow="true"
     data-overflow-max-height="400"
     data-overflow-max-cols="3"
     data-overflow-counter="true">
  <div class="item">Item 1</div>
  <div class="item">Item 2</div>
  <!-- More items... -->
</div>
```

### Clamp Engine

Limits text to specified lines with word-based ellipsis.

**Usage:**
- `data-clamp="N"` - Where N = line count

**Example:**
```html
<span class="description" data-clamp="2">
  Long text that will be truncated to 2 lines with ellipsis...
</span>
```

**Legacy Support:**
- `clamp--none` - Disable clamping
- `clamp--1` through `clamp--50` - Line limits

### Content Limiter

Automatically restricts content height, applies smaller typography and truncation when exceeded.

**Attributes:**
- `data-content-limiter="true"` - Enable limiting
- `data-content-max-height="[pixels]"` - Custom max height

**Behavior:**
1. Adds `content--small` class to reduce typography
2. Truncates first overflowing block using Clamp
3. Hides subsequent blocks

**Example:**
```html
<div class="richtext" data-content-limiter="true" data-content-max-height="300">
  <div class="content">
    <!-- Content that might overflow -->
  </div>
</div>
```

### Table Overflow

Limits table rows with "and X more" indicator.

**Usage:**
- `data-table-limit="true"` - Activate height constraints

**Example:**
```html
<table class="table" data-table-limit="true">
  <thead>
    <tr><th><span class="title">Header</span></th></tr>
  </thead>
  <tbody>
    <!-- Rows will be limited with overflow indicator -->
  </tbody>
</table>
```

## Responsive Patterns

### Mobile-First Approach
Styles cascade upward:
```html
<div class="flex flex--col md:flex--row lg:gap--xlarge">
  <!-- Vertical on small, horizontal on medium+ -->
</div>
```

### Orientation-Specific
```html
<div class="gap--small portrait:gap--large">
  <!-- Different gap for portrait orientation -->
</div>
```

### Bit-Depth Variants
```html
<div class="hidden 4bit:grid">
  <!-- Only show on 4-bit displays -->
</div>
```

### Combined Modifiers
```html
<div class="p--4 md:p--8 lg:p--12 portrait:p--6">
  <!-- Responsive padding with orientation variant -->
</div>
```


path: references/patterns.md
content: # TRMNL Design Patterns

Common patterns from official TRMNL examples. Use these as templates for building plugins.

## Table of Contents
- [Stats Dashboard](#stats-dashboard)
- [List View](#list-view)
- [Centered Rich Text](#centered-rich-text)
- [Big Numbers with Visual](#big-numbers-with-visual)
- [Visual Heatmap/Calendar](#visual-heatmapcalendar)
- [Todo List Columns](#todo-list-columns)
- [Lunar/Phase Display](#lunarphase-display)
- [Two-Column Custom Content](#two-column-custom-content)

---

## Stats Dashboard
*Source: Shopify, Stock Price examples*

Best for: Financial data, KPIs, metrics dashboards.

```html
<div class="layout layout--col gap--space-between">
  <div class="grid">
    <div class="col--span-3">
      <div class="grid grid--cols-1">
        <div class="item">
          <div class="meta"></div>
          <div class="content">
            <span class="value" data-fit-value="true">AAPL</span>
            <span class="label">Apple Inc.</span>
          </div>
        </div>
        <div class="item">
          <div class="meta"></div>
          <div class="content">
            <span class="value value--small value--tnums">+0.62%</span>
            <span class="label">Daily Change</span>
          </div>
        </div>
      </div>
    </div>
    <div class="item col--span-7">
      <div class="meta"></div>
      <div class="content">
        <span class="value value--xxlarge value--tnums" data-fit-value="true">$226.40</span>
      </div>
    </div>
  </div>
  <div class="divider"></div>
  <!-- Second row of stats (same structure) -->
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Stock Prices</span>
  <span class="instance">Portfolio</span>
</div>
```

**Key features:**
- 3:7 column split (sidebar + main value)
- Nested `grid--cols-1` for stacked items in sidebar
- `value--tnums` for all numbers
- `data-fit-value="true"` for large values
- `divider` between rows

---

## List View
*Source: Reddit, Todo examples*

Best for: News feeds, post lists, notifications.

```html
<div class="layout">
  <div class="columns">
    <div class="column" data-overflow="true" data-overflow-counter="true">
      <span class="label label--medium group-header">Section Header</span>
      <div class="item">
        <div class="meta">
          <span class="index">1</span>
        </div>
        <div class="content">
          <span class="title title--small">Item title here</span>
          <div class="flex gap--xsmall">
            <span class="label label--small label--underline">metadata</span>
            <span class="label label--small label--underline">more info</span>
          </div>
        </div>
      </div>
      <!-- More items -->
    </div>
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Plugin Name</span>
  <span class="instance">Context</span>
</div>
```

**Key features:**
- `columns` + `column` for multi-column lists
- `data-overflow="true"` handles variable content length
- `data-overflow-counter="true"` shows "and X more"
- `group-header` for section titles
- `index` for numbered items
- `title--small` for compact titles
- `label--underline` for clickable-looking metadata

---

## Centered Rich Text
*Source: Wikipedia example*

Best for: Articles, quotes, single-focus content.

```html
<div class="layout">
  <div class="columns">
    <div class="column">
      <div class="richtext richtext--center gap--large">
        <span class="title" data-pixel-perfect="true">Article Title</span>
        <div class="content" data-content-limiter="true" data-pixel-perfect="true">
          <p>Article body text goes here. The content limiter will auto-adjust text size if it overflows.</p>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Wikipedia</span>
  <span class="instance">Random Article</span>
</div>
```

**Key features:**
- `richtext richtext--center` for centered text block
- `data-content-limiter="true"` auto-scales text to fit
- `data-pixel-perfect="true"` for crisp rendering
- `gap--large` between title and content

---

## Big Numbers with Visual
*Source: Days Left, GitHub examples*

Best for: Countdowns, progress trackers, analytics.

```html
<div class="layout layout--col gap--large">
  <div class="grid grid--cols-10">
    <div class="col col--span-1"></div>
    <div class="col col--span-4">
      <div class="item">
        <div class="meta"></div>
        <div class="content text--center">
          <span class="value value--xxxlarge" data-fit-value="true">247</span>
          <span class="label">Days Passed</span>
        </div>
      </div>
    </div>
    <div class="col col--span-4">
      <div class="item">
        <div class="meta"></div>
        <div class="content text--center">
          <span class="value value--xxxlarge" data-fit-value="true">118</span>
          <span class="label">Days Left</span>
        </div>
      </div>
    </div>
    <div class="col col--span-1"></div>
  </div>
  <div class="divider"></div>
  <div class="w--full">
    <!-- Visual element (chart, graph, heatmap) -->
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Days Left</span>
  <span class="instance">2026</span>
</div>
```

**Key features:**
- `grid--cols-10` with empty span-1 columns for centering
- `value--xxxlarge` for hero numbers
- `text--center` for centered content
- `w--full` for full-width visual below
- `divider` separates stats from visual

---

## Visual Heatmap/Calendar
*Source: GitHub Commits example*

Best for: Activity graphs, calendars, contribution charts.

```html
<style>
#heatmap {
  width: 755px;
  height: 182px;
  overflow: hidden;
  column-count: auto;
  column-fill: auto;
  column-width: 14px;
  column-gap: 0px;
}
#heatmap .day {
  width: 11px;
  height: 23px;
  float: left;
  border-radius: 4px;
  margin: 0 0 3px 0;
  break-inside: avoid-column;
}
</style>
<div id="heatmap">
  <span class="day bg--gray-10"></span>
  <span class="day bg--gray-30"></span>
  <span class="day bg--gray-60"></span>
  <span class="day bg--black"></span>
  <!-- 365 day elements total -->
</div>
```

**Key features:**
- CSS multi-column layout for efficient grid
- `column-width: 14px` controls column sizing
- `break-inside: avoid-column` prevents cell splitting
- Dithered backgrounds: `bg--gray-10`, `bg--gray-30`, `bg--gray-60`, `bg--black`
- Small cells with `border-radius: 4px` for visual appeal

---

## Todo List Columns
*Source: Todo List example*

Best for: Kanban boards, status columns, categorized lists.

```html
<div class="layout">
  <div class="columns">
    <div class="column" data-overflow="true" data-overflow-counter="true">
      <span class="label label--medium group-header label--gray">Done</span>
      <div class="item">
        <div class="meta"><span class="index">1</span></div>
        <div class="content">
          <span class="title title--small label--gray">Completed task</span>
        </div>
      </div>
    </div>
    <div class="column" data-overflow="true" data-overflow-counter="true">
      <span class="label label--medium group-header">Doing</span>
      <div class="item">
        <div class="meta"><span class="index">1</span></div>
        <div class="content">
          <span class="title title--small">Active task</span>
        </div>
      </div>
    </div>
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Todo List</span>
  <span class="instance">Work Tasks</span>
</div>
```

**Key features:**
- `label--gray` for completed/inactive items
- Different styling for done vs active columns
- `data-overflow-counter` shows overflow count per column

---

## Lunar/Phase Display
*Source: Lunar Calendar example*

Best for: Status displays, phase indicators, icon-centric content.

```html
<div class="layout layout--col gap--space-between">
  <div class="grid">
    <div class="item col--span-4">
      <div class="meta"></div>
      <div class="content">
        <span class="value">Full Moon</span>
        <span class="label">Current Phase</span>
      </div>
    </div>
    <div class="item col--span-3">
      <span class="value value--xsmall">99.8%</span>
      <span class="label">Illumination</span>
    </div>
    <div class="item col--span-3">
      <span class="value value--xsmall">15.0</span>
      <span class="label">Lunar Age</span>
    </div>
  </div>
  <div class="divider"></div>
  <div class="grid grid--row grid--center">
    <img class="w--[90px] h--[90px]" src="moon-phase.svg">
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Lunar Calendar</span>
  <span class="instance">Moon Phases</span>
</div>
```

**Key features:**
- 4:3:3 column split for varied importance
- `value--xsmall` for secondary metrics
- `w--[90px] h--[90px]` for fixed icon sizing
- `grid--center` for centered image

---

## Two-Column Custom Content
*Best for: Horoscopes, paired content, side-by-side comparisons*

```html
<div class="grid grid--cols-2 gap--xlarge" style="padding: 32px; height: 100%; font-family: Georgia, serif;">
  <div class="col layout layout--col layout--start gap--medium">
    <span style="font-size: 42px;">Heading</span>
    <span style="font-size: 24px;">Body text here. Keep it readable with appropriate font sizing based on content length.</span>
  </div>
  <div class="col layout layout--col layout--start gap--medium">
    <span style="font-size: 42px;">Heading</span>
    <span style="font-size: 24px;">Body text here. Content lengths may vary between columns.</span>
  </div>
</div>
<div class="title_bar">
  <img class="image" src="icon.svg">
  <span class="title">Plugin Name</span>
  <span class="instance">Context</span>
</div>
```

**Key features:**
- `layout--start` for top alignment (important when content varies)
- `gap--xlarge` between columns
- `gap--medium` within columns
- Georgia serif font for readability
- Content-aware font sizing (bigger for shorter text)


path: references/webhook-api.md
content: # Webhook API Reference

## Webhook Basics

**Method:** POST
**Content-Type:** application/json
**Rate Limit:** 12 requests/hour (free), 360/hour (TRMNL+)
**Max Payload:** 2KB (free), 5KB (TRMNL+)

## Payload Formats

### Object-Based (Most Common)

```json
{
  "merge_variables": {
    "content": "<div class='layout'>HTML here</div>",
    "title": "Optional title",
    "image": "https://example.com/image.png"
  }
}
```

### Array-Based (Content Field)

```json
{
  "merge_variables": [
    {"content": "<div>HTML content</div>"}
  ]
}
```

### With Merge Strategy

```json
{
  "merge_variables": {"sensor": {"temperature": 42}},
  "merge_strategy": "deep_merge"
}
```

### With Stream Strategy

```json
{
  "merge_variables": {"temperatures": [40, 42]},
  "merge_strategy": "stream",
  "stream_limit": 10
}
```

## cURL Example

```bash
curl "https://trmnl.com/api/custom_plugins/{uuid}" \
  -H "Content-Type: application/json" \
  -d '{"merge_variables": {"content": "<div class=\"layout\">HTML</div>"}}' \
  -X POST
```

**Note:** Escape quotes in JSON strings when using cURL.

## Merge Strategies

### Default
Replaces entire variable state:
```json
{"merge_variables": {"content": "New content"}}
```
Previous content is overwritten.

### Deep Merge
Recursively merges dictionary objects by key:
```json
{
  "merge_variables": {"sensor": {"temperature": 42}},
  "merge_strategy": "deep_merge"
}
```
Existing keys in `sensor` persist, only `temperature` updates.

### Stream
Appends to top-level arrays:
```json
{
  "merge_variables": {"items": ["new item"]},
  "merge_strategy": "stream",
  "stream_limit": 10
}
```
New items prepend to array, limited to `stream_limit`.

## Response Format

### Success Response
```json
{
  "message": null,
  "merge_variables": {
    "content": "<div>HTML content</div>"
  }
}
```

### Error Response
```json
{
  "message": "Error description",
  "merge_variables": null
}
```

## Global Variables (Liquid Templates)

Available in TRMNL Liquid templates (not in webhook payload):

### User Information
- `{{ trmnl.user.first_name }}` - User's first name
- `{{ trmnl.user.last_name }}` - User's last name
- `{{ trmnl.user.email }}` - User's email

### Plugin Settings
- `{{ trmnl.plugin_settings.custom_fields_values }}` - Custom field values

### Device Information
- `{{ model.bit_depth }}` - Display bit depth (1, 2, or 4)
- `{{ model.name }}` - Device model name

## HTML in Merge Variables

### Supported
Full HTML markup is supported in `merge_variables`:

```json
{
  "merge_variables": {
    "content": "<div class=\"layout layout--col gap--large\"><span class=\"value value--xlarge\">Title</span><span class=\"description\">Body text</span></div>"
  }
}
```

### Processing
- HTML is NOT auto-escaped by default
- Use TRMNL framework CSS classes for styling
- Limited tag support in form descriptions: `<br>`, `<strong>`, `<b>`, `<a>`
- Allowed attributes: `class`, `href`, `target="_blank"`

### Size Optimization

When approaching payload limits:

**Remove whitespace:**
```json
{"merge_variables":{"content":"<div class=\"layout\">Content</div>"}}
```

**Use zlib compression:**
```python
import zlib, base64, json
compressed = base64.b64encode(zlib.compress(json.dumps(data).encode())).decode()
payload = {"compressed_data": compressed}
```

**Note:** Decompression requires JavaScript with Pako library (Liquid can't decompress).

## Liquid Filters

Available in TRMNL templates (50+ filters):

### TRMNL-Specific
- `markdown_to_html` - Convert Markdown to HTML
- `number_with_delimiter` - Format numbers (1,234.56)
- `json` - Convert to JSON string
- `parse_json` - Parse JSON string

### Standard Liquid
- `upcase`, `downcase`, `capitalize`
- `strip`, `lstrip`, `rstrip`
- `truncate`, `truncatewords`
- `date` - Format dates
- `default` - Provide fallback value
- `split`, `join`
- `first`, `last`
- `map`, `where`, `sort`

**Example:**
```liquid
{{ text | markdown_to_html }}
{{ price | number_with_delimiter }}
{{ name | upcase | truncate: 20 }}
```

## Best Practices

### Payload Size
- Keep under 2KB for compatibility
- Minify HTML (remove whitespace)
- Avoid inline styles when framework classes work
- Compress large payloads if needed

### Rate Limiting
- Batch updates when possible
- Use merge strategies to avoid full replacements
- Cache generated content to minimize requests

### HTML Content
- Use TRMNL framework classes over inline styles
- Test on actual device (e-ink renders differently)
- Keep markup semantic and simple
- Avoid deeply nested structures

### Images
- Use fully qualified public URLs
- Optimize for e-ink (dithering, contrast)
- Consider payload size impact
- Use `image-dither` class for grayscale illusion

## Error Handling

Common errors:

**Rate Limit Exceeded:**
- Wait before retrying
- Reduce request frequency
- Upgrade to TRMNL+ for higher limits

**Payload Too Large:**
- Remove whitespace
- Compress payload
- Simplify HTML structure
- Use shorter class names

**Invalid JSON:**
- Validate JSON syntax
- Escape special characters
- Use proper encoding

**Webhook Not Found:**
- Verify UUID is correct
- Check webhook is active
- Confirm user has access


path: references/framework-overview.md
content: # TRMNL Framework Overview

## What is TRMNL?

TRMNL (pronounced "terminal") is an open-source e-ink adaptive front-end framework for e-paper displays. Automatically adapts across 1-bit (2 shades), 2-bit (4 shades), and 4-bit (16 shades) color depths with graceful degradation.

## Device Specifications

### TRMNL OG (Original 7.5")
- **Display:** 7.5" e-ink
- **Resolution:** 800x480px (landscape default)
- **Bit depth:** 2-bit (4 grayscale levels)
- **Battery:** 1800-2500mAh (2-6 months life)
- **Weight:** 170g
- **Screen class:** `screen--og`

### TRMNL V2
- **Resolution:** 1040x780px
- **Bit depth:** 4-bit (16 grayscale levels)
- **Screen class:** `screen--v2`

### TRMNL X (10.3")
- **Display:** 10.3" high-density e-ink
- **Resolution:** 1872x1404px
- **Bit depth:** 4-bit (16 grayscale levels)
- **Refresh:** ≤1.2s full, ≤200ms partial
- **Battery:** 6000-12000mAh (2-6 months)
- **Features:** Waterproof, accelerometer, gesture controls

### Other Supported Devices
- Amazon Kindle 2024 (718x540, 4-bit) - `screen--amazon_kindle_2024`
- Kindle Paperwhite, Oasis, Scribe variants
- Kobo devices
- Inkplate displays
- Waveshare panels
- Boox devices
- M5PaperS3

## Bit Depth Support

**1-bit (Monochrome)**
- 2 shades (black/white only)
- Uses dithering patterns for grayscale illusion
- Variant prefix: `1bit:`

**2-bit (Grayscale)**
- 4 levels of gray
- TRMNL OG default
- Variant prefix: `2bit:`

**4-bit (High-fidelity)**
- 16 levels of grayscale
- TRMNL X, Kindle 2024, some Kobo devices
- Variant prefix: `4bit:`

## E-ink Constraints

**Refresh Characteristics**
- Slower than LCD/OLED
- Full refresh prevents ghosting
- Partial refresh faster but can ghost
- Recommendation: Full refresh every 5 partial refreshes

**Ghosting Management**
- Traces of previous images linger without full refresh
- Store with white pattern when not in use
- Different refresh modes balance speed vs quality

**Performance Considerations**
- Low power design prioritizes battery over speed
- Device wakes periodically (configurable minutes)
- Server generates 1-bit or 2-bit PNG images
- Device renders then sleeps

## Responsive Breakpoints

Mobile-first approach, styles cascade upward:

| Prefix | Min Width | Example Device |
|--------|-----------|----------------|
| `sm:` | 600px | Kindle 2024 |
| `md:` | 800px | TRMNL OG |
| `lg:` | 1024px | TRMNL V2 |

**Orientation**
- `portrait:` prefix for portrait-specific styles
- Landscape is default
- `screen--portrait` swaps width/height (480x800)

**Combined Pattern**
```
size:orientation:bit-depth:utility
```
Example: `md:portrait:4bit:gap--large`

## Best Practices

**For E-ink Displays**
- Use dithering for images (`image-dither` class)
- Avoid animations (disable in charts)
- Keep content concise
- Use clear typography with good contrast
- Prefer black/white, limit mid-grays

**Optimization**
- Framework calculates space dynamically
- Avoid hard-coded pixel values where possible
- Use utility classes over inline styles
- Leverage Overflow/Clamp engines for adaptive layouts

**Size Limits**
- JSON payload: 2KB (free), 5KB (TRMNL+)
- Rate limit: 12 requests/hour (free), 360/hour (TRMNL+)

## CSS Variables

Available for custom styling:
- `--screen-w`, `--screen-h` - Screen dimensions
- `--full-w`, `--full-h` - Dimensions minus padding
- `--ui-scale` - UI scaling factor (0.75-1.5)
- `--color-depth` - Display bit depth
- `--gap` - Base gap value

## Framework Runtime

The runtime is a layout engine that automatically handles space constraints. Execution pipeline (sequential):

| Step | Engine | Purpose |
|------|--------|---------|
| 1 | Clamp | Truncate text to specified lines with word-based ellipsis |
| 2 | Overflow | Plan 1-N column layouts with "and X more" labels |
| 3 | Value Formatting | Abbreviate numbers to fit (k, M, B) |
| 4 | Fit Value | Adjust font size, line-height, weight for numbers |
| 5 | Grid Gaps | Tweak CSS gaps for integer pixel widths |
| 6 | Column Gaps | Normalize spacing between columns |
| 7 | Pixel-Perfect | Wrap lines in spans for crisp rendering |
| 8 | Content Limiter | Constrain height by view type |
| 9 | Index Widths | Ensure item indices render at even widths |

**Key behaviors:**
- Per-column re-clamping after layout planning
- Integer pixel alignment prevents visual artifacts
- Off-screen staging tests column arrangements
- Group headers duplicate across columns (never orphaned)

## Troubleshooting

| Problem | Solution |
|---------|----------|
| Multiple layouts error | Keep only one `.layout` element per view |
| Nested content fails | Use `.rich-text` instead of label/description |
| Text overflowing | Apply `data-content-limiter` or `data-clamp` |
| Numbers misaligned | Add `value--tnums` |
| Columns misaligned | Use `layout--start` not `layout--center` |


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (MEDIUM)