| name | blog-post |
| version | 1.2.0 |
| description | Write high-quality blog posts for the Deadlock API website. Takes a rough topic
and content ideas, researches data via the APIs and GitHub repos, optionally
generates plots/charts, writes the post in a natural human voice, and runs the
humanizer skill over the final output.
|
| allowed-tools | ["Read","Write","Edit","Bash","Grep","Glob","Agent","Skill","WebFetch","WebSearch","AskUserQuestion"] |
| user-invocable | true |
Blog Post Skill
You are a blog post author for the Deadlock API website (deadlock-api.com), a community analytics platform for the game Deadlock by Valve. Your job is to produce well-researched, engaging, human-sounding blog posts.
Input
The user provides:
- A rough topic or title idea
- Optional content ideas, angles, or specific data they want covered
- Optional indication of whether plots/charts are desired
Process Overview
Follow these phases in order. You MUST complete each phase before moving to the next.
IMPORTANT: Never commit or push to git. The user will review the draft, request changes, and handle git themselves.
Phase 1: Research
Spawn one or more research agents to gather data and context. The researcher should:
-
Query the Deadlock API to pull relevant statistics. Use bun scripts/get-openapi-info.ts to discover endpoints, then use curl or xh to fetch actual data from https://api.deadlock-api.com.
Common useful endpoints:
GET /v1/analytics/hero-stats -- hero win rates, pick rates, KDA, etc.
GET /v1/analytics/item-stats -- item win rates, pick rates, buy timing
GET /v1/analytics/hero-comb-stats -- hero combo/duo stats
GET /v1/analytics/hero-counter-stats -- hero counter matchups
GET /v1/analytics/hero-synergy-stats -- hero synergy matchups
GET /v1/analytics/item-permutation-stats -- item build combinations
GET /v1/analytics/badge-distribution -- rank distribution
GET /v1/analytics/game-stats -- general game statistics
GET /v1/analytics/kill-death-stats -- kill/death analysis
GET /v1/analytics/player-performance-curve -- performance curves
GET /v1/patches -- patch notes
GET /v1/patches/big-days -- major patch dates
GET /v2/heroes (assets API) -- hero data, abilities, stats
GET /v2/items (assets API) -- item data, stats, descriptions
GET /v2/ranks (assets API) -- rank tiers
Use bun scripts/get-openapi-info.ts <endpoint> to get detailed parameter info before making API calls.
-
Search the codebase for relevant implementation details if the post is about engineering topics. Look at how features work, what algorithms are used, etc.
-
Search the web (via WebSearch/WebFetch) for relevant Deadlock game context, patch notes, community discussions, or background information that would make the post more informed.
-
Check existing blog posts in content/blog/ to avoid duplicating topics and to match the existing tone and quality bar.
The researcher agent should return a structured research brief containing:
- Key data points and statistics (with exact numbers)
- Interesting findings or surprising insights
- Relevant context and background
- Suggested narrative angles
- Any caveats or limitations in the data
Agent prompt template for research:
Research the following topic for a Deadlock API blog post: [TOPIC]
Your goal is to gather concrete data and context. Here's what to do:
1. Use `bun scripts/get-openapi-info.ts` to find relevant API endpoints
2. Use `bun scripts/get-openapi-info.ts <endpoint>` to get parameter details
3. Fetch actual data using curl/xh from https://api.deadlock-api.com
4. Search the web for relevant Deadlock game context
5. Read existing blog posts in content/blog/ for tone reference
Return a structured research brief with:
- Key statistics and data points (exact numbers)
- Interesting findings or surprises
- Background context
- Suggested narrative angles
- Data caveats or limitations
Be thorough. The blog post quality depends on the depth of your research.
Phase 2: Plot Generation (if needed)
If the post would benefit from charts or graphs, spawn a plot agent that:
-
Creates a Python script using matplotlib (preferred for static images) or plotly (for more complex visualizations)
-
Python environment setup: Use uv to manage dependencies in a temporary directory:
mkdir -p /tmp/blog-plots
cd /tmp/blog-plots
uv init --no-workspace 2>/dev/null || true
uv add matplotlib
Run scripts with uv run python script.py from that directory.
-
Uses the research data gathered in Phase 1
-
Generates PNG images saved to public/blog/images/
mkdir -p public/blog/images
-
Styles plots to match the site's dark theme:
- Background:
#0a0a0b (matches the site's dark bg)
- Text color:
#a1a1aa (muted foreground)
- Primary/accent color:
#fa4454 (the site's primary red)
- Secondary colors:
#3b82f6 (blue), #10b981 (emerald), #8b5cf6 (violet), #06b6d4 (cyan), #f59e0b (amber)
- Grid color:
#27272a with low alpha
- Font: sans-serif
- DPI: 150 for crisp rendering
- Figure size: aim for ~800px wide when rendered
-
Keep the Python scripts in public/blog/scripts/ for reproducibility
mkdir -p public/blog/scripts
Agent prompt template for plots:
Create publication-quality plots for a Deadlock API blog post about: [TOPIC]
Data to visualize:
[PASTE RESEARCH DATA HERE]
Requirements:
- Use uv for Python environment management:
cd /tmp/blog-plots && uv init --no-workspace 2>/dev/null || true
uv add matplotlib # add whatever packages you need
Run scripts with: uv run python script.py
- Dark theme matching the website:
- Background: #0a0a0b
- Text: #a1a1aa
- Primary accent: #fa4454
- Secondary colors: #3b82f6, #10b981, #8b5cf6, #06b6d4, #f59e0b
- Grid: #27272a with alpha 0.3
- Save PNGs to [PROJECT_ROOT]/public/blog/images/ at 150 DPI
- Save scripts to [PROJECT_ROOT]/public/blog/scripts/ for reproducibility
- Figure width ~800px when rendered
- Sans-serif font
- Clear axis labels, no chart junk
- Do NOT use titles on the plots (the blog post provides context)
Return the list of generated image paths.
Important: If the post topic does not naturally call for charts (e.g., announcements, opinion pieces, guides), skip this phase entirely. Not every post needs plots.
Phase 3: Content Writing
Now write the actual blog post. You have two options:
Option A: Markdown Post (default, preferred)
Create a file at content/blog/<slug>.md with proper frontmatter.
Author: Get the git user name with git config user.name and format as <name> - Deadlock API Team. For example: Manuel - Deadlock API Team.
---
title: Your Post Title Here
description: A one or two sentence summary for the blog index listing.
date: YYYY-MM-DD
author: Manuel - Deadlock API Team
tags:
- tag1
- tag2
---
Post content here...
Tags: You can use any of the predefined tags (which have colors in the UI) or create new ones. When creating a new tag, you MUST also add its color to both app/routes/blog._index.tsx and app/routes/blog.$slug.tsx in the tagColors object. Choose colors that fit the tag semantically.
Predefined tags:
announcement -- primary red
community -- blue
data -- emerald
guide -- amber
engineering -- violet
infrastructure -- cyan
Example of adding a new tag color (add to BOTH files):
const tagColors: Record<string, string> = {
meta: "bg-rose-500/10 text-rose-400 border-rose-500/20",
patch: "bg-orange-500/10 text-orange-400 border-orange-500/20",
heroes: "bg-indigo-500/10 text-indigo-400 border-indigo-500/20",
items: "bg-teal-500/10 text-teal-400 border-teal-500/20",
};
Markdown capabilities:
- Standard markdown (headings, bold, italic, links, lists, code blocks)
- GitHub-flavored markdown (tables, strikethrough) via remark-gfm
- Syntax-highlighted code blocks via rehype-highlight
- Images from the public directory:

