diff --git a/.coderabbit.yaml b/.coderabbit.yaml new file mode 100644 index 0000000..1f5f628 --- /dev/null +++ b/.coderabbit.yaml @@ -0,0 +1,32 @@ +# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json +language: "en-US" + +# The tone the AI will use when talking to you and your contributors +tone_instructions: 'You are a senior full-stack developer reviewing code for an open-source Next.js project. Be encouraging, highly technical, and concise. Always suggest best practices for TypeScript and Next.js App Router.' + +reviews: + # 'chill' means it won't nitpick tiny style things that ESLint should catch + profile: "chill" + + # When set to true, CodeRabbit will automatically "Approve" the PR when its requested changes are resolved + request_changes_workflow: true + + # Adds a fun, auto-generated summary and a poem about the code at the top of the PR! + high_level_summary: true + poem: true + + # Automatically review all new PRs + auto_review: + enabled: true + drafts: false # Won't waste reviews if a contributor opens a "Draft" PR + + # Specific rules for specific folders! + path_instructions: + - path: "frontend/**/*.tsx" + instructions: "Ensure React components are using modern hooks, avoid unnecessary re-renders, and use TailwindCSS properly." + - path: "frontend/content/**/*.mdx" + instructions: "Ensure MDX files have proper frontmatter, clean Markdown formatting, and that code blocks specify a language." + +# Allows you and contributors to chat with the bot in the PR comments +chat: + auto_reply: true \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..4f3758e --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,41 @@ +name: PR Checks + +on: + pull_request: + branches: [ main ] + +jobs: + quality-checks: + runs-on: ubuntu-latest + + # Run all commands inside the frontend directory + defaults: + run: + working-directory: ./frontend + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: ./frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + # Uses the "lint": "eslint" script from your package.json + run: npm run lint + + - name: Check TypeScript Types + # Uses npx to run the TypeScript compiler and check for errors without generating files + run: npx tsc --noEmit + + - name: Verify Next.js Build + # Uses the "build": "next build" script + # Next.js will also do a strict check here, but having them separate makes debugging easier! + run: npm run build \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md deleted file mode 100644 index e215bc4..0000000 --- a/frontend/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/frontend/app/[topic]/TopicLayout.tsx b/frontend/app/[topic]/TopicLayout.tsx index 72ef5ff..c393bc4 100644 --- a/frontend/app/[topic]/TopicLayout.tsx +++ b/frontend/app/[topic]/TopicLayout.tsx @@ -1,76 +1,562 @@ "use client"; +import { useEffect, useMemo, useRef, useState } from "react"; import { useTheme } from "../../src/context/ThemeContext"; import { useRouter } from "next/navigation"; -import { ArrowLeft } from "lucide-react"; +import { + ArrowLeft, + BookOpenText, + Braces, + ChevronDown, + Code2, + Search, + ShieldCheck, + Sparkles, + TerminalSquare, + Workflow, +} from "lucide-react"; export default function TopicLayout({ children, frontmatter, topicKey, + topics = [], + headings = [], + prevTopic, + nextTopic, }: { children: React.ReactNode; frontmatter: any; topicKey: string; + topics?: Array<{ id: string; title?: string; description?: string }>; + headings?: Array<{ id: string; title: string; depth: number }>; + prevTopic?: { id: string; title?: string } | null; + nextTopic?: { id: string; title?: string } | null; }) { const { darkMode, toggleTheme } = useTheme(); const router = useRouter(); + const searchInputRef = useRef(null); + const [docSearch, setDocSearch] = useState(""); + const [domHeadings, setDomHeadings] = useState>([]); + + useEffect(() => { + const article = document.querySelector('article'); + const headingNodes = article ? Array.from(article.querySelectorAll('h2[id], h3[id]')) : []; + + const nextHeadings = headingNodes.map((node) => ({ + id: node.id, + title: node.textContent?.trim() || node.id, + depth: node.tagName === 'H3' ? 3 : 2, + })); + + setDomHeadings(nextHeadings); + }, [children, topicKey]); + + const activeHeadings = headings.length > 0 ? headings : domHeadings; + const normalizedQuery = docSearch.trim().toLowerCase(); + const hasSearchQuery = normalizedQuery.length > 0; + + const searchResults = useMemo(() => { + if (!hasSearchQuery) return []; + + const sectionMatches = activeHeadings + .filter((heading) => heading.title.toLowerCase().includes(normalizedQuery)) + .map((heading) => ({ + id: heading.id, + title: heading.title, + type: "section" as const, + depth: heading.depth, + })); + + const topicMatches = topics + .filter((item) => { + const title = (item.title || item.id || "").toLowerCase(); + const description = (item.description || "").toLowerCase(); + return title.includes(normalizedQuery) || description.includes(normalizedQuery); + }) + .map((item) => ({ + id: item.id, + title: item.title || item.id, + type: "topic" as const, + depth: 1, + })); + + return [...sectionMatches, ...topicMatches].slice(0, 12); + }, [activeHeadings, hasSearchQuery, normalizedQuery, topics]); + + const outlineGroups = useMemo(() => { + const groups: Array<{ id: string; title: string; items: Array<{ id: string; title: string }> }> = []; + let activeGroup: { id: string; title: string; items: Array<{ id: string; title: string }> } | null = null; + + for (const heading of activeHeadings) { + const headingQuery = heading.title.toLowerCase(); + const matchesQuery = !hasSearchQuery || headingQuery.includes(normalizedQuery); + + if (heading.depth === 2) { + if (hasSearchQuery && !matchesQuery) continue; + + activeGroup = { id: heading.id, title: heading.title, items: [] }; + groups.push(activeGroup); + continue; + } + + if (heading.depth === 3 && activeGroup) { + if (!hasSearchQuery || matchesQuery) { + activeGroup.items.push({ id: heading.id, title: heading.title }); + } + } + } + + return groups; + }, [activeHeadings, hasSearchQuery, normalizedQuery]); + + const [expandedOutline, setExpandedOutline] = useState>({}); + const [activeOutlineId, setActiveOutlineId] = useState(null); + + useEffect(() => { + const ids = outlineGroups.flatMap((group) => [group.id, ...group.items.map((item) => item.id)]); + if (ids.length === 0) { + setActiveOutlineId(null); + return; + } + + const updateActiveOutline = () => { + const offset = 140; + let currentId: string | null = ids[0] ?? null; + + for (const id of ids) { + const element = document.getElementById(id); + if (!element) continue; + + const rect = element.getBoundingClientRect(); + if (rect.top <= offset) { + currentId = id; + } + } + + setActiveOutlineId((previous) => previous === currentId ? previous : currentId); + }; + + updateActiveOutline(); + window.addEventListener("scroll", updateActiveOutline, { passive: true }); + window.addEventListener("resize", updateActiveOutline); + + return () => { + window.removeEventListener("scroll", updateActiveOutline); + window.removeEventListener("resize", updateActiveOutline); + }; + }, [outlineGroups]); + + useEffect(() => { + const handleKeyDown = (event: KeyboardEvent) => { + const isModifier = event.metaKey || event.ctrlKey; + if (!isModifier || event.key.toLowerCase() !== "k") return; + + event.preventDefault(); + searchInputRef.current?.focus(); + searchInputRef.current?.select(); + }; + + window.addEventListener("keydown", handleKeyDown); + return () => window.removeEventListener("keydown", handleKeyDown); + }, []); + + useEffect(() => { + setExpandedOutline((previous) => { + const next: Record = { ...previous }; + outlineGroups.forEach((group, index) => { + next[group.id] = index === 0 ? true : Boolean(previous[group.id]); + }); + return next; + }); + }, [outlineGroups]); + + const sectionGroups = useMemo(() => { + const groups: Array<{ id: string; title: string; items: Array<{ id: string; title: string }> }> = []; + let activeGroup: { id: string; title: string; items: Array<{ id: string; title: string }> } | null = null; + + for (const heading of headings) { + if (heading.depth === 2) { + activeGroup = { id: heading.id, title: heading.title, items: [] }; + groups.push(activeGroup); + continue; + } + + if (heading.depth === 3 && activeGroup) { + activeGroup.items.push({ id: heading.id, title: heading.title }); + } + } + + return groups; + }, [headings]); + + const [expandedSections, setExpandedSections] = useState>({}); + + useEffect(() => { + setExpandedSections((previous) => { + const next: Record = { ...previous }; + for (const group of sectionGroups) { + if (!(group.id in next)) { + next[group.id] = true; + } + } + return next; + }); + }, [sectionGroups]); - // JavaScript-driven theme variables to guarantee instant toggling const theme = { - page: darkMode - ? "bg-[#0A0A0A] text-[#E5E5E5]" - : "bg-[#F4F4F0] text-[#111111]", - border: darkMode ? "border-[#222222]" : "border-[#D1D1D1]", - accent: darkMode ? "text-cyan-400" : "text-indigo-600", - hoverAccent: darkMode ? "hover:text-cyan-400" : "hover:text-indigo-600", - sourceText: darkMode ? "text-amber-500" : "text-amber-700", + page: darkMode ? "bg-[#050505] text-[#F5F5F5]" : "bg-[#F5F3EE] text-[#111111]", + panel: darkMode ? "bg-[#050505]" : "bg-[#F5F3EE]", + border: darkMode ? "border-[#1A1A1A]" : "border-[#DFE1DA]", + muted: darkMode ? "text-[#8A8A8A]" : "text-[#666C73]", + soft: darkMode ? "bg-[#0D0D0D] text-[#F5F5F5]" : "bg-[#F0EFEA] text-[#111111]", + active: darkMode ? "bg-[#111111] text-white" : "bg-white text-black", + accent: darkMode ? "text-zinc-100" : "text-zinc-800", + hoverAccent: darkMode ? "hover:text-white" : "hover:text-zinc-900", + input: darkMode ? "border-[#1D1D1D] bg-[#0B0B0B]" : "border-[#DDE0D7] bg-[#F7F6F3]", + sidebar: darkMode ? "bg-[#050505]" : "bg-[#F2F0EA]", + navItem: darkMode ? "text-[#F5F5F5]" : "text-[#111111]", + }; + + const getTopicIcon = (topicId: string) => { + const value = topicId.toLowerCase(); + + if (value.includes("javascript") || value.includes("js")) return TerminalSquare; + if (value.includes("python")) return Braces; + if (value.includes("react")) return Workflow; + if (value.includes("cpp") || value.includes("c++")) return Code2; + return BookOpenText; + }; + + const getSectionIcon = (title: string) => { + const value = title.toLowerCase(); + + if (value.includes("getting") || value.includes("overview") || value.includes("introduction")) return BookOpenText; + if (value.includes("variable") || value.includes("data") || value.includes("types")) return Braces; + if (value.includes("console") || value.includes("terminal")) return TerminalSquare; + if (value.includes("function") || value.includes("module") || value.includes("async")) return Code2; + if (value.includes("security") || value.includes("protection") || value.includes("rules")) return ShieldCheck; + if (value.includes("best practice") || value.includes("tips") || value.includes("patterns")) return Sparkles; + if (value.includes("workflow") || value.includes("lifecycle") || value.includes("reconciliation")) return Workflow; + return BookOpenText; + }; + + const scrollToSection = (id: string) => { + if (!id) return; + + const target = document.getElementById(id); + if (!target) return; + + const headerOffset = 88; + const top = target.getBoundingClientRect().top + window.scrollY - headerOffset; + window.scrollTo({ top, behavior: "smooth" }); }; return ( -
- {/* LEFT PANE: Sticky Nav & Meta */} -
-
- {/* Back Button */} - +
+
+
+
+
+ R_ +
+
+ RefMe + docs +
+
- {/* Topic Header fed by Markdown Frontmatter */} -

