OpenCode Canva Design Skill¶
Local, account-free "Canva" inside OpenCode. Turns a plain-language request into a finished PNG/WebP by rendering HTML/CSS templates via the cached Chromium (Playwright). No Canva account, no API.
Status: Built 2026-07-11, interactive editor added 2026-07-21.
Location¶
~/.config/opencode/skills/canva-design/
canva-design/
├── SKILL.md # skill definition + workflow + dimensions catalog
├── templates/ # 9 starter HTML/CSS templates (edit inline CSS vars + copy)
│ ├── social-post.html 1080×1080
│ ├── story.html 1080×1920
│ ├── linkedin-banner.html 1584×396
│ ├── youtube-thumbnail.html 1280×720
│ ├── banner.html 1200×628
│ ├── flyer.html 1080×1350
│ ├── quote-card.html 1080×1080
│ ├── announcement.html 1080×1080
│ └── logo.html 600×600
├── editor/ # Interactive editor (port 8790)
│ ├── editor-server.js # HTTP server: UI + templates + /api/export + /api/chat
│ ├── editor.html # Sidebar + iframe stage + Chat with Hal panel
│ ├── editor.js # Contenteditable, drag/resize, CSS-var binding, chat logic
│ └── README.md # Usage docs
└── lib/
├── render.js # HTML → PNG → WebP via Chromium + ImageMagick
├── serve.js # serve + open in browser (preview)
└── brand.json # default brand kit (colors/fonts/logo)
Interactive Editor¶
A browser-based Canva-style editor running on http://localhost:8790/.
Features¶
- Template picker — 9 templates with correct dimensions (1080×1080, 1080×1920, etc.)
- Live CSS-var editing — background, primary, accent, text, muted colors + display/body fonts
- Drag & resize — blue handles on text elements; images are draggable and resizable
- Image upload — "+ Add Image" button or drag-and-drop files onto the canvas
- Chat with Hal — in-page AI chat panel that can edit the canvas (HTML responses applied live)
- Export — renders to temp PNG via Playwright, converts to WebP via ImageMagick (
convert -quality 90)
Running the Editor¶
cd ~/.config/opencode/skills/canva-design/editor
node editor-server.js
# → http://localhost:8790/
Or via detached process:
setsid nohup node editor-server.js > /tmp/canva-editor.log 2>&1 &
Autostarted via Hyprland config (~/.config/hypr/modules/autostart.lua).
Architecture¶
editor-server.js— Express-like HTTP server (port 8790):GET /— serves editor.htmlGET /api/templates— lists templates with dimensionsGET /template/:file— serves template HTMLPOST /api/export— renders HTML to WebP (Playwright → temp PNG → ImageMagick convert)POST /api/chat— proxies to OpenCode Zen API viacurl(Node fetch stalls)editor.html— sidebar (template/color/font controls) + iframe stage + chat paneleditor.js— contenteditable setup, drag/resize handles, CSS-var binding, chat send/receive
Chat with Hal¶
The chat panel talks to the same AI as this OpenCode session via the Zen API.
Important findings (2026-07-21):
- hy3-free model requires account balance despite being labeled "free" in docs
- big-pickle is actually free (routes to MiMo V2.5 via Xiaomi)
- Node's fetch() stalls against opencode.ai/zen/v1 — switched to curl spawn
- Error responses now properly surfaced (both OpenAI-style {error:{message:...}} and opencode-style {code:..., message:...})
Chat system prompt instructs the model to return <!DOCTYPE html> documents for canvas edits, or plain text for questions.
Image Upload¶
- Button: "+ Add Image" in sidebar opens file picker
- Drag-and-drop: drop any image file onto the stage area
- Images injected as
<img>elements with base64 data URLs (embeds in export) - Resizable via corner handle (scales proportionally)
- Draggable via blue move handle
CLI Usage (without editor)¶
- Describe the design (e.g. "make an Instagram post about our sale, orange + black").
- OpenCode copies a template to a working dir, edits copy + CSS variables (
--primary,--bg, fonts). - Render:
Default
node ~/.config/opencode/skills/canva-design/lib/render.js \ ./designs/<name>.html ./designs/<name>.png <W> <H> [scale]scale= 2 → output is 2× for crispness (1080×1080 → 2160×2160). Use1for exact 1:1 px. - Preview in browser (agent can't see images):
Serves
node ~/.config/opencode/skills/canva-design/lib/serve.js ./designs/<name>.png # or preview the live HTML for iteration: node ~/.config/opencode/skills/canva-design/lib/serve.js ./designs/<name>.htmlhttp://localhost:8787/...and opens the default browser. Remote:ssh -L 8787:localhost:8787. - Post-process with ImageMagick if needed:
magick in.png -quality 85 out.jpg.
Requirements / setup notes¶
playwrightis installed inlib/(browsers cached in~/.cache/ms-playwright).DISPLAY=:1is available on this machine, soxdg-openopens a real window.- 1300+ fonts installed (Noto families) — reference by name in
font-family. - ImageMagick 7.1 on system (used for PNG→WebP conversion in export).
- Node v26.4.0, npx 12.0.1.
- OpenCode Zen API key from
~/.config/opencode/opencode.json.
Export Formats¶
- Default: WebP (quality 90) — smaller file size, modern format
- Temp file: PNG (rendered by Playwright), converted by ImageMagick
- Content-type:
image/webp
Why local (not the Canva MCP)¶
The official Canva MCP (https://mcp.canva.com/mcp, OAuth2 via mcp-remote) gives true Canva but is cloud-bound and needs a Canva account. The local skill was chosen for privacy, offline use, and zero account. Switch paths later if real Canva integration is wanted.
Example¶
LEMO EGG.2B.302.CLL Instagram post demo: /home/dini/designs/lemo/instagram-post.{html,png} — used the store's original EGG_CLL.webp product image as the hero.