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

![alt text](/images/screenshots/example.png "Caption text appears below the image")

Standard Markdown. The "title" argument becomes the caption.

2. Italic on the next line

![alt text](/images/screenshots/example.png)
*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 by BrowserWindow in src/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 default
  • loop — replay on end
  • muted — silent
  • controls — 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.
  • richembed blocks share the page's CSS scope. Avoid global selectors like body or *.

Related