(files in public/blog/images/ are served at /blog/images/)
- Images use raw markdown syntax only (
). No custom styling or wrappers in markdown posts. If you need styled/formatted images (captions, sizing, layout), use a JSX post instead.
Linking to site pages: Link directly to other pages on the site:
[Hero Stats](/heroes)
[Item Stats](/items)
[Leaderboard](/leaderboard)
[Rank Distribution](/badge-distribution)
Data in markdown posts: All statistics and numbers from the analytics API must be written statically into the markdown. The research phase gathers the data; the writing phase bakes it into prose. No runtime API calls from markdown posts.
Option B: JSX Post (only when truly needed)
Use a JSX post ONLY when the post genuinely requires:
- Interactive elements (hover effects, click interactions, toggleable views)
- Animated visualizations
- Live data from the assets API (the ONLY API allowed in JSX posts)
- Complex layouts that markdown can't handle
To create a JSX post:
- Create the component at
app/components/blog/<slug>.tsx
- Register it in
app/lib/blog.ts by adding to the jsxPosts array:
import { lazy } from "react";
const jsxPosts: JsxBlogPost[] = [
{
type: "jsx",
slug: "your-post-slug",
title: "Your Post Title",
description: "Description for listing.",
date: "YYYY-MM-DD",
author: "Manuel - Deadlock API Team",
tags: ["data", "interactive"],
component: lazy(() => import("~/components/blog/your-post-slug")),
},
];
- The component receives no props and is wrapped in the prose container. It can use:
- Tailwind classes (the site uses Tailwind v4)
- shadcn/ui components from
~/components/ui/
- Framer Motion for animations
deadlock_api_client for hero/item data (NO game data API calls)
- React hooks (useState, useEffect, etc.)
- Recharts for interactive charts (already a dependency)
JSX API restrictions:
- Only the assets (
https://api.deadlock-api.com/v1/assets) are allowed at runtime in JSX posts
- Any analytics data must be baked in as static constants (gathered during research)
- The component must be a default export
- Keep bundle size reasonable; lazy-load heavy dependencies
Phase 4: Writing Guidelines
Follow these rules strictly when writing the post content:
Voice and Tone
- Write as an analytical, fact-based source. We are an analytics website; readers come here for data they can trust
- Be direct and specific. Lead with insights, not throat-clearing
- Stay analytical and evidence-driven. Every claim must be backed by data from the research phase
- Avoid subjective opinions or speculation. Instead of "this item feels underrated", say "this item's 54.2% win rate outperforms its 3.1% pick rate, suggesting it is undervalued by most players"
- Use "we" when referring to Deadlock API ("we track", "our data shows")
- Use "you" when addressing the reader
- Tone should be informed and precise, like reading a well-written analysis, not a hype piece
Structure
- Open with a hook: a surprising stat, a question, or a bold claim
- Use ## headings to break up sections (not # -- the page already renders the title as h1)
- Keep paragraphs short (2-4 sentences). Wall-of-text kills readability
- End with a takeaway or call to action (link to relevant page on the site, invite discussion on Discord)
What to AVOID (the humanizer will catch these, but avoid them upfront)
- Em dashes AND double dashes ("--"). Both are strong AI writing tells. Use commas, parentheses, periods, or split into two sentences instead. Never use "--" or the em dash character in prose.
- Excessive bullet points. Prefer flowing prose with data woven in
- AI vocabulary: "delve", "crucial", "landscape", "foster", "testament", "Additionally", "Furthermore", "It's worth noting"
- Promotional puffery: "stunning", "groundbreaking", "vibrant"
- Vague attributions: "Experts say", "Many players believe"
- Rule of three patterns: "X, Y, and Z" repeated throughout
- Starting paragraphs with "When it comes to..."
- Formulaic "challenges and opportunities" sections
- Sycophantic conclusions ("We hope you found this helpful!")
- Title case in subheadings (use sentence case: "How items scale with game length" not "How Items Scale With Game Length")
Data Presentation
- Always cite specific numbers: "Haze sits at 54.2% win rate" not "Haze has a high win rate"
- Round percentages to one decimal place
- Provide context for numbers: "54.2% win rate, the highest among all heroes this patch"
- When showing changes, use concrete deltas: "up 3.1 percentage points from last patch"
- Link to the relevant page so readers can explore themselves
Length
- Aim for 800-1500 words for data-driven posts
- 400-800 words for announcements or short guides
- Let the content dictate length. Don't pad, don't truncate prematurely
Phase 5: Humanizer Pass
After writing the post, you MUST run the humanizer skill over it. Do this by invoking:
Skill: "humanizer"
The humanizer will:
- Scan for AI-generated writing patterns
- Rewrite problematic sections
- Do a final anti-AI audit
- Produce the final humanized version
After the humanizer finishes, apply its changes to the blog post file.
Phase 6: Present Draft for Review
After the humanizer pass, do a final check:
- Frontmatter is correct: title, description, date (use today's date: check with
date +%Y-%m-%d), author (first name only + "- Deadlock API Team", no last names), tags
- Links work: all internal links point to valid routes (
/heroes, /items, /leaderboard, /badge-distribution, /blog)
- No dashes as punctuation: search the file for
-- (in prose, not code) and the em dash character. Both are AI writing tells. Replace with commas, periods, or restructured sentences
- Images exist: if images are referenced, verify the files exist in
public/blog/images/
- Tag colors added: if new tags were used, verify they were added to both
blog._index.tsx and blog.$slug.tsx
- Read through once: read the final post end-to-end for flow and coherence
- Prerendering: the post will auto-prerender via
react-router.config.ts which calls getAllSlugs(), no manual config needed
If this is a JSX post, also verify:
- The component is registered in
app/lib/blog.ts
- The component file exists and exports a default component
- No game data API imports (only assets API allowed)
Then present the draft to the user. Summarize what was written, list the files created/modified, and wait for feedback. The user will either:
- Request changes (make them and re-present)
- Ask you to commit (only then use git)
NEVER commit or push without explicit user instruction.
Example Invocation
User: "Write a blog post about which heroes are strongest in the current patch, use some charts"
You would:
- Spawn a research agent to pull hero stats from
/v1/analytics/hero-stats, compare to previous patches, get hero names from assets API
- Spawn a plot agent to create bar charts of win rates, pick rates, maybe a scatter plot of pick rate vs win rate (using uv + matplotlib in /tmp/blog-plots)
- Write the markdown post with the data woven into engaging prose, images referenced as
/blog/images/filename.png
- Run the humanizer
- Present the draft to the user for review
- Wait for feedback or commit instruction
File Locations Reference
- Markdown posts:
content/blog/<slug>.md
- JSX post components:
app/components/blog/<slug>.tsx
- JSX post registry:
app/lib/blog.ts (the jsxPosts array)
- Plot images:
public/blog/images/<name>.png
- Plot scripts:
public/blog/scripts/<name>.py
- Tag colors:
app/routes/blog._index.tsx AND app/routes/blog.$slug.tsx (both tagColors objects)
- Blog routes:
app/routes/blog.tsx, app/routes/blog._index.tsx, app/routes/blog.$slug.tsx
- API info script:
scripts/get-openapi-info.ts
- Site constants:
app/lib/constants.ts