- {frontmatter?.title || topicKey} - _ -

-

- {frontmatter?.description} -

-
+
+
+
+ + setDocSearch(event.target.value)} + aria-label='Search docs' + placeholder='Search topics and sections...' + className='w-full bg-transparent text-sm outline-none placeholder:text-current placeholder:opacity-50' + /> + {docSearch ? ( + + ) : ( + ⌘K + )} +
+ + {hasSearchQuery && ( +
+ {searchResults.length > 0 ? ( +
+ {searchResults.map((result) => ( + + ))} +
+ ) : ( +
+ No matching topics or sections +
+ )} +
+ )} +
+
- {/* Footer Settings */} -
- - Src: {topicKey}.mdx -
-
+ + +
+ + +
+
+
+ {children} +
+
+ +
+
+ {prevTopic ? ( + + ) : ( +
+ )} + + {nextTopic ? ( + + ) : ( +
+ )} +
+
+
+ +
); diff --git a/frontend/app/[topic]/page.tsx b/frontend/app/[topic]/page.tsx index 5db5c6e..1875f23 100644 --- a/frontend/app/[topic]/page.tsx +++ b/frontend/app/[topic]/page.tsx @@ -1,5 +1,5 @@ // app/[topic]/page.tsx -import { getTopicBySlug } from "../../src/lib/mdx"; +import { getAllTopics, getTopicBySlug } from "../../src/lib/mdx"; import { MDXRemote } from "next-mdx-remote/rsc"; import SkeletonTopic from "../components/SkeletonTopic"; import TopicLayout from "./TopicLayout"; @@ -29,10 +29,24 @@ export default async function TopicPage({ }) { const resolvedParams = await params; const topicData = getTopicBySlug(resolvedParams.topic); + const allTopics = getAllTopics(); + const currentIndex = allTopics.findIndex((topic) => topic.id === resolvedParams.topic); + const prevTopic = currentIndex > 0 ? allTopics[currentIndex - 1] : null; + const nextTopic = + currentIndex >= 0 && currentIndex < allTopics.length - 1 + ? allTopics[currentIndex + 1] + : null; if (!topicData) { return ( - + ); @@ -42,6 +56,10 @@ export default async function TopicPage({ diff --git a/frontend/app/components/ClientCodeBlock.tsx b/frontend/app/components/ClientCodeBlock.tsx index b14e34f..19ff564 100644 --- a/frontend/app/components/ClientCodeBlock.tsx +++ b/frontend/app/components/ClientCodeBlock.tsx @@ -3,10 +3,6 @@ import { useTheme } from "../../src/context/ThemeContext"; import { useState, useEffect } from "react"; import { Copy, Check } from "lucide-react"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; -import { - vscDarkPlus, - oneLight, -} from "react-syntax-highlighter/dist/esm/styles/prism"; export default function ClientCodeBlock({ code, @@ -32,12 +28,144 @@ export default function ClientCodeBlock({ } }; + const pitchBlackModernStyle = { + 'code[class*="language-"]': { + background: '#050505', + color: '#FFFFFF', + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + }, + 'pre[class*="language-"]': { + background: '#050505', + color: '#FFFFFF', + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + }, + comment: { color: '#8B8B8B', fontStyle: 'italic' }, + keyword: { color: '#A0A0A0', fontWeight: 600 }, + selector: { color: '#FFFFFF' }, + punctuation: { color: '#A0A0A0' }, + string: { color: '#99FFE4' }, + number: { color: '#99FFE4' }, + function: { color: '#FFFFFF' }, + className: { color: '#FFFFFF' }, + tag: { color: '#FFFFFF' }, + attrName: { color: '#99FFE4' }, + attrValue: { color: '#99FFE4' }, + operator: { color: '#A0A0A0' }, + boolean: { color: '#E580FF' }, + builtin: { color: '#99FFE4' }, + parameter: { color: '#FFFFFF' }, + property: { color: '#FFFFFF' }, + variable: { color: '#FFFFFF' }, + regex: { color: '#99FFE4' }, + deleted: { color: '#E580FF' }, + inserted: { color: '#99FFE4' }, + important: { color: '#E580FF' }, + maybeClassName: { color: '#C699FF' }, + plain: { color: '#FFFFFF' }, + default: { color: '#FFFFFF' }, + symbol: { color: '#99FFE4' }, + class: { color: '#C699FF' }, + title: { color: '#C699FF' }, + constant: { color: '#C699FF' }, + type: { color: '#C699FF' }, + namespace: { color: '#C699FF' }, + char: { color: '#99FFE4' }, + url: { color: '#99FFE4' }, + atrule: { color: '#C699FF' }, + selectorClass: { color: '#C699FF' }, + selectorId: { color: '#C699FF' }, + attribute: { color: '#C699FF' }, + literal: { color: '#99FFE4' }, + hexcode: { color: '#99FFE4' }, + 'template-string': { color: '#99FFE4' }, + 'attr-value': { color: '#99FFE4' }, + 'attr-name': { color: '#99FFE4' }, + 'class-name': { color: '#C699FF' }, + 'function-name': { color: '#FFFFFF' }, + 'tag-name': { color: '#C699FF' }, + 'pseudo-class': { color: '#C699FF' }, + 'pseudo-element': { color: '#C699FF' }, + 'html-tag': { color: '#C699FF' }, + 'json-key': { color: '#99FFE4' }, + 'error': { color: '#E580FF' }, + invalid: { color: '#E580FF' }, + deletedCode: { color: '#E580FF' }, + insert: { background: '#050505' }, + delete: { background: '#050505' }, + }; + + const softLightModernStyle = { + 'code[class*="language-"]': { + background: '#F4F4F0', + color: '#111111', + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + }, + 'pre[class*="language-"]': { + background: '#F4F4F0', + color: '#111111', + fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace', + }, + comment: { color: '#6F7780', fontStyle: 'italic' }, + keyword: { color: '#6D4DD8', fontWeight: 600 }, + selector: { color: '#111111' }, + punctuation: { color: '#4B5563' }, + string: { color: '#0C7C6B' }, + number: { color: '#0C7C6B' }, + function: { color: '#111111' }, + className: { color: '#111111' }, + tag: { color: '#111111' }, + attrName: { color: '#0C7C6B' }, + attrValue: { color: '#0C7C6B' }, + operator: { color: '#4B5563' }, + boolean: { color: '#B33EE5' }, + builtin: { color: '#0C7C6B' }, + parameter: { color: '#111111' }, + property: { color: '#111111' }, + variable: { color: '#111111' }, + regex: { color: '#0C7C6B' }, + deleted: { color: '#B33EE5' }, + inserted: { color: '#0C7C6B' }, + important: { color: '#B33EE5' }, + maybeClassName: { color: '#7A4FD4' }, + plain: { color: '#111111' }, + default: { color: '#111111' }, + symbol: { color: '#0C7C6B' }, + class: { color: '#7A4FD4' }, + title: { color: '#7A4FD4' }, + constant: { color: '#7A4FD4' }, + type: { color: '#7A4FD4' }, + namespace: { color: '#7A4FD4' }, + char: { color: '#0C7C6B' }, + url: { color: '#0C7C6B' }, + atrule: { color: '#7A4FD4' }, + selectorClass: { color: '#7A4FD4' }, + selectorId: { color: '#7A4FD4' }, + attribute: { color: '#7A4FD4' }, + literal: { color: '#0C7C6B' }, + hexcode: { color: '#0C7C6B' }, + 'template-string': { color: '#0C7C6B' }, + 'attr-value': { color: '#0C7C6B' }, + 'attr-name': { color: '#0C7C6B' }, + 'class-name': { color: '#7A4FD4' }, + 'function-name': { color: '#111111' }, + 'tag-name': { color: '#7A4FD4' }, + 'pseudo-class': { color: '#7A4FD4' }, + 'pseudo-element': { color: '#7A4FD4' }, + 'html-tag': { color: '#7A4FD4' }, + 'json-key': { color: '#0C7C6B' }, + 'error': { color: '#B33EE5' }, + invalid: { color: '#B33EE5' }, + deletedCode: { color: '#B33EE5' }, + insert: { background: '#F4F4F0' }, + delete: { background: '#F4F4F0' }, + }; + const theme = { border: darkMode ? "border-[#222222]" : "border-[#D1D1D1]", headerBg: darkMode ? "bg-[#050505]" : "bg-[#EAEAEA]", - codeBg: darkMode ? "bg-[#111111]" : "bg-[#F4F4F0]", - text: darkMode ? "text-amber-500" : "text-amber-700", - iconHover: darkMode ? "hover:text-cyan-400" : "hover:text-indigo-600", + codeBg: darkMode ? "bg-[#050505]" : "bg-[#F4F4F0]", + text: darkMode ? "text-zinc-200" : "text-zinc-700", + iconHover: darkMode ? "hover:text-zinc-100" : "hover:text-indigo-600", }; // Render a seamless skeleton placeholder while server is syncing with client @@ -84,12 +212,13 @@ export default function ClientCodeBlock({ > {code} diff --git a/frontend/app/components/DirectoryList.tsx b/frontend/app/components/DirectoryList.tsx index d5c4d6d..2b67965 100644 --- a/frontend/app/components/DirectoryList.tsx +++ b/frontend/app/components/DirectoryList.tsx @@ -2,7 +2,8 @@ "use client"; import Link from "next/link"; -import { REFERENCE_DATA } from "../page"; // Assuming you export your data from page.tsx or a data file +import { cloneElement } from "react"; +import { REFERENCE_DATA } from "../page"; import { ArrowRight } from "lucide-react"; interface DirectoryListProps { @@ -14,10 +15,6 @@ export default function DirectoryList({ darkMode, searchQuery, }: DirectoryListProps) { - const borderTheme = darkMode ? "border-[#333333]" : "border-[#111111]"; - const hoverTheme = darkMode ? "hover:bg-[#1A1A1A]" : "hover:bg-[#EAEAEA]"; - - // Filter logic const filteredData = REFERENCE_DATA.map((section) => ({ ...section, items: section.items.filter((item) => @@ -27,64 +24,66 @@ export default function DirectoryList({ if (filteredData.length === 0) { return ( -
- [ 0 Results Found ] +
+ No matching entries found.
); } return ( -
- {filteredData.map((section, index) => ( -
- {/* Section Header */} -
-
- - {String(index + 1).padStart(2, "0")} - -

- {section.category} -

+
+ {filteredData.map((section) => ( +
+
+
+ {cloneElement(section.icon, { + className: "block", + style: { color: darkMode ? "#f4f4f5" : "#111111" }, + })}
+

+ {section.category} +

- {/* Ledger Rows (No Cards) */} -
- {section.items.map((item) => ( - - {/* Left Side: Title & Desc */} -
-

- {item.name} -

-

- {item.desc} -

-
+
+ {section.items.map((item) => { + const iconNode = cloneElement(item.icon, { + className: "block", + style: { + color: darkMode ? "#f4f4f5" : "#111111", + opacity: 1, + }, + }); + + return ( + +
+
+ {iconNode} +
+ +
+ Open + +
+
- {/* Right Side: Action */} -
-
- View Docs +
+

{item.name}

+

+ {item.desc} +

- -
- - ))} + + ); + })}
-
+
))}
); diff --git a/frontend/app/components/MdxTypography.tsx b/frontend/app/components/MdxTypography.tsx index 3d87267..516537a 100644 --- a/frontend/app/components/MdxTypography.tsx +++ b/frontend/app/components/MdxTypography.tsx @@ -1,30 +1,58 @@ // app/components/MdxTypography.tsx "use client"; +import React from "react"; import { useTheme } from "../../src/context/ThemeContext"; +const getHeadingId = (children: React.ReactNode) => { + const text = React.Children.toArray(children) + .map((child) => { + if (typeof child === "string" || typeof child === "number") return String(child); + return ""; + }) + .join("") + .trim(); + + return text + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, "") + .trim() + .replace(/\s+/g, "-"); +}; + export const MdxH2 = (props: any) => { const { darkMode } = useTheme(); + const { className = "", style, children, ...rest } = props; + const headingId = props.id || getHeadingId(children); + return (

+ id={headingId} + className={`mt-16 mb-6 block w-full box-border break-words border-b border-current/10 pb-4 text-3xl font-semibold tracking-[-0.05em] text-current md:text-4xl ${ + darkMode ? "text-zinc-100" : "text-zinc-900" + } ${className}`.trim()} + style={{ scrollMarginTop: "1rem", ...style }} + {...rest} + > + {children} +

); }; export const MdxH3 = (props: any) => { const { darkMode } = useTheme(); + const { children, ...rest } = props; + const headingId = props.id || getHeadingId(children); + return (

+ {...rest} + > + {children} +

); }; @@ -32,8 +60,8 @@ export const MdxP = (props: any) => { const { darkMode } = useTheme(); return (

@@ -44,8 +72,8 @@ export const MdxUl = (props: any) => { const { darkMode } = useTheme(); return (

    diff --git a/frontend/app/components/SkeletonTopic.tsx b/frontend/app/components/SkeletonTopic.tsx index 41acbbf..a94cebe 100644 --- a/frontend/app/components/SkeletonTopic.tsx +++ b/frontend/app/components/SkeletonTopic.tsx @@ -1,27 +1,43 @@ export default function SkeletonTopic({ topicKey }: { topicKey: string }) { return ( -
    -
    -

    - The content isn't created yet! -

    -

    We're working on it — thanks for the patience.

    +
    +
    +
    +
    + Draft section +
    +
    -

    - In the meantime, you can check out the project's author: - - @Yash-pluto - -

    +
    +
    +

    + This topic is still being written. +

    +

    + The reference for {topicKey} is queued up for later, and it will be expanded into a full guide with examples, explanations, and patterns. +

    +
    -

    - Requested topic: {topicKey} -

    +
    + Examples + Patterns + Best practices +
    + +
    +

    + Until then, you can explore the rest of the library or check the project author: + + @Yash-pluto + +

    +
    +
    ); diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 2836d3b..b6740c4 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -1,12 +1,9 @@ /* app/globals.css */ @import "tailwindcss"; -/* Wire up the custom Next.js fonts to Tailwind V4 */ @theme { - --font-sans: var(--font-outfit), ui-sans-serif, system-ui, sans-serif; - --font-mono: - var(--font-jetbrains-mono), ui-monospace, SFMono-Regular, Menlo, Monaco, - Consolas, monospace; + --font-sans: "Segoe UI", Inter, "Helvetica Neue", Arial, sans-serif; + --font-mono: "SFMono-Regular", "Consolas", "Liberation Mono", monospace; } :root { @@ -17,6 +14,7 @@ ::-webkit-scrollbar { display: none; } + html { scrollbar-width: none; } @@ -25,14 +23,14 @@ html { pre code { white-space: pre-wrap !important; word-wrap: break-word !important; - font-family: var( - --font-jetbrains-mono - ) !important; /* Force all code blocks to use JetBrains */ + font-family: var(--font-jetbrains-mono) !important; + /* Force all code blocks to use JetBrains */ font-size: 0.9em; } /* 3. SUBTLE TEXT SELECTION COLOR */ ::selection { - background-color: rgba(99, 102, 241, 0.2); /* Soft indigo highlight */ + background-color: rgba(99, 102, 241, 0.2); + /* Soft indigo highlight */ color: inherit; -} +} \ No newline at end of file diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 9f46825..baa0442 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,23 +1,8 @@ // app/layout.tsx import type { Metadata } from "next"; -import { Outfit, JetBrains_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "../src/context/ThemeContext"; -// The premium geometric sans-serif font -const outfit = Outfit({ - variable: "--font-outfit", - subsets: ["latin"], - display: "swap", -}); - -// The gold-standard developer monospace font -const jetbrainsMono = JetBrains_Mono({ - variable: "--font-jetbrains-mono", - subsets: ["latin"], - display: "swap", -}); - export const metadata: Metadata = { title: "RefMe_ | Technical Reference for Modern Developers", description: @@ -59,10 +44,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + , + category: "Core languages", + icon: , + items: [ + { name: "C++", href: "/cpp", icon: , desc: "Low-level systems, performance, and STL patterns.", size: "normal" }, + { name: "JavaScript", href: "/javascript", icon: , desc: "Modern syntax, async flows, and browser fundamentals.", size: "large" }, + { name: "Python", href: "/python", icon: , desc: "Data work, automation, and clean backend scripting.", size: "normal" }, + { name: "TypeScript", href: "/typescript", icon: , desc: "Types, interfaces, and safer large-scale JavaScript systems.", size: "normal" }, + { name: "Go", href: "/go", icon: , desc: "Simple concurrency, tooling, and service-oriented code.", size: "normal" }, + { name: "Rust", href: "/rust", icon: , desc: "Ownership, performance, and safety-first systems design.", size: "normal" }, + { name: "Java", href: "/java", icon: , desc: "Enterprise patterns, JVM tooling, and backend architecture.", size: "normal" }, + { name: "C#", href: "/csharp", icon: , desc: "Strong typing, .NET frameworks, and application structure.", size: "normal" }, + ], + }, + { + category: "Frontend & web", + icon: , + items: [ + { name: "React", href: "/react", icon: , desc: "Components, state, effects, and rendering patterns.", size: "large" }, + { name: "Next.js", href: "/nextjs", icon: , desc: "App routing, server rendering, and performance patterns.", size: "normal" }, + { name: "CSS", href: "/css", icon: , desc: "Layout, design systems, and modern responsive styling.", size: "normal" }, + { name: "HTML", href: "/html", icon: , desc: "Structure, semantics, and accessible document foundations.", size: "normal" }, + { name: "Tailwind", href: "/tailwind", icon: , desc: "Utility-first styling, tokens, and rapid UI composition.", size: "normal" }, + { name: "Accessibility", href: "/accessibility", icon: , desc: "Semantic structure, keyboard flows, and inclusive UI.", size: "normal" }, + ], + }, + { + category: "Backend & systems", + icon: , items: [ - { - name: "ChatGPT", - href: "/chatgpt", - theme: "emerald", - icon: , - desc: "Prompt engineering & API reference.", - size: "large", - }, - { - name: "Claude Code", - href: "/claude", - theme: "orange", - icon: , - desc: "Anthropic's CLI and SDK patterns.", - size: "normal", - }, - { - name: "Cursor", - href: "/cursor", - theme: "blue", - icon: , - desc: "AI-first IDE workflows.", - size: "normal", - }, - { - name: "Gemini", - href: "/gemini", - theme: "sky", - icon: , - desc: "Google's multi-modal intelligence.", - size: "normal", - }, + { name: "Bash", href: "/bash", icon: , desc: "Shell workflows, pipelines, and automation basics.", size: "normal" }, + { name: "Node.js", href: "/nodejs", icon: , desc: "Runtime fundamentals and server-side JavaScript patterns.", size: "normal" }, + { name: "SQL", href: "/sql", icon: , desc: "Queries, joins, indexing, and data modeling essentials.", size: "normal" }, + { name: "APIs", href: "/apis", icon: , desc: "Request patterns, auth flows, and contract design.", size: "normal" }, + { name: "Docker", href: "/docker", icon: , desc: "Containers, images, and deployment isolation basics.", size: "normal" }, + { name: "Git", href: "/git", icon: , desc: "Branching, commits, and collaboration workflows.", size: "normal" }, ], }, { - category: "Core Programming", - icon: , + category: "Concepts", + icon: , items: [ - { - name: "Python", - href: "/python", - theme: "yellow", - icon: , - desc: "Data science, ML, and backend scripting.", - size: "large", - }, - { - name: "JavaScript", - href: "/javascript", - theme: "yellow", - icon: , - desc: "The universal language of the web.", - size: "normal", - }, - { - name: "TypeScript", - href: "/typescript", - theme: "blue", - icon: , - desc: "Type-safe JavaScript for scale.", - size: "normal", - }, - { - name: "Go", - href: "/go", - theme: "cyan", - icon: , - desc: "High-performance concurrency.", - size: "normal", - }, - { - name: "Rust", - href: "/rust", - theme: "orange", - icon: , - desc: "Memory safety without garbage collection.", - size: "normal", - }, - { - name: "C++", - href: "/cpp", - theme: "indigo", - icon: , - desc: "System-level performance and control.", - size: "normal", - }, + { name: "Async", href: "/async", icon: , desc: "Promises, async/await, event loops, and scheduling.", size: "normal" }, + { name: "Testing", href: "/testing", icon: , desc: "Unit tests, integration coverage, and reliability habits.", size: "normal" }, + { name: "Security", href: "/security", icon: , desc: "Auth, validation, and safer defaults for production code.", size: "normal" }, + { name: "Data Structures", href: "/data-structures", icon: , desc: "Arrays, trees, hash maps, and choosing the right shape.", size: "normal" }, + { name: "Algorithms", href: "/algorithms", icon: , desc: "Sorting, traversal, recursion, and complexity trade-offs.", size: "normal" }, + { name: "System Design", href: "/system-design", icon: , desc: "Scalability, resilience, and architecture composition.", size: "normal" }, + { name: "State Management", href: "/state-management", icon: , desc: "Global UI state, immutability, and predictable app flows.", size: "normal" }, + { name: "OAuth", href: "/oauth", icon: , desc: "Delegated access, tokens, and identity flows in real apps.", size: "normal" }, + { name: "Caching", href: "/caching", icon: , desc: "Read-through patterns, invalidation, and latency reduction.", size: "normal" }, + { name: "Web Security", href: "/web-security", icon: , desc: "XSS, CSRF, headers, and safer web defaults.", size: "normal" }, + { name: "Design Patterns", href: "/design-patterns", icon: , desc: "Factory, strategy, observer, and reusable software shapes.", size: "normal" }, + { name: "Dependency Injection", href: "/dependency-injection", icon: , desc: "Loose coupling, wiring, and testable architecture.", size: "normal" }, + { name: "Validation", href: "/validation", icon: , desc: "Input checks, schema rules, and safer user data flows.", size: "normal" }, ], }, { - category: "Infrastructure & CLI", - icon: , + category: "Architecture & infrastructure", + icon: , items: [ - { - name: "Docker", - href: "/docker", - theme: "sky", - icon: , - desc: "Containerization and compose structures.", - size: "large", - }, - { - name: "Bash", - href: "/bash", - theme: "zinc", - icon: , - desc: "Unix shell scripting essentials.", - size: "normal", - }, - { - name: "Kubernetes", - href: "/kubernetes", - theme: "indigo", - icon: , - desc: "Orchestration & Helm charts.", - size: "normal", - }, - { - name: "PowerShell", - href: "/powershell", - theme: "blue", - icon: , - desc: "Windows automation and configuration.", - size: "normal", - }, + { name: "Microservices", href: "/microservices", icon: , desc: "Service boundaries, contracts, and deployment autonomy.", size: "normal" }, + { name: "Event-Driven", href: "/event-driven", icon: , desc: "Streams, decoupling, and asynchronous communication patterns.", size: "normal" }, + { name: "Load Balancing", href: "/load-balancing", icon: , desc: "Traffic distribution, resilience, and horizontal scale.", size: "normal" }, + { name: "Kubernetes", href: "/kubernetes", icon: , desc: "Container orchestration, scaling, and service management.", size: "normal" }, + { name: "Deployment", href: "/deployment", icon: , desc: "Release flow, environments, rollout safety, and automation.", size: "normal" }, + { name: "Monitoring", href: "/monitoring", icon: , desc: "Metrics, logs, traces, and runtime health visibility.", size: "normal" }, + { name: "Cloud Architecture", href: "/cloud-architecture", icon: , desc: "Multi-region design, edge services, and resilient platforms.", size: "normal" }, + { name: "Databases", href: "/databases", icon: , desc: "Storage models, schemas, indexes, and query trade-offs.", size: "normal" }, + { name: "Reliability", href: "/reliability", icon: , desc: "Failover, redundancy, and designing for graceful degradation.", size: "normal" }, ], }, { - category: "Web & Styling", - icon: , + category: "Data & AI", + icon: , items: [ - { - name: "React", - href: "/react", - theme: "sky", - icon: , - desc: "Hooks, patterns, and state management.", - size: "large", - }, - { - name: "Tailwind CSS", - href: "/tailwind", - theme: "cyan", - icon: , - desc: "Utility classes and configuration.", - size: "normal", - }, - { - name: "CSS 3", - href: "/css3", - theme: "blue", - icon: , - desc: "Modern semantic web layouts.", - size: "normal", - }, - { - name: "HTML 5", - href: "/html5", - theme: "orange", - icon: , - desc: "Document structure and accessibility.", - size: "normal", - }, + { name: "Data Science", href: "/data-science", icon: , desc: "Analysis, notebooks, and patterns for working with data.", size: "normal" }, + { name: "ML", href: "/ml", icon: , desc: "Model basics, pipelines, and practical deployment notes.", size: "normal" }, + { name: "LLMs", href: "/llms", icon: , desc: "Prompt design, structured outputs, and app integration basics.", size: "normal" }, + { name: "Vectors", href: "/vectors", icon: , desc: "Embeddings, similarity search, and retrieval patterns.", size: "normal" }, + { name: "Prompt Engineering", href: "/prompt-engineering", icon: , desc: "Instruction design, context framing, and reliable outputs.", size: "normal" }, + { name: "RAG", href: "/rag", icon: , desc: "Grounded generation with retrieval and relevance filtering.", size: "normal" }, + { name: "Model Evaluation", href: "/model-evaluation", icon: , desc: "Benchmarking, scoring, and quality checks for outputs.", size: "normal" }, + { name: "AI Safety", href: "/ai-safety", icon: , desc: "Guardrails, risk mitigation, and responsible model usage.", size: "normal" }, + { name: "Agents", href: "/agents", icon: , desc: "Tool use, planning loops, and autonomous execution patterns.", size: "normal" }, + ], + }, + { + category: "Dev tools", + icon: , + items: [ + { name: "VS Code", href: "/vscode", icon: , desc: "Editor workflows, extensions, and keyboard efficiency.", size: "normal" }, + { name: "Linux", href: "/linux", icon: , desc: "Filesystems, processes, permissions, and command-line habits.", size: "normal" }, + { name: "CI/CD", href: "/ci-cd", icon: , desc: "Pipelines, automation, and release safety checks.", size: "normal" }, + { name: "Observability", href: "/observability", icon: , desc: "Logging, tracing, and debugging production systems.", size: "normal" }, + { name: "Nginx", href: "/nginx", icon: , desc: "Reverse proxying, performance tuning, and request routing.", size: "normal" }, + { name: "Terraform", href: "/terraform", icon: , desc: "Infrastructure as code, repeatable environments, and provisioning.", size: "normal" }, + { name: "GitHub Actions", href: "/github-actions", icon: , desc: "CI pipelines, automation, and shipping code confidently.", size: "normal" }, + { name: "Webpack", href: "/webpack", icon: , desc: "Bundling, module resolution, and frontend build optimization.", size: "normal" }, + { name: "Vercel", href: "/vercel", icon: , desc: "Edge deployment, previews, and modern app delivery.", size: "normal" }, + { name: "Debugging", href: "/debugging", icon: , desc: "Root cause analysis, traces, breakpoints, and narrowing bugs.", size: "normal" }, ], }, ]; @@ -197,111 +130,92 @@ export default function RefMeHero() { const [searchQuery, setSearchQuery] = useState(""); const theme = { - page: darkMode - ? "bg-[#0A0A0A] text-[#E5E5E5]" - : "bg-[#F4F4F0] text-[#111111]", - border: darkMode ? "border-[#222222]" : "border-[#D1D1D1]", - accent: darkMode ? "text-cyan-400" : "text-indigo-600", + page: darkMode ? "bg-[#050505] text-[#f5f5f5]" : "bg-[#f3f0ea] text-[#111111]", + panel: darkMode ? "bg-[#0d0d0d] border-[#1f1f1f]" : "bg-[#f8f6f2] border-[#e1dfd8]", + soft: darkMode ? "bg-[#121212] border-[#262626]" : "bg-[#f3f1ec] border-[#e1dfd8]", + border: darkMode ? "border-[#1f1f1f]" : "border-[#e1dfd8]", + muted: darkMode ? "text-[#9b9b9b]" : "text-[#666a73]", + accent: darkMode ? "text-zinc-200" : "text-zinc-700", + dim: darkMode ? "text-[#b8b8b8]" : "text-[#4d4d4d]", }; return ( -
    - {/* LEFT PANE: Sticky Hero & Search */} -
    -
    - {/* Header */} -
    -

    - REF -
    - +
    +

    - -
    - {/* Description (Syntax Highlighted Text) */} -

    - > SYSTEM.INIT() -

    -

    - A curated collection of{" "} - cheat sheets,{" "} - workflows, and{" "} - - architectural patterns - - . No fluff, no endless scrolling—just the{" "} - raw syntax you - need to build. -

    +
    +
    + Developer reference library +
    - {/* Terminal Search Input */} -
    - - $> - - setSearchQuery(e.target.value)} - className='w-full bg-transparent outline-none font-mono text-lg placeholder-opacity-30 placeholder-current' - /> - -
    -
    - {/* Footer Meta */} -
    -
    - Built by Yash Vardhan. -
    - - - GitHub - - - - LinkedIn - +

    + Clear reference material for modern engineering work. +

    +
    + +

    + Find the syntax, patterns, and implementation notes you need without digging through scattered docs. +

    + +
    + + + + Explore + + +
    +
    + +
    + Documentation-first + • + Concise notes + • + Built for speed
    - - Status: Online - -
    -
    + - {/* RIGHT PANE: Scrolling Directory */} -
    - +
    + +
    ); diff --git a/frontend/src/lib/mdx.ts b/frontend/src/lib/mdx.ts index 7f88269..38bc772 100644 --- a/frontend/src/lib/mdx.ts +++ b/frontend/src/lib/mdx.ts @@ -3,9 +3,41 @@ import fs from "fs"; import path from "path"; import matter from "gray-matter"; +export type DocHeading = { + id: string; + title: string; + depth: number; +}; + // This safely resolves the 'content' directory at the root of your Next.js app const contentDir = path.join(process.cwd(), "content"); +export function extractHeadings(content: string): DocHeading[] { + return content + .split("\n") + .reduce((acc, line) => { + const match = line.match(/^(##|###)\s+(.+)$/); + + if (!match) return acc; + + const depth = match[1].length === 2 ? 2 : 3; + const title = match[2] + .trim() + .replace(/[*_`>#]/g, "") + .replace(/\s+/g, " "); + const id = title + .toLowerCase() + .replace(/[^a-z0-9\s-]/g, "") + .trim() + .replace(/\s+/g, "-"); + + if (!id) return acc; + + acc.push({ id, title, depth }); + return acc; + }, []); +} + export function getTopicBySlug(slug: string) { const fullPath = path.join(contentDir, `${slug}.mdx`); @@ -15,7 +47,7 @@ export function getTopicBySlug(slug: string) { const fileContents = fs.readFileSync(fullPath, "utf8"); const { data, content } = matter(fileContents); - return { frontmatter: data, content }; + return { frontmatter: data, content, headings: extractHeadings(content) }; } export function getAllTopics() {