From dad9efb76ca66f4078004f102fc8449082b981f7 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 01:16:36 +0000
Subject: [PATCH 1/3] Initial plan
From 8b6959d42c011438e391af86b4db4e78ad67e78c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 01:39:55 +0000
Subject: [PATCH 2/3] Add multi-page Astro site with themes and GitHub Pages
deployment
Co-authored-by: cywf <100837335+cywf@users.noreply.github.com>
---
.github/ISSUE_TEMPLATE/documentation.md | 23 +++
.github/ISSUE_TEMPLATE/script-tool.md | 44 +++++
.github/workflows/pages.yml | 86 +++++++++
.gitignore | 13 ++
README.md | 47 +++++
site/astro.config.mjs | 16 ++
site/package.json | 31 ++++
site/public/data/discussions.json | 1 +
site/public/data/projects.json | 4 +
site/public/data/scripts.json | 37 ++++
site/public/data/stats.json | 7 +
site/public/favicon.svg | 3 +
site/scripts/fetch_discussions.ts | 113 ++++++++++++
site/scripts/fetch_projects.ts | 215 ++++++++++++++++++++++
site/scripts/fetch_repo_data.ts | 103 +++++++++++
site/scripts/scan_scripts.ts | 131 ++++++++++++++
site/scripts/tsconfig.json | 16 ++
site/src/components/Breadcrumbs.astro | 25 +++
site/src/components/DevelopmentBoard.tsx | 141 +++++++++++++++
site/src/components/Discussions.tsx | 184 +++++++++++++++++++
site/src/components/MermaidViewer.tsx | 151 ++++++++++++++++
site/src/components/Statistics.tsx | 219 +++++++++++++++++++++++
site/src/components/ThemeSwitcher.tsx | 128 +++++++++++++
site/src/env.d.ts | 1 +
site/src/layouts/Layout.astro | 167 +++++++++++++++++
site/src/pages/404.astro | 39 ++++
site/src/pages/create-issue.astro | 137 ++++++++++++++
site/src/pages/development-board.astro | 21 +++
site/src/pages/discussions.astro | 21 +++
site/src/pages/docs/[...slug].astro | 101 +++++++++++
site/src/pages/docs/index.astro | 109 +++++++++++
site/src/pages/index.astro | 125 +++++++++++++
site/src/pages/statistics.astro | 11 ++
site/src/pages/visualizer.astro | 45 +++++
site/src/styles/global.css | 68 +++++++
site/tailwind.config.mjs | 70 ++++++++
site/tsconfig.json | 8 +
37 files changed, 2661 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/documentation.md
create mode 100644 .github/ISSUE_TEMPLATE/script-tool.md
create mode 100644 .github/workflows/pages.yml
create mode 100644 site/astro.config.mjs
create mode 100644 site/package.json
create mode 100644 site/public/data/discussions.json
create mode 100644 site/public/data/projects.json
create mode 100644 site/public/data/scripts.json
create mode 100644 site/public/data/stats.json
create mode 100644 site/public/favicon.svg
create mode 100644 site/scripts/fetch_discussions.ts
create mode 100644 site/scripts/fetch_projects.ts
create mode 100644 site/scripts/fetch_repo_data.ts
create mode 100644 site/scripts/scan_scripts.ts
create mode 100644 site/scripts/tsconfig.json
create mode 100644 site/src/components/Breadcrumbs.astro
create mode 100644 site/src/components/DevelopmentBoard.tsx
create mode 100644 site/src/components/Discussions.tsx
create mode 100644 site/src/components/MermaidViewer.tsx
create mode 100644 site/src/components/Statistics.tsx
create mode 100644 site/src/components/ThemeSwitcher.tsx
create mode 100644 site/src/env.d.ts
create mode 100644 site/src/layouts/Layout.astro
create mode 100644 site/src/pages/404.astro
create mode 100644 site/src/pages/create-issue.astro
create mode 100644 site/src/pages/development-board.astro
create mode 100644 site/src/pages/discussions.astro
create mode 100644 site/src/pages/docs/[...slug].astro
create mode 100644 site/src/pages/docs/index.astro
create mode 100644 site/src/pages/index.astro
create mode 100644 site/src/pages/statistics.astro
create mode 100644 site/src/pages/visualizer.astro
create mode 100644 site/src/styles/global.css
create mode 100644 site/tailwind.config.mjs
create mode 100644 site/tsconfig.json
diff --git a/.github/ISSUE_TEMPLATE/documentation.md b/.github/ISSUE_TEMPLATE/documentation.md
new file mode 100644
index 0000000..6cb0886
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/documentation.md
@@ -0,0 +1,23 @@
+---
+name: Documentation Update
+about: Suggest improvements or corrections to documentation
+title: 'docs: '
+labels: 'documentation'
+assignees: ''
+
+---
+
+**Which documentation needs updating?**
+Please specify which document(s) need to be updated (e.g., README.md, docs/ctf-overview.md)
+
+**What needs to be changed?**
+A clear and concise description of what should be updated, corrected, or added.
+
+**Why is this change needed?**
+Explain why this documentation change would be helpful.
+
+**Proposed content (optional)**
+If you have specific wording or content to suggest, please include it here.
+
+**Additional context**
+Add any other context, screenshots, or links about the documentation update.
diff --git a/.github/ISSUE_TEMPLATE/script-tool.md b/.github/ISSUE_TEMPLATE/script-tool.md
new file mode 100644
index 0000000..172143c
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/script-tool.md
@@ -0,0 +1,44 @@
+---
+name: Script/Tool Contribution
+about: Share a script or tool for CTF challenges
+title: 'tool: '
+labels: 'script,tool'
+assignees: ''
+
+---
+
+**Script/Tool Name**
+What would you like to call this script or tool?
+
+**Purpose**
+What CTF challenge category or problem does this script/tool address?
+- [ ] Enumeration
+- [ ] Exploitation
+- [ ] Cryptography
+- [ ] Forensics
+- [ ] Web Exploitation
+- [ ] Reverse Engineering
+- [ ] Other (please specify)
+
+**Description**
+Provide a clear description of what the script/tool does and how it works.
+
+**Usage Example**
+Show an example of how to use the script/tool:
+```bash
+# Example usage
+./your-script.sh [arguments]
+```
+
+**Dependencies**
+List any dependencies or requirements:
+- Language/Runtime: (e.g., Python 3.x, Bash, etc.)
+- External tools: (e.g., nmap, metasploit, etc.)
+- Libraries: (e.g., pip packages, etc.)
+
+**Script/Tool Location**
+Where should this be added in the repository?
+- Suggested path: `scripts/[category]/your-script.ext`
+
+**Additional Notes**
+Any additional context, links, or references that would be helpful.
diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 0000000..9c665a4
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,86 @@
+name: Deploy to GitHub Pages
+
+on:
+ push:
+ branches: [main]
+ workflow_dispatch:
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+concurrency:
+ group: "pages"
+ cancel-in-progress: false
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+ cache: 'npm'
+ cache-dependency-path: site/package-lock.json
+
+ - name: Install dependencies
+ working-directory: ./site
+ run: npm ci
+
+ - name: Fetch repository data
+ working-directory: ./site
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "Fetching repository statistics..."
+ node --loader tsx scripts/fetch_repo_data.ts || npm exec tsx scripts/fetch_repo_data.ts
+
+ - name: Fetch discussions
+ working-directory: ./site
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "Fetching discussions..."
+ node --loader tsx scripts/fetch_discussions.ts || npm exec tsx scripts/fetch_discussions.ts
+
+ - name: Fetch projects
+ working-directory: ./site
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ run: |
+ echo "Fetching project data..."
+ node --loader tsx scripts/fetch_projects.ts || npm exec tsx scripts/fetch_projects.ts
+
+ - name: Scan scripts
+ working-directory: ./site
+ run: |
+ echo "Scanning scripts directory..."
+ node --loader tsx scripts/scan_scripts.ts || npm exec tsx scripts/scan_scripts.ts
+
+ - name: Build site
+ working-directory: ./site
+ run: npm run build
+
+ - name: Setup Pages
+ uses: actions/configure-pages@v5
+
+ - name: Upload artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: ./site/dist
+
+ deploy:
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy to GitHub Pages
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/.gitignore b/.gitignore
index 2ba6e74..eccdb97 100644
--- a/.gitignore
+++ b/.gitignore
@@ -74,3 +74,16 @@ secrets.txt
# CTF-specific
writeups/drafts/
notes/private/
+
+# Node.js
+node_modules/
+package-lock.json
+
+# Astro
+.astro/
+dist/
+
+# Site build artifacts
+site/dist/
+site/.astro/
+site/node_modules/
diff --git a/README.md b/README.md
index 7ca295b..27e5f00 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
# Welcome to the CTF-Kit Repository
+π **Live Site**: [https://cywf.github.io/ctf-kit/](https://cywf.github.io/ctf-kit/)
+
## Introduction
Welcome to the CTF-Kit repository! This project is designed to be a collaborative and evolving resource for anyone interested in participating in Capture The Flag (CTF) competitions. Whether you're a seasoned veteran or a newcomer to the world of CTFs, this repository aims to provide you with the tools, resources, and knowledge you need to succeed.
@@ -69,3 +71,48 @@ Your input is valuable! If you have ideas for new features, tools, or improvemen
## Conclusion
Thank you for being a part of the CTF-Kit community! Whether youβre here to learn, contribute, or both, weβre excited to have you with us. This repository is a living project that grows and improves with each contribution, so donβt hesitate to get involved. Fork the repo, create your branch, and start tackling those CTFs!
+
+## Contributing to the Documentation
+
+### Adding Documentation
+Documentation files are stored in the `/docs/` directory and are automatically rendered on the [live site](https://cywf.github.io/ctf-kit/docs). To add or update documentation:
+
+1. Create or edit markdown files in `/docs/` or subdirectories
+2. Use clear headings and formatting
+3. Submit a pull request with your changes
+4. The site will automatically update when changes are merged to main
+
+### Adding Mermaid Diagrams
+Visual diagrams help explain complex concepts and workflows. To add a diagram:
+
+1. Create a `.mmd` file in the `/mermaid/` directory (create this directory if it doesn't exist)
+2. Write your diagram using [Mermaid syntax](https://mermaid.js.org/intro/)
+3. Commit and push your changes
+4. The CI workflow will process the diagram and make it available on the [visualizer page](https://cywf.github.io/ctf-kit/visualizer)
+
+Example diagram structure:
+```mermaid
+graph TD
+ A[Start] --> B{Is it?}
+ B -->|Yes| C[OK]
+ B -->|No| D[End]
+```
+
+## How the Site Works
+
+### CI/CD Pipeline
+Our GitHub Actions workflow automatically:
+1. Fetches live repository statistics (stars, forks, languages, commits)
+2. Retrieves discussions from GitHub Discussions
+3. Syncs project board items or falls back to issues
+4. Indexes scripts with descriptions from comments
+5. Builds and deploys the site to GitHub Pages
+
+### Data Snapshots
+The site uses JSON data snapshots created during the CI build:
+- `stats.json` - Repository metrics and activity
+- `discussions.json` - Latest community discussions
+- `projects.json` - Development board status
+- `scripts.json` - Available scripts with descriptions
+
+These snapshots are generated server-side and consumed client-side for optimal performance.
diff --git a/site/astro.config.mjs b/site/astro.config.mjs
new file mode 100644
index 0000000..97f035c
--- /dev/null
+++ b/site/astro.config.mjs
@@ -0,0 +1,16 @@
+import { defineConfig } from 'astro/config';
+import react from '@astrojs/react';
+import tailwind from '@astrojs/tailwind';
+
+// https://astro.build/config
+export default defineConfig({
+ site: 'https://cywf.github.io',
+ base: '/ctf-kit',
+ integrations: [
+ react(),
+ tailwind({
+ applyBaseStyles: false,
+ }),
+ ],
+ output: 'static',
+});
diff --git a/site/package.json b/site/package.json
new file mode 100644
index 0000000..1617018
--- /dev/null
+++ b/site/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "ctf-kit-site",
+ "type": "module",
+ "version": "0.0.1",
+ "scripts": {
+ "dev": "astro dev",
+ "build": "astro check && astro build",
+ "preview": "astro preview",
+ "typecheck": "astro check"
+ },
+ "dependencies": {
+ "@astrojs/check": "^0.9.4",
+ "@astrojs/react": "^3.6.2",
+ "@astrojs/tailwind": "^5.1.2",
+ "@types/react": "^18.3.12",
+ "@types/react-dom": "^18.3.1",
+ "astro": "^4.16.12",
+ "chart.js": "^4.4.6",
+ "daisyui": "^4.12.14",
+ "mermaid": "^11.4.0",
+ "react": "^18.3.1",
+ "react-chartjs-2": "^5.2.0",
+ "react-dom": "^18.3.1",
+ "tailwindcss": "^3.4.15",
+ "typescript": "^5.6.3"
+ },
+ "devDependencies": {
+ "@types/node": "^22.9.0",
+ "tsx": "^4.19.2"
+ }
+}
diff --git a/site/public/data/discussions.json b/site/public/data/discussions.json
new file mode 100644
index 0000000..0637a08
--- /dev/null
+++ b/site/public/data/discussions.json
@@ -0,0 +1 @@
+[]
\ No newline at end of file
diff --git a/site/public/data/projects.json b/site/public/data/projects.json
new file mode 100644
index 0000000..addfcd3
--- /dev/null
+++ b/site/public/data/projects.json
@@ -0,0 +1,4 @@
+{
+ "name": "Project Board",
+ "items": []
+}
\ No newline at end of file
diff --git a/site/public/data/scripts.json b/site/public/data/scripts.json
new file mode 100644
index 0000000..bc856dd
--- /dev/null
+++ b/site/public/data/scripts.json
@@ -0,0 +1,37 @@
+[
+ {
+ "path": "README.md",
+ "name": "README.md",
+ "description": "Script: README.md"
+ },
+ {
+ "path": "Networking/README.md",
+ "name": "README.md",
+ "description": "Script: README.md"
+ },
+ {
+ "path": "Networking/advanced_nmap_scan.sh",
+ "name": "advanced_nmap_scan.sh",
+ "description": "############################################################################"
+ },
+ {
+ "path": "Networking/basic_nmap_scan.sh",
+ "name": "basic_nmap_scan.sh",
+ "description": "############################################################################"
+ },
+ {
+ "path": "Enumeration/README.md",
+ "name": "README.md",
+ "description": "Script: README.md"
+ },
+ {
+ "path": "Enumeration/smb_share_enum.sh",
+ "name": "smb_share_enum.sh",
+ "description": "############################################################################"
+ },
+ {
+ "path": "Enumeration/web_server_enum.sh",
+ "name": "web_server_enum.sh",
+ "description": "############################################################################"
+ }
+]
\ No newline at end of file
diff --git a/site/public/data/stats.json b/site/public/data/stats.json
new file mode 100644
index 0000000..5d4b5da
--- /dev/null
+++ b/site/public/data/stats.json
@@ -0,0 +1,7 @@
+{
+ "stars": 0,
+ "forks": 0,
+ "watchers": 0,
+ "languages": {},
+ "commitActivity": []
+}
\ No newline at end of file
diff --git a/site/public/favicon.svg b/site/public/favicon.svg
new file mode 100644
index 0000000..56b0182
--- /dev/null
+++ b/site/public/favicon.svg
@@ -0,0 +1,3 @@
+
+
+
diff --git a/site/scripts/fetch_discussions.ts b/site/scripts/fetch_discussions.ts
new file mode 100644
index 0000000..26a2f4c
--- /dev/null
+++ b/site/scripts/fetch_discussions.ts
@@ -0,0 +1,113 @@
+#!/usr/bin/env node
+
+import { writeFile, mkdir } from 'fs/promises';
+import { join } from 'path';
+
+const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
+const REPO_OWNER = 'cywf';
+const REPO_NAME = 'ctf-kit';
+
+interface Discussion {
+ title: string;
+ author: string;
+ url: string;
+ createdAt: string;
+ category: string;
+}
+
+async function fetchGraphQL(query: string) {
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'CTF-Kit-Site',
+ };
+
+ if (GITHUB_TOKEN) {
+ headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
+ }
+
+ const response = await fetch('https://api.github.com/graphql', {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ query }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`GitHub GraphQL error: ${response.status} ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+async function fetchDiscussions(): Promise {
+ console.log('Fetching discussions...');
+
+ const query = `
+ query {
+ repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") {
+ discussions(first: 25, orderBy: {field: CREATED_AT, direction: DESC}) {
+ nodes {
+ title
+ url
+ createdAt
+ author {
+ login
+ }
+ category {
+ name
+ }
+ }
+ }
+ }
+ }
+ `;
+
+ try {
+ const result = await fetchGraphQL(query);
+
+ if (result.errors) {
+ console.warn('GraphQL errors:', result.errors);
+ }
+
+ const discussions: Discussion[] = result.data?.repository?.discussions?.nodes?.map((node: any) => ({
+ title: node.title,
+ author: node.author?.login || 'Unknown',
+ url: node.url,
+ createdAt: node.createdAt,
+ category: node.category?.name || 'General',
+ })) || [];
+
+ console.log(`β Fetched ${discussions.length} discussions`);
+ return discussions;
+ } catch (error) {
+ console.warn('Failed to fetch discussions:', error);
+ return [];
+ }
+}
+
+async function main() {
+ try {
+ const discussions = await fetchDiscussions();
+
+ // Ensure directory exists
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+
+ // Write to file
+ const outputPath = join(dataDir, 'discussions.json');
+ await writeFile(outputPath, JSON.stringify(discussions, null, 2));
+
+ console.log(`β Discussions written to ${outputPath}`);
+ } catch (error) {
+ console.error('Error fetching discussions:', error);
+
+ // Write empty array as fallback
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+ const outputPath = join(dataDir, 'discussions.json');
+ await writeFile(outputPath, JSON.stringify([], null, 2));
+
+ console.log('β Wrote empty discussions array');
+ }
+}
+
+main();
diff --git a/site/scripts/fetch_projects.ts b/site/scripts/fetch_projects.ts
new file mode 100644
index 0000000..2f37374
--- /dev/null
+++ b/site/scripts/fetch_projects.ts
@@ -0,0 +1,215 @@
+#!/usr/bin/env node
+
+import { writeFile, mkdir } from 'fs/promises';
+import { join } from 'path';
+
+const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
+const REPO_OWNER = 'cywf';
+const REPO_NAME = 'ctf-kit';
+
+interface ProjectItem {
+ title: string;
+ status: string;
+ url: string;
+ labels: string[];
+ assignees: string[];
+}
+
+interface ProjectData {
+ name: string;
+ items: ProjectItem[];
+}
+
+async function fetchGitHub(path: string) {
+ const url = `https://api.github.com${path}`;
+ const headers: Record = {
+ 'Accept': 'application/vnd.github.v3+json',
+ 'User-Agent': 'CTF-Kit-Site',
+ };
+
+ if (GITHUB_TOKEN) {
+ headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
+ }
+
+ const response = await fetch(url, { headers });
+
+ if (!response.ok) {
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+async function fetchGraphQL(query: string) {
+ const headers: Record = {
+ 'Content-Type': 'application/json',
+ 'User-Agent': 'CTF-Kit-Site',
+ };
+
+ if (GITHUB_TOKEN) {
+ headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
+ }
+
+ const response = await fetch('https://api.github.com/graphql', {
+ method: 'POST',
+ headers,
+ body: JSON.stringify({ query }),
+ });
+
+ if (!response.ok) {
+ throw new Error(`GitHub GraphQL error: ${response.status} ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+async function fetchProjectsV2(): Promise {
+ console.log('Attempting to fetch Projects v2...');
+
+ const query = `
+ query {
+ repository(owner: "${REPO_OWNER}", name: "${REPO_NAME}") {
+ projectsV2(first: 1) {
+ nodes {
+ title
+ items(first: 100) {
+ nodes {
+ content {
+ ... on Issue {
+ title
+ url
+ labels(first: 10) {
+ nodes {
+ name
+ }
+ }
+ assignees(first: 5) {
+ nodes {
+ login
+ }
+ }
+ }
+ }
+ fieldValues(first: 10) {
+ nodes {
+ ... on ProjectV2ItemFieldSingleSelectValue {
+ name
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ `;
+
+ try {
+ const result = await fetchGraphQL(query);
+
+ if (result.errors) {
+ console.warn('GraphQL errors:', result.errors);
+ return null;
+ }
+
+ const project = result.data?.repository?.projectsV2?.nodes?.[0];
+ if (!project) {
+ console.log('No Projects v2 found');
+ return null;
+ }
+
+ const items: ProjectItem[] = project.items.nodes
+ .filter((item: any) => item.content)
+ .map((item: any) => {
+ const status = item.fieldValues?.nodes?.find((field: any) => field.name)?.name || 'Todo';
+ return {
+ title: item.content.title,
+ status,
+ url: item.content.url,
+ labels: item.content.labels?.nodes?.map((label: any) => label.name) || [],
+ assignees: item.content.assignees?.nodes?.map((assignee: any) => assignee.login) || [],
+ };
+ });
+
+ console.log(`β Fetched Projects v2 with ${items.length} items`);
+ return {
+ name: project.title,
+ items,
+ };
+ } catch (error) {
+ console.warn('Failed to fetch Projects v2:', error);
+ return null;
+ }
+}
+
+async function fetchIssuesFallback(): Promise {
+ console.log('Using issues fallback...');
+
+ const issues = await fetchGitHub(`/repos/${REPO_OWNER}/${REPO_NAME}/issues?state=all&per_page=100`);
+
+ const items: ProjectItem[] = issues.map((issue: any) => {
+ // Determine status from labels
+ let status = 'Todo';
+ const labelNames = issue.labels.map((label: any) => label.name);
+
+ if (labelNames.some((name: string) => name.toLowerCase().includes('done') || name.toLowerCase().includes('completed'))) {
+ status = 'Done';
+ } else if (labelNames.some((name: string) => name.toLowerCase().includes('doing') || name.toLowerCase().includes('progress'))) {
+ status = 'In Progress';
+ }
+
+ return {
+ title: issue.title,
+ status,
+ url: issue.html_url,
+ labels: labelNames,
+ assignees: issue.assignees?.map((assignee: any) => assignee.login) || [],
+ };
+ });
+
+ console.log(`β Fetched ${items.length} issues as fallback`);
+ return {
+ name: 'Issues (Fallback)',
+ items,
+ };
+}
+
+async function main() {
+ try {
+ // Try Projects v2 first
+ let projectData = await fetchProjectsV2();
+
+ // Fallback to issues if Projects v2 not available
+ if (!projectData) {
+ projectData = await fetchIssuesFallback();
+ }
+
+ // Ensure directory exists
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+
+ // Write to file
+ const outputPath = join(dataDir, 'projects.json');
+ await writeFile(outputPath, JSON.stringify(projectData, null, 2));
+
+ console.log(`β Project data written to ${outputPath}`);
+ } catch (error) {
+ console.error('Error fetching project data:', error);
+
+ // Write empty fallback data
+ const fallbackData: ProjectData = {
+ name: 'Project Board',
+ items: [],
+ };
+
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+ const outputPath = join(dataDir, 'projects.json');
+ await writeFile(outputPath, JSON.stringify(fallbackData, null, 2));
+
+ console.log('β Wrote fallback project data');
+ }
+}
+
+main();
diff --git a/site/scripts/fetch_repo_data.ts b/site/scripts/fetch_repo_data.ts
new file mode 100644
index 0000000..445c62d
--- /dev/null
+++ b/site/scripts/fetch_repo_data.ts
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+
+import { writeFile, mkdir } from 'fs/promises';
+import { join } from 'path';
+
+const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
+const REPO_OWNER = 'cywf';
+const REPO_NAME = 'ctf-kit';
+
+interface RepoStats {
+ stars: number;
+ forks: number;
+ watchers: number;
+ languages: Record;
+ commitActivity: Array<{ week: string; commits: number }>;
+}
+
+async function fetchGitHub(path: string) {
+ const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}${path}`;
+ const headers: Record = {
+ 'Accept': 'application/vnd.github.v3+json',
+ 'User-Agent': 'CTF-Kit-Site',
+ };
+
+ if (GITHUB_TOKEN) {
+ headers['Authorization'] = `Bearer ${GITHUB_TOKEN}`;
+ }
+
+ const response = await fetch(url, { headers });
+
+ if (!response.ok) {
+ throw new Error(`GitHub API error: ${response.status} ${response.statusText}`);
+ }
+
+ return response.json();
+}
+
+async function fetchRepoData(): Promise {
+ console.log('Fetching repository data...');
+
+ // Fetch repo info
+ const repoInfo = await fetchGitHub('');
+
+ // Fetch languages
+ const languages = await fetchGitHub('/languages');
+
+ // Fetch commit activity
+ const commitActivity = await fetchGitHub('/stats/commit_activity');
+
+ // Process commit activity for last 12 weeks
+ const last12Weeks = commitActivity.slice(-12).map((week: any) => ({
+ week: new Date(week.week * 1000).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }),
+ commits: week.total,
+ }));
+
+ const stats: RepoStats = {
+ stars: repoInfo.stargazers_count || 0,
+ forks: repoInfo.forks_count || 0,
+ watchers: repoInfo.watchers_count || 0,
+ languages,
+ commitActivity: last12Weeks,
+ };
+
+ console.log(`Stats: ${stats.stars} stars, ${stats.forks} forks, ${stats.watchers} watchers`);
+
+ return stats;
+}
+
+async function main() {
+ try {
+ const stats = await fetchRepoData();
+
+ // Ensure directory exists
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+
+ // Write to file
+ const outputPath = join(dataDir, 'stats.json');
+ await writeFile(outputPath, JSON.stringify(stats, null, 2));
+
+ console.log(`β Repository stats written to ${outputPath}`);
+ } catch (error) {
+ console.error('Error fetching repository data:', error);
+
+ // Write empty fallback data
+ const fallbackStats: RepoStats = {
+ stars: 0,
+ forks: 0,
+ watchers: 0,
+ languages: {},
+ commitActivity: [],
+ };
+
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+ const outputPath = join(dataDir, 'stats.json');
+ await writeFile(outputPath, JSON.stringify(fallbackStats, null, 2));
+
+ console.log('β Wrote fallback stats data');
+ }
+}
+
+main();
diff --git a/site/scripts/scan_scripts.ts b/site/scripts/scan_scripts.ts
new file mode 100644
index 0000000..7a98f19
--- /dev/null
+++ b/site/scripts/scan_scripts.ts
@@ -0,0 +1,131 @@
+#!/usr/bin/env node
+
+import { writeFile, mkdir, readdir, readFile } from 'fs/promises';
+import { join } from 'path';
+
+interface ScriptInfo {
+ path: string;
+ name: string;
+ description: string;
+}
+
+async function scanScripts(): Promise {
+ console.log('Scanning scripts directory...');
+
+ const scriptsPath = join(process.cwd(), '..', 'scripts');
+ const scripts: ScriptInfo[] = [];
+
+ try {
+ const entries = await readdir(scriptsPath, { recursive: true, withFileTypes: true });
+
+ for (const entry of entries) {
+ if (entry.isFile()) {
+ const filePath = join(entry.parentPath || (entry as any).path || '', entry.name);
+ const relativePath = filePath.replace(scriptsPath, '').replace(/^\//, '');
+
+ // Try to extract description from file header
+ let description = '';
+ try {
+ const content = await readFile(filePath, 'utf-8');
+
+ // Look for comment blocks at the start of the file
+ const lines = content.split('\n').slice(0, 20); // First 20 lines
+
+ // Python style comments
+ if (entry.name.endsWith('.py')) {
+ const docstring = content.match(/"""([\s\S]*?)"""/);
+ if (docstring) {
+ const docText = docstring[1];
+ if (typeof docText === 'string') {
+ const lines = docText.trim().split('\n');
+ if (lines.length > 0 && lines[0]) {
+ description = lines[0];
+ }
+ }
+ }
+ if (!description) {
+ const comment = lines.find(line => line.trim().startsWith('#') && line.length > 2);
+ if (comment) {
+ description = comment.replace(/^#\s*/, '').trim();
+ }
+ }
+ }
+
+ // Shell/Bash comments
+ else if (entry.name.endsWith('.sh')) {
+ const comment = lines.find(line => line.trim().startsWith('#') && line.length > 2 && !line.includes('#!/'));
+ if (comment) {
+ description = comment.replace(/^#\s*/, '').trim();
+ }
+ }
+
+ // JavaScript/TypeScript comments
+ else if (entry.name.endsWith('.js') || entry.name.endsWith('.ts')) {
+ const blockComment = content.match(/\/\*\*([\s\S]*?)\*\//);
+ if (blockComment) {
+ const commentText = blockComment[1];
+ if (typeof commentText === 'string') {
+ const processedLines = commentText.replace(/\*/g, '').trim().split('\n');
+ if (processedLines.length > 0 && processedLines[0]) {
+ description = processedLines[0].trim();
+ }
+ }
+ }
+ if (!description) {
+ const comment = lines.find(line => line.trim().startsWith('//') && line.length > 3);
+ if (comment) {
+ description = comment.replace(/^\/\/\s*/, '').trim();
+ }
+ }
+ }
+
+ if (!description) {
+ description = `Script: ${entry.name}`;
+ }
+ } catch (err) {
+ description = `Script: ${entry.name}`;
+ }
+
+ scripts.push({
+ path: relativePath,
+ name: entry.name,
+ description,
+ });
+ }
+ }
+
+ console.log(`β Found ${scripts.length} scripts`);
+ return scripts;
+ } catch (error) {
+ console.warn('Scripts directory not found or empty:', error);
+ return [];
+ }
+}
+
+async function main() {
+ try {
+ const scripts = await scanScripts();
+
+ // Ensure directory exists
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+
+ // Write to file
+ const outputPath = join(dataDir, 'scripts.json');
+ await writeFile(outputPath, JSON.stringify(scripts, null, 2));
+
+ console.log(`β Scripts index written to ${outputPath}`);
+ } catch (error) {
+ console.error('Error scanning scripts:', error);
+
+ // Write empty array as fallback
+ const dataDir = join(process.cwd(), 'public', 'data');
+ await mkdir(dataDir, { recursive: true });
+ const outputPath = join(dataDir, 'scripts.json');
+ await writeFile(outputPath, JSON.stringify([], null, 2));
+
+ console.log('β Wrote empty scripts array');
+ }
+}
+
+main();
diff --git a/site/scripts/tsconfig.json b/site/scripts/tsconfig.json
new file mode 100644
index 0000000..3f909db
--- /dev/null
+++ b/site/scripts/tsconfig.json
@@ -0,0 +1,16 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "lib": ["ES2022"],
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "forceConsistentCasingInFileNames": true,
+ "resolveJsonModule": true,
+ "outDir": "./dist"
+ },
+ "include": ["scripts/**/*.ts"],
+ "exclude": ["node_modules"]
+}
diff --git a/site/src/components/Breadcrumbs.astro b/site/src/components/Breadcrumbs.astro
new file mode 100644
index 0000000..41f5f15
--- /dev/null
+++ b/site/src/components/Breadcrumbs.astro
@@ -0,0 +1,25 @@
+---
+interface Props {
+ items: Array<{ label: string; href?: string }>;
+}
+
+const { items } = Astro.props;
+const base = import.meta.env.BASE_URL;
+---
+
+
+
+
+ Home
+
+ {items.map((item, index) => (
+
+ {item.href && index < items.length - 1 ? (
+ {item.label}
+ ) : (
+ {item.label}
+ )}
+
+ ))}
+
+
diff --git a/site/src/components/DevelopmentBoard.tsx b/site/src/components/DevelopmentBoard.tsx
new file mode 100644
index 0000000..1db34eb
--- /dev/null
+++ b/site/src/components/DevelopmentBoard.tsx
@@ -0,0 +1,141 @@
+import { useEffect, useState } from 'react';
+
+interface ProjectItem {
+ title: string;
+ status: string;
+ url: string;
+ labels: string[];
+ assignees: string[];
+}
+
+interface ProjectData {
+ name: string;
+ items: ProjectItem[];
+}
+
+export default function DevelopmentBoard() {
+ const [projectData, setProjectData] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchProjects = async () => {
+ try {
+ const base = import.meta.env.BASE_URL || '';
+ const response = await fetch(`${base}/data/projects.json`);
+ if (!response.ok) {
+ throw new Error('Failed to load project data');
+ }
+ const data = await response.json();
+ setProjectData(data);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'An error occurred');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchProjects();
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ {[...Array(3)].map((_, i) => (
+
+ ))}
+
+
+ );
+ }
+
+ if (error || !projectData) {
+ return (
+
+
+
+
+
Error loading project data: {error}
+
+ );
+ }
+
+ // Group items by status
+ const todoItems = projectData.items.filter((item) => item.status === 'Todo' || item.status === 'todo');
+ const inProgressItems = projectData.items.filter((item) => item.status === 'In Progress' || item.status === 'doing');
+ const doneItems = projectData.items.filter((item) => item.status === 'Done' || item.status === 'done');
+
+ const renderColumn = (title: string, items: ProjectItem[], colorClass: string) => (
+
+
+
+
{title}
+
{items.length}
+
+
+
+ {items.length === 0 ? (
+
+ ) : (
+ items.map((item, index) => (
+
+
+
+ {item.labels.length > 0 && (
+
+ {item.labels.map((label, idx) => (
+
+ {label}
+
+ ))}
+
+ )}
+ {item.assignees.length > 0 && (
+
+ π€
+ {item.assignees.join(', ')}
+
+ )}
+
+
+ ))
+ )}
+
+
+ );
+
+ return (
+
+
+
+
+
+
+
{projectData.name}
+
This is a read-only view. Visit GitHub to update items.
+
+
+
+
+ {renderColumn('π To Do', todoItems, 'border-l-4 border-info')}
+ {renderColumn('π§ In Progress', inProgressItems, 'border-l-4 border-warning')}
+ {renderColumn('β
Done', doneItems, 'border-l-4 border-success')}
+
+
+ );
+}
diff --git a/site/src/components/Discussions.tsx b/site/src/components/Discussions.tsx
new file mode 100644
index 0000000..785c09f
--- /dev/null
+++ b/site/src/components/Discussions.tsx
@@ -0,0 +1,184 @@
+import { useEffect, useState } from 'react';
+
+interface Discussion {
+ title: string;
+ author: string;
+ url: string;
+ createdAt: string;
+ category: string;
+}
+
+export default function Discussions() {
+ const [discussions, setDiscussions] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [searchTerm, setSearchTerm] = useState('');
+
+ useEffect(() => {
+ const fetchDiscussions = async () => {
+ try {
+ const base = import.meta.env.BASE_URL || '';
+ const response = await fetch(`${base}/data/discussions.json`);
+ if (!response.ok) {
+ throw new Error('Failed to load discussions');
+ }
+ const data = await response.json();
+ setDiscussions(data);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'An error occurred');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchDiscussions();
+ }, []);
+
+ const filteredDiscussions = discussions.filter(
+ (discussion) =>
+ discussion.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ discussion.author.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ discussion.category.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ if (loading) {
+ return (
+
+ {[...Array(5)].map((_, i) => (
+
+ ))}
+
+ );
+ }
+
+ if (error) {
+ return (
+
+
+
+
+
Error loading discussions: {error}
+
+ );
+ }
+
+ if (discussions.length === 0) {
+ return (
+
+
+
+
+
+
+
No Discussions Yet
+
+ Be the first to start a conversation! Share your ideas, ask questions, or discuss CTF strategies with the community.
+
+
+ Start a Discussion
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Search Bar */}
+
+
+
setSearchTerm(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+ {/* Discussions List */}
+ {filteredDiscussions.length === 0 ? (
+
+
+
+
+
No discussions match your search.
+
+ ) : (
+
+ {filteredDiscussions.map((discussion, index) => (
+
+
+
+
+
+
+ {discussion.category}
+ by {discussion.author}
+ β’
+ {new Date(discussion.createdAt).toLocaleDateString()}
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ );
+}
diff --git a/site/src/components/MermaidViewer.tsx b/site/src/components/MermaidViewer.tsx
new file mode 100644
index 0000000..a20b010
--- /dev/null
+++ b/site/src/components/MermaidViewer.tsx
@@ -0,0 +1,151 @@
+import { useEffect, useRef, useState } from 'react';
+import mermaid from 'mermaid';
+
+interface MermaidDiagram {
+ name: string;
+ path: string;
+ content: string;
+}
+
+interface MermaidViewerProps {
+ diagrams: MermaidDiagram[];
+}
+
+export default function MermaidViewer({ diagrams }: MermaidViewerProps) {
+ const [selectedDiagram, setSelectedDiagram] = useState(diagrams[0]?.name || '');
+ const [error, setError] = useState(null);
+ const containerRef = useRef(null);
+
+ useEffect(() => {
+ mermaid.initialize({
+ startOnLoad: true,
+ theme: 'dark',
+ themeVariables: {
+ darkMode: true,
+ background: '#0f0f0f',
+ primaryColor: '#00ffff',
+ primaryTextColor: '#fff',
+ primaryBorderColor: '#00ffff',
+ lineColor: '#00ffff',
+ secondaryColor: '#ff00ff',
+ tertiaryColor: '#00ff00',
+ fontSize: '16px',
+ },
+ });
+ }, []);
+
+ useEffect(() => {
+ if (!containerRef.current || !selectedDiagram) return;
+
+ const renderDiagram = async () => {
+ try {
+ setError(null);
+ const diagram = diagrams.find((d) => d.name === selectedDiagram);
+ if (!diagram) {
+ setError('Diagram not found');
+ return;
+ }
+
+ const container = containerRef.current;
+ if (!container) return;
+
+ container.innerHTML = '';
+ const id = `mermaid-${Date.now()}`;
+ const { svg } = await mermaid.render(id, diagram.content);
+ container.innerHTML = svg;
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to render diagram');
+ console.error('Mermaid render error:', err);
+ }
+ };
+
+ renderDiagram();
+ }, [selectedDiagram, diagrams]);
+
+ if (diagrams.length === 0) {
+ return (
+
+
+
+
+
+
+
No Diagrams Yet
+
+ Add Mermaid diagrams to visualize your project architecture, workflows, and more. Create .mmd files in the /mermaid/ directory.
+
+
+
+
+
+
+
+
How to add diagrams:
+
+ Create a .mmd file in /mermaid/
+ Write your Mermaid diagram syntax
+ Commit and push your changes
+ The CI will automatically process it
+
+
+
+
+ Learn Mermaid Syntax
+
+
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Diagram Selector */}
+
+ {diagrams.map((diagram) => (
+ setSelectedDiagram(diagram.name)}
+ className={`btn ${selectedDiagram === diagram.name ? 'btn-primary' : 'btn-ghost'}`}
+ >
+ {diagram.name}
+
+ ))}
+
+
+ {/* Error Display */}
+ {error && (
+
+
+
+
+
Error rendering diagram: {error}
+
+ )}
+
+ {/* Diagram Display */}
+
+
+ );
+}
diff --git a/site/src/components/Statistics.tsx b/site/src/components/Statistics.tsx
new file mode 100644
index 0000000..02e63a4
--- /dev/null
+++ b/site/src/components/Statistics.tsx
@@ -0,0 +1,219 @@
+import { useEffect, useState } from 'react';
+import {
+ Chart as ChartJS,
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ ArcElement,
+ Title,
+ Tooltip,
+ Legend,
+ type ChartOptions,
+} from 'chart.js';
+import { Bar, Pie } from 'react-chartjs-2';
+
+ChartJS.register(
+ CategoryScale,
+ LinearScale,
+ BarElement,
+ ArcElement,
+ Title,
+ Tooltip,
+ Legend
+);
+
+interface RepoStats {
+ stars: number;
+ forks: number;
+ watchers: number;
+ languages: Record;
+ commitActivity: Array<{ week: string; commits: number }>;
+}
+
+export default function Statistics() {
+ const [stats, setStats] = useState(null);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ const fetchStats = async () => {
+ try {
+ const base = import.meta.env.BASE_URL || '';
+ const response = await fetch(`${base}/data/stats.json`);
+ if (!response.ok) {
+ throw new Error('Failed to load statistics');
+ }
+ const data = await response.json();
+ setStats(data);
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'An error occurred');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ fetchStats();
+ }, []);
+
+ if (loading) {
+ return (
+
+ );
+ }
+
+ if (error || !stats) {
+ return (
+
+
+
+
+
Error loading statistics: {error}
+
+ );
+ }
+
+ const languageData = {
+ labels: Object.keys(stats.languages),
+ datasets: [
+ {
+ label: 'Language Usage',
+ data: Object.values(stats.languages),
+ backgroundColor: [
+ 'rgba(255, 99, 132, 0.8)',
+ 'rgba(54, 162, 235, 0.8)',
+ 'rgba(255, 206, 86, 0.8)',
+ 'rgba(75, 192, 192, 0.8)',
+ 'rgba(153, 102, 255, 0.8)',
+ 'rgba(255, 159, 64, 0.8)',
+ ],
+ borderColor: [
+ 'rgba(255, 99, 132, 1)',
+ 'rgba(54, 162, 235, 1)',
+ 'rgba(255, 206, 86, 1)',
+ 'rgba(75, 192, 192, 1)',
+ 'rgba(153, 102, 255, 1)',
+ 'rgba(255, 159, 64, 1)',
+ ],
+ borderWidth: 2,
+ },
+ ],
+ };
+
+ const commitData = {
+ labels: stats.commitActivity.map((item) => item.week),
+ datasets: [
+ {
+ label: 'Commits',
+ data: stats.commitActivity.map((item) => item.commits),
+ backgroundColor: 'rgba(54, 162, 235, 0.8)',
+ borderColor: 'rgba(54, 162, 235, 1)',
+ borderWidth: 2,
+ },
+ ],
+ };
+
+ const chartOptions: ChartOptions<'bar'> = {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ labels: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ },
+ },
+ },
+ scales: {
+ y: {
+ ticks: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ },
+ grid: {
+ color: 'rgba(255, 255, 255, 0.1)',
+ },
+ },
+ x: {
+ ticks: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ },
+ grid: {
+ color: 'rgba(255, 255, 255, 0.1)',
+ },
+ },
+ },
+ };
+
+ const pieOptions: ChartOptions<'pie'> = {
+ responsive: true,
+ maintainAspectRatio: true,
+ plugins: {
+ legend: {
+ labels: {
+ color: 'rgba(255, 255, 255, 0.8)',
+ },
+ },
+ },
+ };
+
+ return (
+
+ {/* Stats Cards */}
+
+
+
+
Stars
+
{stats.stars}
+
+
+
+
+
Forks
+
{stats.forks}
+
+
+
+
+
Watchers
+
{stats.watchers}
+
+
+
+ {/* Charts */}
+
+
+
+
Language Distribution
+
+
+
+
+
+
+
Commit Activity (12 Weeks)
+
+
+
+
+
+
+
+ );
+}
diff --git a/site/src/components/ThemeSwitcher.tsx b/site/src/components/ThemeSwitcher.tsx
new file mode 100644
index 0000000..8c9a72f
--- /dev/null
+++ b/site/src/components/ThemeSwitcher.tsx
@@ -0,0 +1,128 @@
+import { useEffect, useState } from 'react';
+
+const themes = [
+ { id: 'night', name: 'Nightfall' },
+ { id: 'dracula', name: 'Dracula' },
+ { id: 'cyberpunk', name: 'Cyberpunk' },
+ { id: 'dark-neon', name: 'Dark Neon' },
+ { id: 'hackerman', name: 'Hackerman' },
+ { id: 'gamecore', name: 'Gamecore' },
+ { id: 'neon-accent', name: 'Neon Accent' },
+];
+
+const DEFAULT_THEME = import.meta.env.DEFAULT_THEME || 'night';
+
+export default function ThemeSwitcher() {
+ const [currentTheme, setCurrentTheme] = useState(DEFAULT_THEME);
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+
+ // Check localStorage first
+ const savedTheme = localStorage.getItem('theme');
+ if (savedTheme && themes.some(t => t.id === savedTheme)) {
+ setCurrentTheme(savedTheme);
+ document.documentElement.setAttribute('data-theme', savedTheme);
+ return;
+ }
+
+ // Check prefers-color-scheme
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+ const initialTheme = prefersDark ? DEFAULT_THEME : 'night';
+ setCurrentTheme(initialTheme);
+ document.documentElement.setAttribute('data-theme', initialTheme);
+ }, []);
+
+ const handleThemeChange = (themeId: string) => {
+ setCurrentTheme(themeId);
+ document.documentElement.setAttribute('data-theme', themeId);
+ localStorage.setItem('theme', themeId);
+ };
+
+ if (!mounted) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+
+
+
+
+ {themes.map((theme) => (
+
+ handleThemeChange(theme.id)}
+ className={`justify-between ${currentTheme === theme.id ? 'active' : ''}`}
+ role="menuitem"
+ aria-current={currentTheme === theme.id ? 'true' : 'false'}
+ >
+ {theme.name}
+ {currentTheme === theme.id && (
+
+
+
+ )}
+
+
+ ))}
+
+
+ );
+}
diff --git a/site/src/env.d.ts b/site/src/env.d.ts
new file mode 100644
index 0000000..9bc5cb4
--- /dev/null
+++ b/site/src/env.d.ts
@@ -0,0 +1 @@
+///
\ No newline at end of file
diff --git a/site/src/layouts/Layout.astro b/site/src/layouts/Layout.astro
new file mode 100644
index 0000000..259933a
--- /dev/null
+++ b/site/src/layouts/Layout.astro
@@ -0,0 +1,167 @@
+---
+import '../styles/global.css';
+import ThemeSwitcher from '../components/ThemeSwitcher';
+import Breadcrumbs from '../components/Breadcrumbs.astro';
+
+interface Props {
+ title: string;
+ description?: string;
+ breadcrumbs?: Array<{ label: string; href?: string }>;
+}
+
+const { title, description = 'CTF-Kit - Your collaborative CTF resource hub', breadcrumbs = [] } = Astro.props;
+const base = import.meta.env.BASE_URL;
+const currentPath = Astro.url.pathname;
+
+const navItems = [
+ { label: 'Home', href: `${base}/` },
+ { label: 'Statistics', href: `${base}/statistics` },
+ { label: 'Discussions', href: `${base}/discussions` },
+ { label: 'Dev Board', href: `${base}/development-board` },
+ { label: 'Docs', href: `${base}/docs` },
+ { label: 'Visualizer', href: `${base}/visualizer` },
+ { label: 'Create Issue', href: `${base}/create-issue` },
+];
+---
+
+
+
+
+
+
+
+
+
+ {title} | CTF-Kit
+
+
+ Skip to content
+
+
+
+
+
+ {breadcrumbs.length > 0 && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/site/src/pages/404.astro b/site/src/pages/404.astro
new file mode 100644
index 0000000..dbd9702
--- /dev/null
+++ b/site/src/pages/404.astro
@@ -0,0 +1,39 @@
+---
+import Layout from '../layouts/Layout.astro';
+
+const base = import.meta.env.BASE_URL;
+---
+
+
+
+
+
+
404
+
Page Not Found
+
+ Oops! The page you're looking for doesn't exist. It might have been moved or deleted.
+
+
+
+
+
+
+
diff --git a/site/src/pages/create-issue.astro b/site/src/pages/create-issue.astro
new file mode 100644
index 0000000..77703b1
--- /dev/null
+++ b/site/src/pages/create-issue.astro
@@ -0,0 +1,137 @@
+---
+import Layout from '../layouts/Layout.astro';
+
+const breadcrumbs = [{ label: 'Create Issue' }];
+
+const issueTemplates = [
+ {
+ title: 'π Bug Report',
+ description: 'Report a bug or unexpected behavior',
+ url: 'https://github.com/cywf/ctf-kit/issues/new?template=bug_report.md&labels=bug',
+ icon: 'M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z',
+ color: 'error',
+ },
+ {
+ title: 'β¨ Feature Request',
+ description: 'Suggest a new feature or enhancement',
+ url: 'https://github.com/cywf/ctf-kit/issues/new?template=feature_request.md&labels=enhancement',
+ icon: 'M9.663 17h4.673M12 3v1m6.364 1.636l-.707.707M21 12h-1M4 12H3m3.343-5.657l-.707-.707m2.828 9.9a5 5 0 117.072 0l-.548.547A3.374 3.374 0 0014 18.469V19a2 2 0 11-4 0v-.531c0-.895-.356-1.754-.988-2.386l-.548-.547z',
+ color: 'primary',
+ },
+ {
+ title: 'π Documentation',
+ description: 'Improve or fix documentation',
+ url: 'https://github.com/cywf/ctf-kit/issues/new?labels=documentation&title=docs:',
+ icon: 'M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253',
+ color: 'info',
+ },
+ {
+ title: 'π§ CTF Script/Tool',
+ description: 'Share or request a CTF script or tool',
+ url: 'https://github.com/cywf/ctf-kit/issues/new?labels=script,tool&title=tool:',
+ icon: 'M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 110-4m0 4v2m0-6V4',
+ color: 'secondary',
+ },
+ {
+ title: 'π― CTF Challenge',
+ description: 'Document a CTF challenge or solution',
+ url: 'https://github.com/cywf/ctf-kit/issues/new?labels=ctf-challenge&title=challenge:',
+ icon: 'M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z',
+ color: 'accent',
+ },
+ {
+ title: 'π¬ General Question',
+ description: 'Ask a question or start a discussion',
+ url: 'https://github.com/cywf/ctf-kit/discussions/new',
+ icon: 'M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z',
+ color: 'success',
+ },
+];
+---
+
+
+
+
+
Create an Issue
+
+ Choose a template below to create an issue or start a discussion. This will take you to GitHub where you can provide more details.
+
+
+
+
+
+
+
+
+
+
+
Need help with something else?
+
+ Visit our
issue templates page for more options, or browse existing issues.
+
+
+
+
+
+
+
Before Creating an Issue
+
+ Search existing issues to avoid duplicates
+ Provide as much detail as possible
+ Use descriptive titles that summarize the issue
+ For bug reports, include steps to reproduce
+ Add relevant labels to help categorize your issue
+
+
+
+
+
diff --git a/site/src/pages/development-board.astro b/site/src/pages/development-board.astro
new file mode 100644
index 0000000..e51203b
--- /dev/null
+++ b/site/src/pages/development-board.astro
@@ -0,0 +1,21 @@
+---
+import Layout from '../layouts/Layout.astro';
+import DevelopmentBoardComponent from '../components/DevelopmentBoard';
+
+const breadcrumbs = [{ label: 'Development Board' }];
+---
+
+
+
+
+
diff --git a/site/src/pages/discussions.astro b/site/src/pages/discussions.astro
new file mode 100644
index 0000000..4ea2f42
--- /dev/null
+++ b/site/src/pages/discussions.astro
@@ -0,0 +1,21 @@
+---
+import Layout from '../layouts/Layout.astro';
+import DiscussionsComponent from '../components/Discussions';
+
+const breadcrumbs = [{ label: 'Discussions' }];
+---
+
+
+
+
+
diff --git a/site/src/pages/docs/[...slug].astro b/site/src/pages/docs/[...slug].astro
new file mode 100644
index 0000000..41e4b73
--- /dev/null
+++ b/site/src/pages/docs/[...slug].astro
@@ -0,0 +1,101 @@
+---
+import Layout from '../../layouts/Layout.astro';
+import { readFile, readdir } from 'node:fs/promises';
+import { join } from 'node:path';
+
+export async function getStaticPaths() {
+ const docsPath = join(process.cwd(), '..', 'docs');
+ const allDocs = await readdir(docsPath, { recursive: true, withFileTypes: true });
+
+ const paths = allDocs
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
+ .map((dirent) => {
+ const relativePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name)
+ .replace(docsPath, '')
+ .replace(/^\//, '')
+ .replace('.md', '');
+
+ return {
+ params: { slug: relativePath },
+ };
+ });
+
+ return paths;
+}
+
+const { slug } = Astro.params;
+const slugPath = slug || '';
+
+const docsPath = join(process.cwd(), '..', 'docs');
+const filePath = join(docsPath, `${slugPath}.md`);
+
+let content = '';
+let title: string = 'Documentation';
+try {
+ content = await readFile(filePath, 'utf-8');
+
+ // Extract title from markdown
+ const titleMatch = content.match(/^#\s+(.+)$/m);
+ if (titleMatch && titleMatch[1]) {
+ title = titleMatch[1];
+ } else {
+ const fallback = slugPath.split('/').pop()?.replace(/-/g, ' ');
+ title = fallback || 'Documentation';
+ }
+} catch (error) {
+ content = `# Document Not Found\n\nThe requested document could not be found.`;
+ title = 'Not Found';
+}
+
+const breadcrumbs = [
+ { label: 'Documentation', href: `${import.meta.env.BASE_URL}/docs` },
+ { label: title },
+];
+
+// Simple markdown to HTML conversion (for basic rendering)
+// In production, you'd want to use a proper markdown parser
+const htmlContent = content
+ .replace(/^### (.+)$/gm, '$1 ')
+ .replace(/^## (.+)$/gm, '$1 ')
+ .replace(/^# (.+)$/gm, '$1 ')
+ .replace(/\*\*(.+?)\*\*/g, '$1 ')
+ .replace(/\*(.+?)\*/g, '$1 ')
+ .replace(/`(.+?)`/g, '$1')
+ .replace(/^\- (.+)$/gm, '$1 ')
+ .replace(/(.*<\/li>\n?)+/g, '')
+ .replace(/^\d+\. (.+)$/gm, ' $1 ')
+ .replace(/\[(.+?)\]\((.+?)\)/g, '$1 ')
+ .replace(/\n\n/g, '
')
+ .replace(/^(?!<[h\|ul\|li])/gm, '
');
+
+const githubUrl = `https://github.com/cywf/ctf-kit/blob/main/docs/${slugPath}.md`;
+---
+
+
+
+
diff --git a/site/src/pages/docs/index.astro b/site/src/pages/docs/index.astro
new file mode 100644
index 0000000..a33e198
--- /dev/null
+++ b/site/src/pages/docs/index.astro
@@ -0,0 +1,109 @@
+---
+import Layout from '../../layouts/Layout.astro';
+import { readdir } from 'node:fs/promises';
+import { join } from 'node:path';
+
+const breadcrumbs = [{ label: 'Documentation' }];
+
+// Get all markdown files from docs directory
+const docsPath = join(process.cwd(), '..', 'docs');
+const allDocs = await readdir(docsPath, { recursive: true, withFileTypes: true });
+const mdFiles = allDocs
+ .filter((dirent) => dirent.isFile() && dirent.name.endsWith('.md'))
+ .map((dirent) => {
+ const relativePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name).replace(docsPath, '').replace(/^\//, '');
+ return {
+ name: dirent.name.replace('.md', ''),
+ path: relativePath,
+ category: relativePath.includes('/') ? relativePath.split('/')[0] : 'root',
+ };
+ });
+
+// Group by category
+const groupedDocs = mdFiles.reduce((acc, doc) => {
+ const category = doc.category || 'root';
+ if (!acc[category]) {
+ acc[category] = [];
+ }
+ acc[category].push(doc);
+ return acc;
+}, {} as Record);
+
+const base = import.meta.env.BASE_URL;
+---
+
+
+
+
+
Documentation
+
+ Browse our comprehensive guides and documentation for CTF competitions.
+
+
+
+
+
+
+
+
+
Contribute to Documentation
+
+ Found an error or want to add more content? Contributions are welcome! Visit the
docs folder on GitHub .
+
+
+
+
+ {Object.entries(groupedDocs).map(([category, docs]) => (
+
+
+ {category === 'root' ? 'General' : category.replace(/-/g, ' ')}
+
+
+
+ ))}
+
+
diff --git a/site/src/pages/index.astro b/site/src/pages/index.astro
new file mode 100644
index 0000000..d95d237
--- /dev/null
+++ b/site/src/pages/index.astro
@@ -0,0 +1,125 @@
+---
+import Layout from '../layouts/Layout.astro';
+
+const base = import.meta.env.BASE_URL;
+---
+
+
+
+
+
+
+
Welcome to CTF-Kit
+
+ Your collaborative and evolving resource for Capture The Flag competitions
+
+
+
+
+
+
+
+
+
+
+
Introduction
+
+ Welcome to the CTF-Kit repository! This project is designed to be a collaborative and evolving resource for anyone interested in participating in Capture The Flag (CTF) competitions. Whether you're a seasoned veteran or a newcomer to the world of CTFs, this repository aims to provide you with the tools, resources, and knowledge you need to succeed.
+
+
+
+
+
+
+
+
Vision and Goals
+
+
Our Vision
+
+ The vision for this repository is to become a central hub for CTF learning and collaboration. We aim to create a dynamic space where participants can share their insights, strategies, and resources to help each other improve and grow.
+
+
+
Goals
+
+ Facilitate Collaboration: Encourage participants to work together, share their findings, and contribute to the collective knowledge base.
+ Improve CTF Strategies: Through shared experiences and resources, help users develop more effective strategies for tackling various types of CTF challenges.
+ Create a Rich Database: Build a repository of scripts, tools, and writeups that serve as valuable references for future CTFs.
+
+
+
+
+
+
+
+
How to Use This Repository
+
+
+
+
1. Fork the Repository
+
Start by forking this repository to your own GitHub account.
+
+
+
+
2. Create a Branch
+
When you participate in a CTF, create a new branch in your forked repo. Name the branch after the CTF event you are participating in (e.g., metactf-august2024).
+
+
+
+
3. Work in Your Branch
+
Use this branch to document your progress, add scripts, and write up your solutions.
+
+
+
+
4. Contribute Back
+
After the CTF, you can submit a pull request to contribute your findings and improvements back to the main repository.
+
+
+
+
+
+
+
+
+
+
+
+
Start a CTF Branch
+
Follow our branching strategy to participate in CTF events and contribute your solutions.
+
+
+
+
+
+
+
Browse Scripts
+
Explore our collection of tools and scripts for various CTF challenges.
+
+
+
+
+
+
diff --git a/site/src/pages/statistics.astro b/site/src/pages/statistics.astro
new file mode 100644
index 0000000..a77b074
--- /dev/null
+++ b/site/src/pages/statistics.astro
@@ -0,0 +1,11 @@
+---
+import Layout from '../layouts/Layout.astro';
+import StatisticsComponent from '../components/Statistics';
+
+const breadcrumbs = [{ label: 'Statistics' }];
+---
+
+
+ Repository Statistics
+
+
diff --git a/site/src/pages/visualizer.astro b/site/src/pages/visualizer.astro
new file mode 100644
index 0000000..a64ba43
--- /dev/null
+++ b/site/src/pages/visualizer.astro
@@ -0,0 +1,45 @@
+---
+import Layout from '../layouts/Layout.astro';
+import MermaidViewer from '../components/MermaidViewer';
+import { readdir, readFile } from 'node:fs/promises';
+import { join } from 'node:path';
+
+const breadcrumbs = [{ label: 'Visualizer' }];
+
+// Try to read mermaid files
+let diagrams: Array<{ name: string; path: string; content: string }> = [];
+try {
+ const mermaidPath = join(process.cwd(), '..', 'mermaid');
+ const files = await readdir(mermaidPath, { recursive: true, withFileTypes: true });
+
+ const mmdFiles = files.filter((dirent) => dirent.isFile() && dirent.name.endsWith('.mmd'));
+
+ diagrams = await Promise.all(
+ mmdFiles.map(async (dirent) => {
+ const filePath = join(dirent.parentPath || (dirent as any).path || '', dirent.name);
+ const content = await readFile(filePath, 'utf-8');
+ return {
+ name: dirent.name.replace('.mmd', ''),
+ path: filePath,
+ content,
+ };
+ })
+ );
+} catch (error) {
+ // Mermaid directory doesn't exist or is empty
+ console.log('No mermaid diagrams found');
+}
+---
+
+
+
+
+
Project Visualizer
+
+ Interactive Mermaid diagrams showing project architecture, workflows, and relationships.
+
+
+
+
+
+
diff --git a/site/src/styles/global.css b/site/src/styles/global.css
new file mode 100644
index 0000000..5a5d71e
--- /dev/null
+++ b/site/src/styles/global.css
@@ -0,0 +1,68 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ color-scheme: dark;
+ }
+
+ html {
+ scroll-behavior: smooth;
+ }
+
+ body {
+ @apply bg-base-100 text-base-content;
+ }
+
+ @media (prefers-reduced-motion: reduce) {
+ * {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ transition-duration: 0.01ms !important;
+ }
+ }
+}
+
+@layer components {
+ .skip-to-content {
+ @apply absolute -top-10 left-0 z-50 bg-primary text-primary-content px-4 py-2 focus:top-0;
+ }
+
+ .prose-dark {
+ @apply max-w-none text-base-content;
+ }
+
+ .prose-dark h1 {
+ @apply text-4xl font-bold mb-6;
+ }
+
+ .prose-dark h2 {
+ @apply text-2xl font-bold mt-8 mb-4;
+ }
+
+ .prose-dark h3 {
+ @apply text-xl font-bold mt-6 mb-3;
+ }
+
+ .prose-dark p {
+ @apply mb-4;
+ }
+
+ .prose-dark pre {
+ @apply bg-base-300 p-4 rounded-lg overflow-x-auto;
+ }
+
+ .prose-dark code {
+ @apply bg-base-300 px-2 py-1 rounded text-sm;
+ }
+
+ .prose-dark a {
+ @apply text-primary hover:underline;
+ }
+
+ .prose-dark ul,
+ .prose-dark ol {
+ @apply list-disc list-inside space-y-2 my-4 ml-4;
+ }
+}
diff --git a/site/tailwind.config.mjs b/site/tailwind.config.mjs
new file mode 100644
index 0000000..7af1646
--- /dev/null
+++ b/site/tailwind.config.mjs
@@ -0,0 +1,70 @@
+/** @type {import('tailwindcss').Config} */
+export default {
+ content: ['./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}'],
+ theme: {
+ extend: {},
+ },
+ plugins: [require('daisyui')],
+ daisyui: {
+ themes: [
+ 'night',
+ 'dracula',
+ 'cyberpunk',
+ {
+ 'dark-neon': {
+ 'primary': '#00ffff',
+ 'secondary': '#ff00ff',
+ 'accent': '#00ff00',
+ 'neutral': '#0a0a0a',
+ 'base-100': '#0f0f0f',
+ 'base-200': '#1a1a1a',
+ 'base-300': '#252525',
+ 'info': '#00d4ff',
+ 'success': '#00ff88',
+ 'warning': '#ffaa00',
+ 'error': '#ff0055',
+ },
+ 'hackerman': {
+ 'primary': '#00ff00',
+ 'secondary': '#00aa00',
+ 'accent': '#00ffaa',
+ 'neutral': '#000000',
+ 'base-100': '#0d0d0d',
+ 'base-200': '#151515',
+ 'base-300': '#1d1d1d',
+ 'info': '#00ccff',
+ 'success': '#00ff00',
+ 'warning': '#ffcc00',
+ 'error': '#ff0000',
+ },
+ 'gamecore': {
+ 'primary': '#8b5cf6',
+ 'secondary': '#ec4899',
+ 'accent': '#f59e0b',
+ 'neutral': '#1e1e2e',
+ 'base-100': '#0f0f1e',
+ 'base-200': '#161628',
+ 'base-300': '#1e1e3f',
+ 'info': '#3b82f6',
+ 'success': '#10b981',
+ 'warning': '#f59e0b',
+ 'error': '#ef4444',
+ },
+ 'neon-accent': {
+ 'primary': '#ff006e',
+ 'secondary': '#8338ec',
+ 'accent': '#3a86ff',
+ 'neutral': '#14141f',
+ 'base-100': '#0a0a12',
+ 'base-200': '#12121d',
+ 'base-300': '#1a1a28',
+ 'info': '#06b6d4',
+ 'success': '#14f195',
+ 'warning': '#fbbf24',
+ 'error': '#f43f5e',
+ },
+ },
+ ],
+ darkTheme: 'night',
+ },
+};
diff --git a/site/tsconfig.json b/site/tsconfig.json
new file mode 100644
index 0000000..0f7c346
--- /dev/null
+++ b/site/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "astro/tsconfigs/strictest",
+ "compilerOptions": {
+ "jsx": "react-jsx",
+ "jsxImportSource": "react",
+ "types": ["node"]
+ }
+}
From efbcbc7abeaa0a6057262c5a875cd7d7ae39b2a6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sun, 2 Nov 2025 01:42:24 +0000
Subject: [PATCH 3/3] Add example Mermaid diagrams and rebuild site
Co-authored-by: cywf <100837335+cywf@users.noreply.github.com>
---
mermaid/contribution-flow.mmd | 26 ++++++++++++++++++++++++++
mermaid/ctf-workflow.mmd | 27 +++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
create mode 100644 mermaid/contribution-flow.mmd
create mode 100644 mermaid/ctf-workflow.mmd
diff --git a/mermaid/contribution-flow.mmd b/mermaid/contribution-flow.mmd
new file mode 100644
index 0000000..de9bc7f
--- /dev/null
+++ b/mermaid/contribution-flow.mmd
@@ -0,0 +1,26 @@
+graph LR
+ A[CTF-Kit Repository] --> B[Fork Repository]
+ B --> C[Create Branch]
+ C --> D{CTF Event}
+
+ D --> E[Use Tools & Scripts]
+ E --> F[Document Findings]
+ F --> G[Add Solutions]
+
+ G --> H{Event Complete?}
+ H -->|No| E
+ H -->|Yes| I[Submit Pull Request]
+
+ I --> J[Code Review]
+ J --> K{Approved?}
+ K -->|Yes| L[Merge to Main]
+ K -->|No| M[Make Changes]
+ M --> I
+
+ L --> N[Community Benefits]
+ N --> O[Enhanced Repository]
+
+ style A fill:#8b5cf6,stroke:#8b5cf6,color:#fff
+ style I fill:#00ffff,stroke:#00ffff,color:#000
+ style L fill:#00ff00,stroke:#00ff00,color:#000
+ style O fill:#ff00ff,stroke:#ff00ff,color:#000
diff --git a/mermaid/ctf-workflow.mmd b/mermaid/ctf-workflow.mmd
new file mode 100644
index 0000000..5c554c2
--- /dev/null
+++ b/mermaid/ctf-workflow.mmd
@@ -0,0 +1,27 @@
+graph TD
+ A[Start CTF] --> B{Choose Challenge Type}
+ B -->|Web| C[Web Exploitation]
+ B -->|Crypto| D[Cryptography]
+ B -->|Forensics| E[Forensics]
+ B -->|Reversing| F[Reverse Engineering]
+ B -->|Misc| G[Miscellaneous]
+
+ C --> H[Enumerate & Scan]
+ D --> H
+ E --> H
+ F --> H
+ G --> H
+
+ H --> I[Find Vulnerability]
+ I --> J{Exploit Successful?}
+ J -->|Yes| K[Capture Flag]
+ J -->|No| L[Try Different Approach]
+ L --> H
+
+ K --> M[Document Solution]
+ M --> N[Submit Flag]
+ N --> O[End]
+
+ style A fill:#00ffff,stroke:#00ffff,color:#000
+ style K fill:#00ff00,stroke:#00ff00,color:#000
+ style O fill:#ff00ff,stroke:#ff00ff,color:#000