Visual Media Integration: Images, Charts & Cover Images skill
- Cover Images & OG Images
by AgriciDaniel·MIT license·★ 2,219 Stars on the repo·GitHub ↗
Files of Visual Media Integration: Images, Charts & Cover Images
AgriciDaniel/
Show the full text432 lines
Visual Media Integration: Images, Charts & Cover Images
Contents
- Cover Images & OG Images
- Image Sourcing
- Image Format Optimization
- SVG Chart Integration (Built-In)
- YouTube Video Embeds
Cover Images & OG Images
Every blog post should have a cover image for social sharing and blog listings.
Option 1: Photo Cover (Pixabay/Unsplash/Pexels)
Search for a wide, high-quality image relevant to the topic through official APIs when keys are available, or through Openverse for CC assets. Download the chosen asset locally, store attribution/license metadata, and do not hotlink raw CDN URLs.
Sizing requirements:
| Use Case | Dimensions | Aspect Ratio |
|---|---|---|
| Blog hero/cover | 1200x630 or 1920x1080 | 1.91:1 or 16:9 |
| Open Graph (OG) | 1200x630 | 1.91:1 (required) |
| Twitter card | 1200x628 | ~1.91:1 |
Resize/crop locally to the target dimensions and keep hero-credit.txt or
equivalent attribution next to the downloaded asset.
Option 2: Generated Chart Cover (via blog-chart)
For branded or data-driven covers, generate via blog-chart:
- Text-on-gradient with title and key statistic
- Dark-mode compatible (use
currentColorwhere possible) - Include blog name/author subtle branding
- ViewBox:
0 0 1200 630for OG compatibility - Render the final social image to PNG or WebP at 1200x630. Do not use raw SVG
as
og:image; many social parsers do not reliably render SVG previews.
Option 3: AI-Generated Cover (via blog-image)
For custom, topic-specific covers when stock photos don't match:
- Requires nanobanana-mcp configured (see
/blog image setup) - Uses 6-component Reasoning Brief for optimized Gemini prompts
- Supports 14 aspect ratios (16:9 for hero, 1.91:1 for OG)
- Use current Gemini image models:
gemini-3.1-flash-image,gemini-3.1-flash-lite-image, orgemini-3-pro-image - Post-processing: auto-resize to 1200x630, convert to WebP/AVIF
Best for: Abstract topics, branded imagery, niche subjects with poor stock results.
Frontmatter Fields
---
title: "..."
description: "..."
coverImage: "/images/blog/topic-cover.jpg"
coverImageAlt: "Descriptive sentence about the cover image"
ogImage: "/images/blog/topic-og.jpg" # Same as cover or custom OG
date: "YYYY-MM-DD"
---
coverImage: displayed as hero at the top of the postogImage: used for social sharing previews (Open Graph / Twitter Card)- If only one image, use the same URL for both fields
- Alt text is required for the cover image
When to Use Each Option
| Scenario | Recommendation |
|---|---|
| General topic | Photo cover from Pixabay/Unsplash/Pexels |
| Data-heavy article | Generated SVG with key stat highlight |
| Brand-focused | Generated SVG with brand colors |
| Abstract/niche topic | AI-generated via blog-image (Gemini) |
| Tutorial/how-to | Screenshot or relevant photo |
Image Sourcing
Pixabay (Preferred)
- License: Pixabay Content License - free for commercial use, no attribution required
- URL: https://pixabay.com
- Hotlinking: Allowed via CDN URLs
Finding images:
- WebSearch:
site:pixabay.com [topic keywords] - Visit the image page to get the direct CDN URL
- Direct URL pattern:
https://cdn.pixabay.com/photo/YYYY/MM/DD/HH/MM/filename.jpg - Verify:
curl -sI "<url>" | head -1- must return HTTP 200
Sizing: Append query params for optimization:
- Blog hero: original size (typically 1920px wide)
- Inline images: use as-is (most are 1280px+)
Unsplash (Alternative)
- License: Unsplash License - free for commercial use, no attribution required
- URL: https://unsplash.com
- Hotlinking: Required - must use their CDN
Finding images:
- WebSearch:
site:unsplash.com [topic keywords] - Extract photo ID from URL (e.g.,
photo-1234567890123-abcdef) - Build direct URL:
https://images.unsplash.com/photo-<id>?w=1200&h=630&fit=crop&q=80 - Verify:
curl -sI "<url>" | head -1- must return HTTP 200
Pexels (Fallback)
- License: Pexels License - free for commercial use, no attribution required
- URL: https://pexels.com
- Finding: WebSearch
site:pexels.com [topic keywords]
Image Usage Rules
| Rule | Requirement |
|---|---|
| Alt text | Required on ALL images - full descriptive sentence |
| Placement | After H2 headings, before body text |
| Distribution | Spread evenly - never cluster images |
| Count | Intent-based density within the page-weight budget |
| Relevance | Must relate to adjacent content |
| Format | AVIF preferred, WebP fallback, JPEG last resort |
Image Density by Content Type
Use one density model: add visuals where they clarify, prove, or summarize the adjacent section, while keeping total image payload under the page budget.
| Content Type | Typical Density | Example (2,000-word post) |
|---|---|---|
| Standard article | 1 visual per 400-600 words | 3-5 visuals |
| How-to or tutorial | 1 visual per major step | 5-8 visuals |
| Listicle or product roundup | 1 visual per item only when useful | 5-10 visuals |
| Case study or data post | charts/screenshots for proof points | 4-7 visuals |
Avoid mechanical image-every-75-100-word targets. Use optimized formats (AVIF/WebP) and responsive sizes; if the post exceeds the page-weight budget, reduce decorative media before cutting proof screenshots or charts.
SVG Impact on Engagement
D.C. Thomson case study results after replacing raster images with contextual SVGs:
- Session duration doubled
- 317% increase in read-to-completion rate
- SVGs are resolution-independent, lightweight, and dark-mode compatible
Alt Text Guidelines
- Full descriptive sentence including topic keywords naturally
- Describe what the image shows AND its relevance to the content
- 10-125 characters
- No keyword stuffing - natural language only
Good: Marketing team analyzing AI search traffic data on a dashboard showing citation metrics
Bad: SEO AI marketing blog optimization image
AI Systems and Images: AI crawlers read alt text and captions, NOT the images themselves. Write context-rich alt text that conveys the data or insight the image represents. For charts, include the key data point in the alt text. For screenshots, describe what the screenshot demonstrates.
Embedding Images
Standard Markdown:

