Post Editor — Authoring Conventions
BlackOps blog posts are Markdown plus a handful of opt-in extensions: captioned figures, browser-chrome screenshots, looping videos, React component shortcodes, and a raw-HTML escape hatch. Everything below works in any post body.
Image Captions
Any image can have a caption. The renderer wraps the result in a<figure> with a<figcaption>. Two equivalent author conventions — pick whichever you prefer:
1. Markdown title attribute
Standard Markdown. The "title" argument becomes the caption.
2. Italic on the next line

*Caption text appears below the image*Image and italic must be in the same paragraph (no blank line between).
[browser] Images
Prefix the alt text with [browser] to wrap a screenshot in a fake browser window — traffic-light dots, an address bar, drop shadow, click-to-zoom lightbox. Use it for hero shots and full-page captures where you want the reader to read the image as "a real product page," not just a UI fragment.
![[browser] BlackOps dashboard at 1440px](/images/screenshots/dashboard.png)
*The dashboard with three reservoirs and recent activity.*- The
[browser]token is stripped from the alt before render — screen readers hear just the descriptive part. - Captions work the same way as standard images (title or italic-after).
- Address bar defaults to
blackopscenter.com. The chrome is rendered byBrowserWindowinsrc/components/ui/browser-window.tsx.
Inline Video
Drop a raw HTML <video> tag anywhere in the post. The renderer detects it, parses the attributes, and renders a proper player with view tracking.
Standard player (with controls)
<video controls src="https://example.com/demo.mp4"></video>Looping autoplay (silent demo)
<video autoplay loop muted playsinline src="https://example.com/demo.mp4"></video>Browsers require muted for autoplay. The renderer adds it automatically when autoplay is present, and hides the controls when autoplay is on (unless you also pass controls).
Recognized attributes
src(required)autoplay— implies muted, hides controls by defaultloop— replay on endmuted— silentcontrols— show controls (forces them on even with autoplay)
Player is constrained to max-height: 600px and full width with rounded corners. View tracking fires once on first play.
Component Shortcodes
Drop a self-closing PascalCase tag and the renderer swaps in a full Remotion or React component. Tags must start with a capital letter and use the self-closing /> form.
<ProblemSolutionComparison/>
<VoiceRecordingDemo/>
<GitHubIssueCreation/>
<IssueTypeSelector/>
<BrainDiagram/>- ProblemSolutionComparison — Remotion napkin-sketch loop comparing a problem to its solution. 10s, 800×600.
- VoiceRecordingDemo — Simple voice-recording animation loop.
- GitHubIssueCreation — Animated "creating a GitHub issue" loop.
- IssueTypeSelector — Generic AI-processing loop animation.
- BrainDiagram — Static diagram of the BlackOps brain pipeline.
Adding a new shortcode? Edit the switch (componentName) block in src/components/blog/markdown-content.tsx.
Raw HTML — richembed Blocks
For embeds Markdown can't express — Twitter cards, custom CSS, third-party widgets — use a richembed fenced block. Everything inside renders as raw HTML, isolated in its own segment.
```richembed
<div class="my-fancy-embed">
<iframe src="https://example.com/widget" width="100%" height="400"></iframe>
</div>
```The block is parsed by parseRichEmbeds() in src/lib/parseRichEmbeds.ts and rendered via the RichEmbed component. It has access to the page CSS — write your styles inline or in a <style> block within the embed.
Standard Markdown
Everything else is plain GitHub-Flavored Markdown via remark-gfm:
- Headings, paragraphs, lists, blockquotes
- Tables (GFM)
- Strikethrough, task lists, autolinks (GFM)
- Code fences with syntax highlighting (Prism / oneDark theme)
Things to Know
- Component shortcodes must be self-closing (
<Foo/>) and start with an uppercase letter.<Foo>...</Foo>won't parse. - An image with both
[browser]and a title attribute will use the browser chrome path; the title is treated as the caption only when no[browser]tag is present. - Italic-after-image captions only work when the image and italic are in the same paragraph. A blank line between them breaks the pairing.
richembedblocks share the page's CSS scope. Avoid global selectors likebodyor*.