Compare commits
6 Commits
ff22696b49
...
79c5a1f06e
| Author | SHA1 | Date | |
|---|---|---|---|
| 79c5a1f06e | |||
| 6ae23fb783 | |||
| aee0035a4f | |||
| 7f04b1befd | |||
| f6819b42bd | |||
| 650164e412 |
@@ -7,6 +7,7 @@ import { MDXRemote } from "next-mdx-remote/rsc";
|
|||||||
import rehypePrettyCode from "rehype-pretty-code";
|
import rehypePrettyCode from "rehype-pretty-code";
|
||||||
import rehypeSlug from "rehype-slug";
|
import rehypeSlug from "rehype-slug";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import Video from "@/components/video";
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: Promise<{ slug: string }>;
|
params: Promise<{ slug: string }>;
|
||||||
@@ -63,6 +64,7 @@ export default async function Post({ params }: PageProps) {
|
|||||||
<div className="prose lg:prose-lg max-w-none">
|
<div className="prose lg:prose-lg max-w-none">
|
||||||
<MDXRemote
|
<MDXRemote
|
||||||
source={postData.content}
|
source={postData.content}
|
||||||
|
components={{ Video }}
|
||||||
options={{
|
options={{
|
||||||
mdxOptions: {
|
mdxOptions: {
|
||||||
remarkPlugins: [remarkGfm],
|
remarkPlugins: [remarkGfm],
|
||||||
|
|||||||
28
components/video.tsx
Normal file
28
components/video.tsx
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
interface VideoProps {
|
||||||
|
src: string;
|
||||||
|
caption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Video({ src, caption }: VideoProps) {
|
||||||
|
return (
|
||||||
|
<figure className="my-4">
|
||||||
|
<div className="rounded-lg border border-white/30 bg-white/10 backdrop-blur-sm overflow-hidden shadow-md">
|
||||||
|
<video
|
||||||
|
src={src}
|
||||||
|
className="w-full"
|
||||||
|
aria-label={caption ?? src}
|
||||||
|
preload="metadata"
|
||||||
|
controls
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{caption && (
|
||||||
|
<figcaption className="mt-2 text-center text-sm text-gray-500 italic">
|
||||||
|
{caption}
|
||||||
|
</figcaption>
|
||||||
|
)}
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
142
docs/superpowers/plans/2026-03-31-video-component.md
Normal file
142
docs/superpowers/plans/2026-03-31-video-component.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Video Component Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Create a reusable `<Video>` MDX component with sensible defaults and an optional caption.
|
||||||
|
|
||||||
|
**Architecture:** A single React component in `components/video.tsx` registered in the MDXRemote `components` prop in `app/posts/[slug]/page.tsx`. No per-post imports needed.
|
||||||
|
|
||||||
|
**Tech Stack:** Next.js 15, React 19, Tailwind v4, next-mdx-remote
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Create Video component
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `components/video.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the component**
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
interface VideoProps {
|
||||||
|
src: string;
|
||||||
|
caption?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Video({ src, caption }: VideoProps) {
|
||||||
|
return (
|
||||||
|
<figure className="my-4">
|
||||||
|
<div className="rounded-lg border border-white/30 bg-white/10 backdrop-blur-sm overflow-hidden shadow-md">
|
||||||
|
<video
|
||||||
|
src={src}
|
||||||
|
width="100%"
|
||||||
|
controls
|
||||||
|
loop
|
||||||
|
muted
|
||||||
|
playsInline
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{caption && (
|
||||||
|
<figcaption className="mt-2 text-center text-sm text-gray-500 italic">
|
||||||
|
{caption}
|
||||||
|
</figcaption>
|
||||||
|
)}
|
||||||
|
</figure>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add components/video.tsx
|
||||||
|
git commit -m "feat: add Video component"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Register Video in MDXRemote
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `app/posts/[slug]/page.tsx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Add import at top of file**
|
||||||
|
|
||||||
|
After the existing imports, add:
|
||||||
|
```tsx
|
||||||
|
import Video from "@/components/video";
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Add components prop to MDXRemote**
|
||||||
|
|
||||||
|
Find the `<MDXRemote ... />` call (around line 64) and add a `components` prop:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<MDXRemote
|
||||||
|
source={postData.content}
|
||||||
|
components={{ Video }}
|
||||||
|
options={{
|
||||||
|
mdxOptions: {
|
||||||
|
remarkPlugins: [remarkGfm],
|
||||||
|
rehypePlugins: [
|
||||||
|
rehypeSlug,
|
||||||
|
[
|
||||||
|
rehypePrettyCode,
|
||||||
|
{
|
||||||
|
theme: "github-dark-dimmed",
|
||||||
|
keepBackground: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Verify dev server compiles without errors**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: server starts, no TypeScript errors.
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add app/posts/[slug]/page.tsx
|
||||||
|
git commit -m "feat: register Video component in MDXRemote"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: Use component in a post
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `posts/my-2024-and-2025-roadmap.mdx`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace raw video tag with component**
|
||||||
|
|
||||||
|
Find (around line 54):
|
||||||
|
```html
|
||||||
|
<video width="100%" height="auto" controls loop muted playsInline>
|
||||||
|
<source src="/posts/rts.mp4" type="video/mp4" />
|
||||||
|
Your browser does not support the video tag.
|
||||||
|
</video>
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace with:
|
||||||
|
```mdx
|
||||||
|
<Video src="/posts/rts.mp4" caption="My failed RTS engine attempt" />
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Verify in browser**
|
||||||
|
|
||||||
|
Open the post in the dev server and confirm the video renders with styling and caption.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add posts/my-2024-and-2025-roadmap.mdx
|
||||||
|
git commit -m "chore: use Video component in roadmap post"
|
||||||
|
```
|
||||||
30
docs/superpowers/specs/2026-03-31-video-component-design.md
Normal file
30
docs/superpowers/specs/2026-03-31-video-component-design.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Video Component Design
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
A reusable `<Video>` MDX component for embedding videos in blog posts with sensible defaults and optional caption.
|
||||||
|
|
||||||
|
## Component
|
||||||
|
|
||||||
|
**File:** `components/video.tsx`
|
||||||
|
|
||||||
|
**Props:**
|
||||||
|
- `src: string` — video file path (required)
|
||||||
|
- `caption?: string` — text displayed below video (optional)
|
||||||
|
|
||||||
|
**Video defaults:** `controls loop muted playsInline width="100%"`
|
||||||
|
|
||||||
|
**Styling:**
|
||||||
|
- Container: `rounded-lg border border-white/30 bg-white/10 backdrop-blur-sm overflow-hidden shadow-md`
|
||||||
|
- Caption: small, muted, italic, centered
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
Register `Video` in `page.tsx` MDXRemote `components` prop so it's available in all MDX posts without per-post imports.
|
||||||
|
|
||||||
|
## Usage in MDX
|
||||||
|
|
||||||
|
```mdx
|
||||||
|
<Video src="/posts/rts.mp4" />
|
||||||
|
<Video src="/posts/rts.mp4" caption="My failed RTS attempt" />
|
||||||
|
```
|
||||||
@@ -25,6 +25,8 @@ But that wasn't all I did in the first month of 2024. I had this crazy idea to c
|
|||||||
|
|
||||||
Next, I wrote a **Minesweeper** game in C++ using Raylib. That was pretty fun! The code, however, isn't really good—it's not idiomatic C++, more like C with classes—but the point of the project was just to have fun. That’s why I’m not going to link the repo. However, if someone is interested, they can easily find it on my GitHub.
|
Next, I wrote a **Minesweeper** game in C++ using Raylib. That was pretty fun! The code, however, isn't really good—it's not idiomatic C++, more like C with classes—but the point of the project was just to have fun. That’s why I’m not going to link the repo. However, if someone is interested, they can easily find it on my GitHub.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## February - Small but Fun Projects
|
## February - Small but Fun Projects
|
||||||
|
|
||||||
February, the shortest month of the year! I hopped from project to project, but some were actually finished and published.
|
February, the shortest month of the year! I hopped from project to project, but some were actually finished and published.
|
||||||
@@ -37,18 +39,26 @@ You might ask, _Why use Rust for that instead of JavaScript?_ Great question! Th
|
|||||||
|
|
||||||
Next, I started working on a **Missile Commander clone**—a recreation of the old Atari game **Missile Command**. I actually managed to replicate the core gameplay, but since I’m terrible at designing games and coming up with new mechanics, levels, and features, I abandoned it pretty quickly. I even wrote a **level editor** for it! The game and editor were both written in Rust using **macroquad**, as I wanted to export it to the web. Unfortunately, I ran into some errors when trying to export and gave up.
|
Next, I started working on a **Missile Commander clone**—a recreation of the old Atari game **Missile Command**. I actually managed to replicate the core gameplay, but since I’m terrible at designing games and coming up with new mechanics, levels, and features, I abandoned it pretty quickly. I even wrote a **level editor** for it! The game and editor were both written in Rust using **macroquad**, as I wanted to export it to the web. Unfortunately, I ran into some errors when trying to export and gave up.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## March - A Sokoban Game in Java
|
## March - A Sokoban Game in Java
|
||||||
|
|
||||||
In March, I only worked on one toy project—a **Sokoban game in Java** using Raylib. Why Java? No idea. I guess I just wanted to refresh my skills in the language.
|
In March, I only worked on one toy project—a **Sokoban game in Java** using Raylib. Why Java? No idea. I guess I just wanted to refresh my skills in the language.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
## April - The RTS Struggle
|
## April - The RTS Struggle
|
||||||
|
|
||||||
In April, I really wanted to create an **RTS game**. Unfortunately, I didn’t succeed. Nevertheless, I made an attempt at creating an **RTS engine in Rust** using the _comfy_ crate. Later, I tried moving to Bevy, but pathfinding defeated me. I haven't given up on the idea completely, though—I may go back to it in the future, because I love RTS and strategy games so much.
|
In April, I really wanted to create an **RTS game**. Unfortunately, I didn’t succeed. Nevertheless, I made an attempt at creating an **RTS engine in Rust** using the _comfy_ crate. Later, I tried moving to Bevy, but pathfinding defeated me. I haven't given up on the idea completely, though—I may go back to it in the future, because I love RTS and strategy games so much.
|
||||||
|
|
||||||
|
<Video src="/posts/rts.mp4" caption="My failed RTS engine attempt" />
|
||||||
|
|
||||||
While researching RTS mechanics, I wrote my own **quadtree** in Rust and visualized it using Raylib. That was pretty fun and easy.
|
While researching RTS mechanics, I wrote my own **quadtree** in Rust and visualized it using Raylib. That was pretty fun and easy.
|
||||||
|
|
||||||
I also got into **ray tracing** and, thanks to _Ray Tracing in One Weekend_, I wrote my own **software ray tracer** in Rust.
|
I also got into **ray tracing** and, thanks to _Ray Tracing in One Weekend_, I wrote my own **software ray tracer** in Rust.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
Another Rust project from April was an **Otodom scraper** with a frontend in React. Otodom is a website listing real estate for sale or rent, and since I was house-hunting, I needed a better way to filter available listings. So I wrote a simple service using _Axum_ that scrapes the data every ten minutes, allowing me to easily filter through listings.
|
Another Rust project from April was an **Otodom scraper** with a frontend in React. Otodom is a website listing real estate for sale or rent, and since I was house-hunting, I needed a better way to filter available listings. So I wrote a simple service using _Axum_ that scrapes the data every ten minutes, allowing me to easily filter through listings.
|
||||||
|
|
||||||
Last but not least, I created **better_notepad**, a Notepad-like app because I was annoyed that the default Windows Notepad didn’t close tabs when the app was closed. I wrote mine in C++ with wxWidgets. Later, I learned that you can actually turn that behavior off in the Notepad settings... oops.
|
Last but not least, I created **better_notepad**, a Notepad-like app because I was annoyed that the default Windows Notepad didn’t close tabs when the app was closed. I wrote mine in C++ with wxWidgets. Later, I learned that you can actually turn that behavior off in the Notepad settings... oops.
|
||||||
|
|||||||
Reference in New Issue
Block a user