MDX (Next.js):

For Next.js projects, verify next.config.ts includes the image domain:
images: {
remotePatterns: [
{ protocol: 'https', hostname: 'cdn.pixabay.com' },
{ protocol: 'https', hostname: 'images.unsplash.com' },
{ protocol: 'https', hostname: 'images.pexels.com' },
],
}
HTML:
<figure>
<img src="https://cdn.pixabay.com/photo/.../image.jpg"
alt="Descriptive alt text sentence"
width="1200" height="630" loading="lazy">
<figcaption>Photo via Pixabay</figcaption>
</figure>
Image Format Optimization
AVIF as Primary Format
AVIF is the recommended image format for 2026:
- ~50% smaller than JPEG at equivalent quality
- ~20-30% smaller than WebP
- 93.8% global browser support (caniuse, Jan 2026)
- Supports HDR, wide color gamut, and transparency
<picture> Element with Progressive Fallback
Always use the <picture> element for format negotiation:
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="Descriptive alt text" width="1200" height="630" loading="lazy">
</picture>
This pattern serves AVIF to supporting browsers, falls back to WebP, then JPEG.
LCP Image Rules
NEVER use loading="lazy" on hero/LCP (Largest Contentful Paint) images.
Lazy loading the LCP image delays the largest element on the page and directly
harms Core Web Vitals scores.
For hero/above-the-fold images:
<img src="hero.avif" alt="..." width="1200" height="630"
fetchpriority="high" decoding="async">
For below-the-fold images:
<img src="image.avif" alt="..." width="800" height="450"
loading="lazy" decoding="async">
Dark Mode Image Support
Use <picture> with prefers-color-scheme media query for theme-aware images:
<picture>
<source srcset="chart-dark.avif" media="(prefers-color-scheme: dark)" type="image/avif">
<source srcset="chart-dark.webp" media="(prefers-color-scheme: dark)" type="image/webp">
<source srcset="chart-light.avif" type="image/avif">
<source srcset="chart-light.webp" type="image/webp">
<img src="chart-light.jpg" alt="Descriptive alt text" width="800" height="450">
</picture>
CSS variable pattern for inline SVG dark mode:
:root {
--chart-bg: #ffffff;
--chart-text: #111827;
--chart-grid: rgba(0, 0, 0, 0.08);
}
@media (prefers-color-scheme: dark) {
:root {
--chart-bg: transparent;
--chart-text: #f3f4f6;
--chart-grid: rgba(255, 255, 255, 0.08);
}
}
SVG Chart Integration (Built-In)
Charts are generated by the blog-chart sub-skill. The writer identifies chart-worthy
data during the writing process and delegates chart generation internally.
Chart Type Selection Guide
| Data Pattern | Best Chart Type |
|---|---|
| Before/after comparison | Grouped bar chart |
| Ranked factors / correlations | Lollipop chart |
| Parts of whole / market share | Donut chart |
| Trend over time | Line chart |
| Percentage improvement | Horizontal bar chart |
| Distribution / range | Area chart |
| Multi-dimensional scoring | Radar chart |
Diversity is mandatory - never use the same chart type twice in one post. Target 2-4 charts per 2,000-word post.
Dark-Mode Compatible Styling
All charts must work on both dark and light backgrounds:
Text elements: fill="currentColor"
Grid lines: stroke="currentColor" opacity="0.08"
Axis lines: stroke="currentColor" opacity="0.3"
Background: transparent (no fill on root SVG)
Subtitle text: fill="currentColor" opacity="0.45"
Source text: fill="currentColor" opacity="0.35"
Label text: fill="currentColor" opacity="0.8"
Color Palette (works on dark and light)
| Color | Hex | Use Case |
|---|---|---|
| Orange | #f97316 |
Primary / highest value |
| Sky Blue | #38bdf8 |
Secondary / comparison |
| Purple | #a78bfa |
Tertiary / special category |
| Green | #22c55e |
Quaternary / positive indicator |
For text inside colored elements: fill="white" with fontWeight="800".
Standard SVG Shell
<svg
viewBox="0 0 560 380"
style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif"
role="img"
aria-label="Chart description with key data point"
>
<title>Chart Title</title>
<desc>Description for screen readers with all key data points and source</desc>
<!-- Chart content -->
<text x="280" y="372" text-anchor="middle" font-size="10" fill="currentColor" opacity="0.35">
Source: Source Name (Year)
</text>
</svg>
JSX/MDX Shell (camelCase attributes)
<svg
viewBox="0 0 560 380"
style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}}
role="img"
aria-label="Chart description"
>
<title>Chart Title</title>
<desc>Description for screen readers</desc>
{/* Chart content */}
<text x="280" y="372" textAnchor="middle" fontSize="10" fill="currentColor" opacity="0.35">
Source: Source Name (Year)
</text>
</svg>
JSX Attribute Conversion (Required for MDX)
| HTML | JSX |
|---|---|
stroke-width |
strokeWidth |
stroke-dasharray |
strokeDasharray |
stroke-linecap |
strokeLinecap |
text-anchor |
textAnchor |
font-size |
fontSize |
font-weight |
fontWeight |
font-family |
fontFamily |
class |
className |
style="..." |
style={{...}} |
Embedding Charts
Standard HTML:
<figure>
<svg viewBox="0 0 560 380" ...>...</svg>
<figcaption>Source: Source Name, Year</figcaption>
</figure>
MDX:
<figure className="chart-container" style={{margin: '2.5rem 0', textAlign: 'center', padding: '1.5rem', borderRadius: '12px'}}>
<svg viewBox="0 0 560 380" ...>...</svg>
</figure>
Invoking blog-chart
When generating charts, pass to the blog-chart sub-skill:
- Chart type (ensure diversity - never repeat within a post)
- Title for the chart
- Exact data values with sources
- Source attribution (name and year)
- Platform format: html or mdx
The sub-skill returns complete SVG wrapped in a <figure>. Verify before embedding:
currentColorusage (no hardcoded text colors)- No white/light backgrounds
- If MDX: camelCase attributes
- Source attribution present
Common Pitfalls
| Mistake | Impact | Fix |
|---|---|---|
fill="#111827" on text |
Invisible on dark mode | Use fill="currentColor" |
rect fill="white" background |
Bright flash on dark mode | Remove or use transparent |
stroke-width in MDX |
Compilation error | Use strokeWidth |
class in MDX |
Compilation error | Use className |
| Same chart type twice | Visual monotony | Enforce chart diversity |
No role="img" |
Accessibility failure | Always include |
| No source attribution | Trust issue | Always cite data source |
YouTube Video Embeds
YouTube videos are part of the visual media mix alongside images, charts, and AI-generated images. Vendor studies report a strong correlation with AI visibility, but treat that as directional and use videos only when relevant.
See video-embeds.md for:
- Embed patterns (srcdoc lazy loading for MDX, HTML, Markdown, Hugo)
- Video quality criteria and scoring (min 50/100)
- Placement strategy (2-3 per post, 500+ words apart)
- VideoObject JSON-LD schema template
- Noscript fallback for AI crawlers
| 1 | # Visual Media Integration: Images, Charts & Cover Images |
| 2 | |
| 3 | ## Contents |
| 4 | |
| 5 | [Cover Images & OG Images] |
| 6 | [Image Sourcing] |
| 7 | [Image Format Optimization] |
| 8 | [SVG Chart Integration (Built-In)] |
| 9 | [YouTube Video Embeds] |
| 10 | |
| 11 | ## Cover Images & OG Images |
| 12 | |
| 13 | Every blog post should have a cover image for social sharing and blog listings. |
| 14 | |
| 15 | ### Option 1: Photo Cover (Pixabay/Unsplash/Pexels) |
| 16 | |
| 17 | Search for a wide, high-quality image relevant to the topic through official |
| 18 | APIs when keys are available, or through Openverse for CC assets. Download the |
| 19 | chosen asset locally, store attribution/license metadata, and do not hotlink raw |
| 20 | CDN URLs. |
| 21 | |
| 22 | **Sizing requirements:** |
| 23 | | Use Case | Dimensions | Aspect Ratio | |
| 24 | |----------|-----------|--------------| |
| 25 | | Blog hero/cover | 1200x630 or 1920x1080 | 1.91:1 or 16:9 | |
| 26 | | Open Graph (OG) | 1200x630 | 1.91:1 (required) | |
| 27 | | Twitter card | 1200x628 | ~1.91:1 | |
| 28 | |
| 29 | Resize/crop locally to the target dimensions and keep `hero-credit.txt` or |
| 30 | equivalent attribution next to the downloaded asset. |
| 31 | |
| 32 | ### Option 2: Generated Chart Cover (via blog-chart) |
| 33 | |
| 34 | For branded or data-driven covers, generate via `blog-chart`: |
| 35 | Text-on-gradient with title and key statistic |
| 36 | Dark-mode compatible (use `currentColor` where possible) |
| 37 | Include blog name/author subtle branding |
| 38 | ViewBox: `0 0 1200 630` for OG compatibility |
| 39 | Render the final social image to PNG or WebP at 1200x630. Do not use raw SVG |
| 40 | as `og:image`; many social parsers do not reliably render SVG previews. |
| 41 | |
| 42 | ### Option 3: AI-Generated Cover (via blog-image) |
| 43 | |
| 44 | For custom, topic-specific covers when stock photos don't match: |
| 45 | Requires nanobanana-mcp configured (see `/blog image setup`) |
| 46 | Uses 6-component Reasoning Brief for optimized Gemini prompts |
| 47 | Supports 14 aspect ratios (16:9 for hero, 1.91:1 for OG) |
| 48 | Use current Gemini image models: `gemini-3.1-flash-image`, |
| 49 | `gemini-3.1-flash-lite-image`, or `gemini-3-pro-image` |
| 50 | Post-processing: auto-resize to 1200x630, convert to WebP/AVIF |
| 51 | |
| 52 | Best for: Abstract topics, branded imagery, niche subjects with poor stock results. |
| 53 | |
| 54 | ### Frontmatter Fields |
| 55 | |
| 56 | |
| 57 | |
| 58 | title: "..." |
| 59 | description: "..." |
| 60 | coverImage: "/images/blog/topic-cover.jpg" |
| 61 | coverImageAlt: "Descriptive sentence about the cover image" |
| 62 | ogImage: "/images/blog/topic-og.jpg" # Same as cover or custom OG |
| 63 | date: "YYYY-MM-DD" |
| 64 | |
| 65 | |
| 66 | |
| 67 | `coverImage`: displayed as hero at the top of the post |
| 68 | `ogImage`: used for social sharing previews (Open Graph / Twitter Card) |
| 69 | If only one image, use the same URL for both fields |
| 70 | Alt text is required for the cover image |
| 71 | |
| 72 | ### When to Use Each Option |
| 73 | |
| 74 | | Scenario | Recommendation | |
| 75 | |----------|---------------| |
| 76 | | General topic | Photo cover from Pixabay/Unsplash/Pexels | |
| 77 | | Data-heavy article | Generated SVG with key stat highlight | |
| 78 | | Brand-focused | Generated SVG with brand colors | |
| 79 | | Abstract/niche topic | AI-generated via `blog-image` (Gemini) | |
| 80 | | Tutorial/how-to | Screenshot or relevant photo | |
| 81 | |
| 82 | |
| 83 | |
| 84 | ## Image Sourcing |
| 85 | |
| 86 | ### Pixabay (Preferred) |
| 87 | **License**: Pixabay Content License - free for commercial use, no attribution required |
| 88 | **URL**: https://pixabay.com |
| 89 | **Hotlinking**: Allowed via CDN URLs |
| 90 | |
| 91 | **Finding images:** |
| 92 | WebSearch: `site:pixabay.com [topic keywords]` |
| 93 | Visit the image page to get the direct CDN URL |
| 94 | Direct URL pattern: `https://cdn.pixabay.com/photo/YYYY/MM/DD/HH/MM/filename.jpg` |
| 95 | Verify: `curl -sI "<url>" | head -1` - must return HTTP 200 |
| 96 | |
| 97 | **Sizing**: Append query params for optimization: |
| 98 | Blog hero: original size (typically 1920px wide) |
| 99 | Inline images: use as-is (most are 1280px+) |
| 100 | |
| 101 | ### Unsplash (Alternative) |
| 102 | **License**: Unsplash License - free for commercial use, no attribution required |
| 103 | **URL**: https://unsplash.com |
| 104 | **Hotlinking**: Required - must use their CDN |
| 105 | |
| 106 | **Finding images:** |
| 107 | WebSearch: `site:unsplash.com [topic keywords]` |
| 108 | Extract photo ID from URL (e.g., `photo-1234567890123-abcdef`) |
| 109 | Build direct URL: `https://images.unsplash.com/photo-<id>?w=1200&h=630&fit=crop&q=80` |
| 110 | Verify: `curl -sI "<url>" | head -1` - must return HTTP 200 |
| 111 | |
| 112 | ### Pexels (Fallback) |
| 113 | **License**: Pexels License - free for commercial use, no attribution required |
| 114 | **URL**: https://pexels.com |
| 115 | **Finding**: WebSearch `site:pexels.com [topic keywords]` |
| 116 | |
| 117 | ### Image Usage Rules |
| 118 | |
| 119 | | Rule | Requirement | |
| 120 | |------|-------------| |
| 121 | | Alt text | Required on ALL images - full descriptive sentence | |
| 122 | | Placement | After H2 headings, before body text | |
| 123 | | Distribution | Spread evenly - never cluster images | |
| 124 | | Count | Intent-based density within the page-weight budget | |
| 125 | | Relevance | Must relate to adjacent content | |
| 126 | | Format | AVIF preferred, WebP fallback, JPEG last resort | |
| 127 | |
| 128 | ### Image Density by Content Type |
| 129 | |
| 130 | Use one density model: add visuals where they clarify, prove, or summarize the |
| 131 | adjacent section, while keeping total image payload under the page budget. |
| 132 | |
| 133 | | Content Type | Typical Density | Example (2,000-word post) | |
| 134 | |-------------|-----------------|---------------------------| |
| 135 | | Standard article | 1 visual per 400-600 words | 3-5 visuals | |
| 136 | | How-to or tutorial | 1 visual per major step | 5-8 visuals | |
| 137 | | Listicle or product roundup | 1 visual per item only when useful | 5-10 visuals | |
| 138 | | Case study or data post | charts/screenshots for proof points | 4-7 visuals | |
| 139 | |
| 140 | Avoid mechanical image-every-75-100-word targets. Use optimized formats |
| 141 | (AVIF/WebP) and responsive sizes; if the post exceeds the page-weight budget, |
| 142 | reduce decorative media before cutting proof screenshots or charts. |
| 143 | |
| 144 | ### SVG Impact on Engagement |
| 145 | |
| 146 | D.C. Thomson case study results after replacing raster images with contextual SVGs: |
| 147 | Session duration doubled |
| 148 | 317% increase in read-to-completion rate |
| 149 | SVGs are resolution-independent, lightweight, and dark-mode compatible |
| 150 | |
| 151 | ### Alt Text Guidelines |
| 152 | Full descriptive sentence including topic keywords naturally |
| 153 | Describe what the image shows AND its relevance to the content |
| 154 | 10-125 characters |
| 155 | No keyword stuffing - natural language only |
| 156 | |
| 157 | Good: `Marketing team analyzing AI search traffic data on a dashboard showing citation metrics` |
| 158 | Bad: `SEO AI marketing blog optimization image` |
| 159 | |
| 160 | **AI Systems and Images**: AI crawlers read alt text and captions, NOT the images |
| 161 | themselves. Write context-rich alt text that conveys the data or insight the image |
| 162 | represents. For charts, include the key data point in the alt text. For screenshots, |
| 163 | describe what the screenshot demonstrates. |
| 164 | |
| 165 | ### Embedding Images |
| 166 | |
| 167 | **Standard Markdown:** |
| 168 | |
| 169 |  |
| 170 | |
| 171 | |
| 172 | **MDX (Next.js):** |
| 173 | |
| 174 |  |
| 175 | |
| 176 | |
| 177 | For Next.js projects, verify `next.config.ts` includes the image domain: |
| 178 | |
| 179 | images: { |
| 180 | remotePatterns: [ |
| 181 | { protocol: 'https', hostname: 'cdn.pixabay.com' }, |
| 182 | { protocol: 'https', hostname: 'images.unsplash.com' }, |
| 183 | { protocol: 'https', hostname: 'images.pexels.com' }, |
| 184 | ], |
| 185 | } |
| 186 | |
| 187 | |
| 188 | **HTML:** |
| 189 | |
| 190 | <figure> |
| 191 | <img src="https://cdn.pixabay.com/photo/.../image.jpg" |
| 192 | alt="Descriptive alt text sentence" |
| 193 | width="1200" height="630" loading="lazy"> |
| 194 | <figcaption>Photo via Pixabay</figcaption> |
| 195 | </figure> |
| 196 | |
| 197 | |
| 198 | |
| 199 | |
| 200 | ## Image Format Optimization |
| 201 | |
| 202 | ### AVIF as Primary Format |
| 203 | |
| 204 | AVIF is the recommended image format for 2026: |
| 205 | ~50% smaller than JPEG at equivalent quality |
| 206 | ~20-30% smaller than WebP |
| 207 | 93.8% global browser support (caniuse, Jan 2026) |
| 208 | Supports HDR, wide color gamut, and transparency |
| 209 | |
| 210 | ### `<picture>` Element with Progressive Fallback |
| 211 | |
| 212 | Always use the `<picture>` element for format negotiation: |
| 213 | |
| 214 | |
| 215 | <picture> |
| 216 | <source srcset="image.avif" type="image/avif"> |
| 217 | <source srcset="image.webp" type="image/webp"> |
| 218 | <img src="image.jpg" alt="Descriptive alt text" width="1200" height="630" loading="lazy"> |
| 219 | </picture> |
| 220 | |
| 221 | |
| 222 | This pattern serves AVIF to supporting browsers, falls back to WebP, then JPEG. |
| 223 | |
| 224 | ### LCP Image Rules |
| 225 | |
| 226 | **NEVER** use `loading="lazy"` on hero/LCP (Largest Contentful Paint) images. |
| 227 | Lazy loading the LCP image delays the largest element on the page and directly |
| 228 | harms Core Web Vitals scores. |
| 229 | |
| 230 | For hero/above-the-fold images: |
| 231 | |
| 232 | <img src="hero.avif" alt="..." width="1200" height="630" |
| 233 | fetchpriority="high" decoding="async"> |
| 234 | |
| 235 | |
| 236 | For below-the-fold images: |
| 237 | |
| 238 | <img src="image.avif" alt="..." width="800" height="450" |
| 239 | loading="lazy" decoding="async"> |
| 240 | |
| 241 | |
| 242 | ### Dark Mode Image Support |
| 243 | |
| 244 | Use `<picture>` with `prefers-color-scheme` media query for theme-aware images: |
| 245 | |
| 246 | |
| 247 | <picture> |
| 248 | <source srcset="chart-dark.avif" media="(prefers-color-scheme: dark)" type="image/avif"> |
| 249 | <source srcset="chart-dark.webp" media="(prefers-color-scheme: dark)" type="image/webp"> |
| 250 | <source srcset="chart-light.avif" type="image/avif"> |
| 251 | <source srcset="chart-light.webp" type="image/webp"> |
| 252 | <img src="chart-light.jpg" alt="Descriptive alt text" width="800" height="450"> |
| 253 | </picture> |
| 254 | |
| 255 | |
| 256 | CSS variable pattern for inline SVG dark mode: |
| 257 | |
| 258 | :root { |
| 259 | --chart-bg: #ffffff; |
| 260 | --chart-text: #111827; |
| 261 | --chart-grid: rgba(0, 0, 0, 0.08); |
| 262 | } |
| 263 | |
| 264 | @media (prefers-color-scheme: dark) { |
| 265 | :root { |
| 266 | --chart-bg: transparent; |
| 267 | --chart-text: #f3f4f6; |
| 268 | --chart-grid: rgba(255, 255, 255, 0.08); |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | |
| 273 | |
| 274 | |
| 275 | ## SVG Chart Integration (Built-In) |
| 276 | |
| 277 | Charts are generated by the `blog-chart` sub-skill. The writer identifies chart-worthy |
| 278 | data during the writing process and delegates chart generation internally. |
| 279 | |
| 280 | ### Chart Type Selection Guide |
| 281 | |
| 282 | | Data Pattern | Best Chart Type | |
| 283 | |-------------|-----------------| |
| 284 | | Before/after comparison | Grouped bar chart | |
| 285 | | Ranked factors / correlations | Lollipop chart | |
| 286 | | Parts of whole / market share | Donut chart | |
| 287 | | Trend over time | Line chart | |
| 288 | | Percentage improvement | Horizontal bar chart | |
| 289 | | Distribution / range | Area chart | |
| 290 | | Multi-dimensional scoring | Radar chart | |
| 291 | |
| 292 | **Diversity is mandatory** - never use the same chart type twice in one post. |
| 293 | Target 2-4 charts per 2,000-word post. |
| 294 | |
| 295 | ### Dark-Mode Compatible Styling |
| 296 | |
| 297 | All charts must work on both dark and light backgrounds: |
| 298 | |
| 299 | |
| 300 | Text elements: fill="currentColor" |
| 301 | Grid lines: stroke="currentColor" opacity="0.08" |
| 302 | Axis lines: stroke="currentColor" opacity="0.3" |
| 303 | Background: transparent (no fill on root SVG) |
| 304 | Subtitle text: fill="currentColor" opacity="0.45" |
| 305 | Source text: fill="currentColor" opacity="0.35" |
| 306 | Label text: fill="currentColor" opacity="0.8" |
| 307 | |
| 308 | |
| 309 | ### Color Palette (works on dark and light) |
| 310 | |
| 311 | | Color | Hex | Use Case | |
| 312 | |-------|-----|----------| |
| 313 | | Orange | `#f97316` | Primary / highest value | |
| 314 | | Sky Blue | `#38bdf8` | Secondary / comparison | |
| 315 | | Purple | `#a78bfa` | Tertiary / special category | |
| 316 | | Green | `#22c55e` | Quaternary / positive indicator | |
| 317 | |
| 318 | For text inside colored elements: `fill="white"` with `fontWeight="800"`. |
| 319 | |
| 320 | ### Standard SVG Shell |
| 321 | |
| 322 | |
| 323 | <svg |
| 324 | viewBox="0 0 560 380" |
| 325 | style="max-width: 100%; height: auto; font-family: 'Inter', system-ui, sans-serif" |
| 326 | role="img" |
| 327 | aria-label="Chart description with key data point" |
| 328 | > |
| 329 | <title>Chart Title</title> |
| 330 | <desc>Description for screen readers with all key data points and source</desc> |
| 331 | |
| 332 | <!-- Chart content --> |
| 333 | |
| 334 | <text x="280" y="372" text-anchor="middle" font-size="10" fill="currentColor" opacity="0.35"> |
| 335 | Source: Source Name (Year) |
| 336 | </text> |
| 337 | </svg> |
| 338 | |
| 339 | |
| 340 | ### JSX/MDX Shell (camelCase attributes) |
| 341 | |
| 342 | |
| 343 | <svg |
| 344 | viewBox="0 0 560 380" |
| 345 | style={{maxWidth: '100%', height: 'auto', fontFamily: "'Inter', system-ui, sans-serif"}} |
| 346 | role="img" |
| 347 | aria-label="Chart description" |
| 348 | > |
| 349 | <title>Chart Title</title> |
| 350 | <desc>Description for screen readers</desc> |
| 351 | |
| 352 | {/* Chart content */} |
| 353 | |
| 354 | <text x="280" y="372" textAnchor="middle" fontSize="10" fill="currentColor" opacity="0.35"> |
| 355 | Source: Source Name (Year) |
| 356 | </text> |
| 357 | </svg> |
| 358 | |
| 359 | |
| 360 | ### JSX Attribute Conversion (Required for MDX) |
| 361 | |
| 362 | | HTML | JSX | |
| 363 | |------|-----| |
| 364 | | `stroke-width` | `strokeWidth` | |
| 365 | | `stroke-dasharray` | `strokeDasharray` | |
| 366 | | `stroke-linecap` | `strokeLinecap` | |
| 367 | | `text-anchor` | `textAnchor` | |
| 368 | | `font-size` | `fontSize` | |
| 369 | | `font-weight` | `fontWeight` | |
| 370 | | `font-family` | `fontFamily` | |
| 371 | | `class` | `className` | |
| 372 | | `style="..."` | `style={{...}}` | |
| 373 | |
| 374 | ### Embedding Charts |
| 375 | |
| 376 | **Standard HTML:** |
| 377 | |
| 378 | <figure> |
| 379 | <svg viewBox="0 0 560 380" ...>...</svg> |
| 380 | <figcaption>Source: Source Name, Year</figcaption> |
| 381 | </figure> |
| 382 | |
| 383 | |
| 384 | **MDX:** |
| 385 | |
| 386 | <figure className="chart-container" style={{margin: '2.5rem 0', textAlign: 'center', padding: '1.5rem', borderRadius: '12px'}}> |
| 387 | <svg viewBox="0 0 560 380" ...>...</svg> |
| 388 | </figure> |
| 389 | |
| 390 | |
| 391 | ### Invoking blog-chart |
| 392 | |
| 393 | When generating charts, pass to the `blog-chart` sub-skill: |
| 394 | **Chart type** (ensure diversity - never repeat within a post) |
| 395 | **Title** for the chart |
| 396 | **Exact data values** with sources |
| 397 | **Source attribution** (name and year) |
| 398 | **Platform format**: html or mdx |
| 399 | |
| 400 | The sub-skill returns complete SVG wrapped in a `<figure>`. Verify before embedding: |
| 401 | `currentColor` usage (no hardcoded text colors) |
| 402 | No white/light backgrounds |
| 403 | If MDX: camelCase attributes |
| 404 | Source attribution present |
| 405 | |
| 406 | ### Common Pitfalls |
| 407 | |
| 408 | | Mistake | Impact | Fix | |
| 409 | |---------|--------|-----| |
| 410 | | `fill="#111827"` on text | Invisible on dark mode | Use `fill="currentColor"` | |
| 411 | | `rect fill="white"` background | Bright flash on dark mode | Remove or use transparent | |
| 412 | | `stroke-width` in MDX | Compilation error | Use `strokeWidth` | |
| 413 | | `class` in MDX | Compilation error | Use `className` | |
| 414 | | Same chart type twice | Visual monotony | Enforce chart diversity | |
| 415 | | No `role="img"` | Accessibility failure | Always include | |
| 416 | | No source attribution | Trust issue | Always cite data source | |
| 417 | |
| 418 | |
| 419 | |
| 420 | ## YouTube Video Embeds |
| 421 | |
| 422 | YouTube videos are part of the visual media mix alongside images, charts, and |
| 423 | AI-generated images. Vendor studies report a strong correlation with AI |
| 424 | visibility, but treat that as directional and use videos only when relevant. |
| 425 | |
| 426 | See `video-embeds.md` for: |
| 427 | Embed patterns (srcdoc lazy loading for MDX, HTML, Markdown, Hugo) |
| 428 | Video quality criteria and scoring (min 50/100) |
| 429 | Placement strategy (2-3 per post, 500+ words apart) |
| 430 | VideoObject JSON-LD schema template |
| 431 | Noscript fallback for AI crawlers |
| 432 |
Discussion
Browse more free Claude skills.