diff --git a/docs/templates/notes-app.md b/docs/templates/notes-app.md
new file mode 100644
index 0000000..385daa5
--- /dev/null
+++ b/docs/templates/notes-app.md
@@ -0,0 +1,50 @@
+# Notes App
+
+## Preview
+
+
+
+## Dimensions
+
+- **Width:** 1080px
+- **Height:** 1350px
+- **Aspect Ratio:** 1080:1350
+
+## Fonts
+
+- Inter
+
+## Slide Fields
+
+| Field | Type | Required | Max Length |
+|-------|------|----------|------------|
+| `title` | text | — | 40 |
+| `body` | text | ✅ | 400 |
+| `note_bg` | color | — | — |
+| `title_color` | color | — | — |
+| `body_color` | color | — | — |
+| `note_timestamp` | text | — | 30 |
+
+`body` supports inline markup: `*bold*`, `_italic_`, `*color:#hex*...*color*`.
+`note_bg` defaults to the iOS notes yellow (`#fef9c3`).
+
+## Brand Fields
+
+| Field | Type | Required | Default |
+|-------|------|----------|---------|
+| `brand_name` | text | ✅ | — |
+| `brand_handle` | text | ✅ | — |
+| `bg_color` | bg | — | #1e1e2e |
+| `bg_color_end` | bg | — | #11111b |
+| `accent_color` | text | — | #cba6f7 |
+| `accent_color_end` | text | — | #89b4fa |
+| `bg_image` | image | — | — |
+| `bg_image_opacity` | number | — | 0.15 |
+| `bg_overlay_opacity` | number | — | 0.7 |
+| `logo` | image | — | — |
+
+## Usage
+
+```bash
+cosy render --template notes-app --data input.json --output output.png
+```
diff --git a/docs/templates/whatsapp-chat.md b/docs/templates/whatsapp-chat.md
new file mode 100644
index 0000000..91c0400
--- /dev/null
+++ b/docs/templates/whatsapp-chat.md
@@ -0,0 +1,59 @@
+# WhatsApp Chat
+
+## Preview
+
+
+
+## Dimensions
+
+- **Width:** 1080px
+- **Height:** 1350px
+- **Aspect Ratio:** 1080:1350
+
+## Fonts
+
+- Inter
+
+## Slide Fields
+
+| Field | Type | Required | Max Length |
+|-------|------|----------|------------|
+| `contact_name` | text | — | 24 |
+| `contact_status` | text | — | 24 |
+| `text_color` | color | — | — |
+| `m1_text` | text | — | 120 |
+| `m1_side` | text | — | 5 |
+| `m2_text` | text | — | 120 |
+| `m2_side` | text | — | 5 |
+| `m3_text` | text | — | 120 |
+| `m3_side` | text | — | 5 |
+| `m4_text` | text | — | 120 |
+| `m4_side` | text | — | 5 |
+| `m5_text` | text | — | 120 |
+| `m5_side` | text | — | 5 |
+| `m6_text` | text | — | 120 |
+| `m6_side` | text | — | 5 |
+
+Message fields support inline markup: `*bold*`, `_italic_`, `*color:#hex*...*color*`.
+Each `mN_side` is `left` (default) or `right`.
+
+## Brand Fields
+
+| Field | Type | Required | Default |
+|-------|------|----------|---------|
+| `brand_name` | text | ✅ | — |
+| `brand_handle` | text | ✅ | — |
+| `bg_color` | bg | — | #1e1e2e |
+| `bg_color_end` | bg | — | #11111b |
+| `accent_color` | text | — | #cba6f7 |
+| `accent_color_end` | text | — | #89b4fa |
+| `bg_image` | image | — | — |
+| `bg_image_opacity` | number | — | 0.15 |
+| `bg_overlay_opacity` | number | — | 0.7 |
+| `logo` | image | — | — |
+
+## Usage
+
+```bash
+cosy render --template whatsapp-chat --data input.json --output output.png
+```
diff --git a/templates/notes-app/defaults.json b/templates/notes-app/defaults.json
new file mode 100644
index 0000000..548df1a
--- /dev/null
+++ b/templates/notes-app/defaults.json
@@ -0,0 +1,18 @@
+{
+ "brand": {
+ "brand_name": "ajianaz",
+ "brand_handle": "@ajianaz",
+ "bg_color": "#1e1e2e",
+ "bg_color_end": "#11111b",
+ "accent_color": "#cba6f7",
+ "accent_color_end": "#89b4fa",
+ "show_brand": false
+ },
+ "slides": [
+ {
+ "title": "Catatan penting",
+ "body": " inget ya: *gaji bukan ukuran value kamu*. value itu pas kamu bisa bilang *nggak* ke hal yang _nggak ngerespon_. sisanya cuma _noise_.",
+ "note_timestamp": "Today 21:14"
+ }
+ ]
+}
diff --git a/templates/notes-app/schema.json b/templates/notes-app/schema.json
new file mode 100644
index 0000000..d74a06f
--- /dev/null
+++ b/templates/notes-app/schema.json
@@ -0,0 +1,108 @@
+{
+ "id": "notes-app",
+ "name": "Notes App",
+ "dimensions": {
+ "width": 1080,
+ "height": 1350
+ },
+ "fonts": [
+ "Inter"
+ ],
+ "brand_fields": {
+ "show_brand": {
+ "type": "boolean",
+ "required": false,
+ "default": false,
+ "description": "Show brand name/handle watermark at bottom. Default: false."
+ },
+ "brand_name": {
+ "type": "text",
+ "required": true,
+ "max": 30
+ },
+ "brand_handle": {
+ "type": "text",
+ "required": true,
+ "max": 30
+ },
+ "bg_color": {
+ "type": "bg",
+ "required": false,
+ "default": "#1e1e2e"
+ },
+ "bg_color_end": {
+ "type": "bg",
+ "required": false,
+ "default": "#11111b"
+ },
+ "accent_color": {
+ "type": "text",
+ "required": false,
+ "default": "#cba6f7"
+ },
+ "accent_color_end": {
+ "type": "text",
+ "required": false,
+ "default": "#89b4fa"
+ },
+ "bg_image": {
+ "type": "image",
+ "required": false,
+ "description": "Background image — local path or http(s) URL (PNG/JPG/WebP)."
+ },
+ "bg_image_opacity": {
+ "type": "number",
+ "required": false,
+ "default": 0.15,
+ "description": "Opacity of the background image (0.0-1.0)."
+ },
+ "bg_overlay_opacity": {
+ "type": "number",
+ "required": false,
+ "default": 0.7,
+ "description": "Opacity of the gradient overlay when bg_image is set. Default: 0.7."
+ },
+ "logo": {
+ "type": "image",
+ "required": false,
+ "description": "Brand logo path (PNG/SVG). Rendered at bottom-right corner."
+ }
+ },
+ "slide_fields": {
+ "title": {
+ "type": "text",
+ "required": false,
+ "max": 40,
+ "description": "Note title (first line, bold)."
+ },
+ "body": {
+ "type": "text",
+ "required": true,
+ "max": 400,
+ "wrap_width": 34,
+ "options": ["markup"],
+ "description": "Note body text. Supports *bold*, _italic_, *color:#hex*...*color*."
+ },
+ "note_bg": {
+ "type": "color",
+ "required": false,
+ "description": "Note paper color (default iOS yellow #fef9c3)."
+ },
+ "title_color": {
+ "type": "color",
+ "required": false,
+ "description": "Title text color (default #1e1e2e)."
+ },
+ "body_color": {
+ "type": "color",
+ "required": false,
+ "description": "Body text color (default #3a3a3c)."
+ },
+ "note_timestamp": {
+ "type": "text",
+ "required": false,
+ "max": 30,
+ "description": "Timestamp text in the window bar (default 'Today')."
+ }
+ }
+}
diff --git a/templates/notes-app/template.svg b/templates/notes-app/template.svg
new file mode 100644
index 0000000..d61b0b7
--- /dev/null
+++ b/templates/notes-app/template.svg
@@ -0,0 +1,51 @@
+
diff --git a/templates/quote-stack/schema.json b/templates/quote-stack/schema.json
index 6c1cbf2..7c9c43a 100644
--- a/templates/quote-stack/schema.json
+++ b/templates/quote-stack/schema.json
@@ -80,7 +80,10 @@
"type": "text",
"required": true,
"max": 120,
- "wrap_width": 35
+ "wrap_width": 35,
+ "options": [
+ "markup"
+ ]
},
"q1_author": {
"type": "text",
@@ -91,7 +94,10 @@
"type": "text",
"required": true,
"max": 120,
- "wrap_width": 35
+ "wrap_width": 35,
+ "options": [
+ "markup"
+ ]
},
"q2_author": {
"type": "text",
@@ -102,7 +108,10 @@
"type": "text",
"required": true,
"max": 120,
- "wrap_width": 35
+ "wrap_width": 35,
+ "options": [
+ "markup"
+ ]
},
"q3_author": {
"type": "text",
diff --git a/templates/quote-stack/template.svg b/templates/quote-stack/template.svg
index 95fd16c..8e8a300 100644
--- a/templates/quote-stack/template.svg
+++ b/templates/quote-stack/template.svg
@@ -20,6 +20,9 @@
+ {# One styled segment #}
+ {% macro segout(seg) %}{% if seg.b %}
{% endif %}{% if seg.i %}{% endif %}{% if seg.c %}{{ seg.t }}{% else %}{{ seg.t }}{% endif %}{% if seg.i %}{% endif %}{% if seg.b %}{% endif %}{% endmacro %}
+
{% if bg_image_data_uri %}
{% endif %}
@@ -30,9 +33,9 @@
{{ slide.title }}
{% endif %}
{% set items = [
- (slide.q1_text, slide.q1_author, q1_text_lines),
- (slide.q2_text, slide.q2_author, q2_text_lines),
- (slide.q3_text, slide.q3_author, q3_text_lines)
+ (slide.q1_text, slide.q1_author, q1_text_lines, q1_text_segments),
+ (slide.q2_text, slide.q2_author, q2_text_lines, q2_text_segments),
+ (slide.q3_text, slide.q3_author, q3_text_lines, q3_text_segments)
] %}
{% for q in items %}
{% if q[0] %}
@@ -40,7 +43,12 @@
&
{% set ql = q[2] %}
- {% if ql %}
+ {% set qsegs = q[3] %}
+ {% if qsegs %}
+ {% for line in qsegs %}
+
{% for seg in line %}{{ segout(seg) }}{% endfor %}
+ {% endfor %}
+ {% elif ql %}
{% for line in ql %}
{{ line }}
{% endfor %}
diff --git a/templates/tiktok-quote/schema.json b/templates/tiktok-quote/schema.json
index 3e4829b..34da4e5 100644
--- a/templates/tiktok-quote/schema.json
+++ b/templates/tiktok-quote/schema.json
@@ -75,7 +75,10 @@
"type": "text",
"required": true,
"max": 200,
- "wrap_width": 25
+ "wrap_width": 25,
+ "options": [
+ "markup"
+ ]
},
"author": {
"type": "text",
diff --git a/templates/tiktok-quote/template.svg b/templates/tiktok-quote/template.svg
index 583c7a9..10bdc23 100644
--- a/templates/tiktok-quote/template.svg
+++ b/templates/tiktok-quote/template.svg
@@ -16,6 +16,11 @@
+ {# One styled segment #}
+ {% macro segout(seg) %}{% if seg.b %}
{% endif %}{% if seg.i %}{% endif %}{% if seg.c %}{{ seg.t }}{% else %}{{ seg.t }}{% endif %}{% if seg.i %}{% endif %}{% if seg.b %}{% endif %}{% endmacro %}
+ {# One quote line: segments variant #}
+ {% macro segline(line, first, x, dy) %}{% if first %}{% for seg in line %}{{ segout(seg) }}{% endfor %}{% else %}
{% for seg in line %}{{ segout(seg) }}{% endfor %}{% endif %}{% endmacro %}
+
{% if bg_image_data_uri %}
{% endif %}
@@ -23,7 +28,7 @@
- {% for line in quote_lines %}{{ line }}{% endfor %}
+ {% if quote_segments %}{% for line in quote_segments %}{{ segline(line, loop.first, "540", "72") }}{% endfor %}{% elif quote_lines %}{% for line in quote_lines %}{{ line }}{% endfor %}{% endif %}
{% if not quote_lines %}{{ slide.quote }}{% endif %}
diff --git a/templates/twitter-quote/schema.json b/templates/twitter-quote/schema.json
index 112bf40..6132afc 100644
--- a/templates/twitter-quote/schema.json
+++ b/templates/twitter-quote/schema.json
@@ -75,7 +75,10 @@
"type": "text",
"required": true,
"max": 280,
- "wrap_width": 35
+ "wrap_width": 35,
+ "options": [
+ "markup"
+ ]
},
"author": {
"type": "text",
diff --git a/templates/twitter-quote/template.svg b/templates/twitter-quote/template.svg
index 97b2bab..4e6eabd 100644
--- a/templates/twitter-quote/template.svg
+++ b/templates/twitter-quote/template.svg
@@ -16,6 +16,11 @@
+ {# One styled segment #}
+ {% macro segout(seg) %}{% if seg.b %}
{% endif %}{% if seg.i %}{% endif %}{% if seg.c %}{{ seg.t }}{% else %}{{ seg.t }}{% endif %}{% if seg.i %}{% endif %}{% if seg.b %}{% endif %}{% endmacro %}
+ {# One quote line: segments variant #}
+ {% macro segline(line, first, x, dy) %}{% if first %}{% for seg in line %}{{ segout(seg) }}{% endfor %}{% else %}
{% for seg in line %}{{ segout(seg) }}{% endfor %}{% endif %}{% endmacro %}
+
{% if bg_image_data_uri %}
{% endif %}
@@ -30,7 +35,7 @@
{{ brand.brand_name | default('') }}{% if brand.brand_handle %} · {{ brand.brand_handle }}{% endif %}
{% endif %}
- {% for line in quote_lines %}{{ line }}{% endfor %}
+ {% if quote_segments %}{% for line in quote_segments %}{{ segline(line, loop.first, "80", "55") }}{% endfor %}{% elif quote_lines %}{% for line in quote_lines %}{{ line }}{% endfor %}{% endif %}
{% if not quote_lines %}{{ slide.quote }}{% endif %}
{{ slide.author }}
diff --git a/templates/whatsapp-chat/defaults.json b/templates/whatsapp-chat/defaults.json
new file mode 100644
index 0000000..9a848d1
--- /dev/null
+++ b/templates/whatsapp-chat/defaults.json
@@ -0,0 +1,29 @@
+{
+ "brand": {
+ "brand_name": "ajianaz",
+ "brand_handle": "@ajianaz",
+ "bg_color": "#1e1e2e",
+ "bg_color_end": "#11111b",
+ "accent_color": "#cba6f7",
+ "accent_color_end": "#89b4fa",
+ "show_brand": false
+ },
+ "slides": [
+ {
+ "contact_name": "Tim Lead",
+ "contact_status": "online",
+ "m1_text": "Bro, besok bisa cover standup aku?",
+ "m1_side": "left",
+ "m2_text": "Bisa. *Standup jam 9 kan?*",
+ "m2_side": "right",
+ "m3_text": "Ye. Tapi jangan bilang siapa siapa",
+ "m3_side": "left",
+ "m4_text": "Alasannya apa _anak baru_ ngerasa dikejar",
+ "m4_side": "left",
+ "m5_text": "*Tenang.* Aku bilang kita yang minta",
+ "m5_side": "right",
+ "m6_text": "Makasih bro, kamu nyelamatin minggu aku",
+ "m6_side": "left"
+ }
+ ]
+}
diff --git a/templates/whatsapp-chat/schema.json b/templates/whatsapp-chat/schema.json
new file mode 100644
index 0000000..f38f971
--- /dev/null
+++ b/templates/whatsapp-chat/schema.json
@@ -0,0 +1,210 @@
+{
+ "id": "whatsapp-chat",
+ "name": "WhatsApp Chat",
+ "dimensions": {
+ "width": 1080,
+ "height": 1350
+ },
+ "fonts": [
+ "Inter"
+ ],
+ "brand_fields": {
+ "show_brand": {
+ "type": "boolean",
+ "required": false,
+ "default": false,
+ "description": "Show brand name/handle watermark at bottom. Default: false."
+ },
+ "brand_name": {
+ "type": "text",
+ "required": true,
+ "max": 30
+ },
+ "brand_handle": {
+ "type": "text",
+ "required": true,
+ "max": 30
+ },
+ "bg_color": {
+ "type": "bg",
+ "required": false,
+ "default": "#1e1e2e"
+ },
+ "bg_color_end": {
+ "type": "bg",
+ "required": false,
+ "default": "#11111b"
+ },
+ "accent_color": {
+ "type": "text",
+ "required": false,
+ "default": "#cba6f7"
+ },
+ "accent_color_end": {
+ "type": "text",
+ "required": false,
+ "default": "#89b4fa"
+ },
+ "bg_image": {
+ "type": "image",
+ "required": false,
+ "description": "Background image — local path or http(s) URL (PNG/JPG/WebP)."
+ },
+ "bg_image_opacity": {
+ "type": "number",
+ "required": false,
+ "default": 0.15,
+ "description": "Opacity of the background image (0.0-1.0)."
+ },
+ "bg_overlay_opacity": {
+ "type": "number",
+ "required": false,
+ "default": 0.7,
+ "description": "Opacity of the gradient overlay when bg_image is set. Lower = photo shows through more. Default: 0.7."
+ },
+ "logo": {
+ "type": "image",
+ "required": false,
+ "description": "Brand logo path (PNG/SVG). Rendered at bottom-right corner."
+ }
+ },
+ "slide_fields": {
+ "contact_name": {
+ "type": "text",
+ "required": false,
+ "max": 24,
+ "description": "Chat header contact name."
+ },
+ "contact_status": {
+ "type": "text",
+ "required": false,
+ "max": 24,
+ "description": "Chat header status line (e.g. 'online', 'last seen today')."
+ },
+ "text_color": {
+ "type": "color",
+ "required": false,
+ "description": "Message text color. Use a dark hex when the background is light."
+ },
+ "m1_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 1 text. Supports *bold*, _italic_, *color:#hex*...*color*."
+ },
+ "m1_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 1 bubble side: left or right (default left)."
+ },
+ "m2_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 2 text. Supports inline markup."
+ },
+ "m2_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 2 bubble side (default right)."
+ },
+ "m3_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 3 text. Supports inline markup."
+ },
+ "m3_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 3 bubble side (default left)."
+ },
+ "m4_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 4 text. Supports inline markup."
+ },
+ "m4_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 4 bubble side (default right)."
+ },
+ "m5_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 5 text. Supports inline markup."
+ },
+ "m5_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 5 bubble side (default left)."
+ },
+ "m6_text": {
+ "type": "text",
+ "required": false,
+ "max": 60,
+ "wrap_width": 34,
+ "options": [
+ "markup"
+ ],
+ "description": "Message 6 text. Supports inline markup."
+ },
+ "m6_side": {
+ "type": "text",
+ "required": false,
+ "max": 5,
+ "options": [
+ "left",
+ "right"
+ ],
+ "description": "Message 6 bubble side (default right)."
+ }
+ }
+}
diff --git a/templates/whatsapp-chat/template.svg b/templates/whatsapp-chat/template.svg
new file mode 100644
index 0000000..62cc3db
--- /dev/null
+++ b/templates/whatsapp-chat/template.svg
@@ -0,0 +1,77 @@
+