coding-friend-cli 1.1.1 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +44 -6
  2. package/dist/{chunk-AQXTNLQD.js → chunk-6OI37OZX.js} +9 -1
  3. package/dist/chunk-CSF4FAHL.js +129 -0
  4. package/dist/{chunk-KZT4AFDW.js → chunk-Q4DKU5IG.js} +4 -6
  5. package/dist/dev-MAAWPWML.js +290 -0
  6. package/dist/{host-JBTJCWM2.js → host-2WINWEW7.js} +2 -2
  7. package/dist/index.js +44 -6
  8. package/dist/{init-E6CL3UZQ.js → init-CTCDQKIQ.js} +24 -13
  9. package/dist/{mcp-MWESK6UX.js → mcp-43HCE2KD.js} +2 -2
  10. package/dist/postinstall.js +1 -1
  11. package/dist/{statusline-7D6YU5YM.js → statusline-ARI7I5YM.js} +1 -1
  12. package/dist/{update-IH3G4SN5.js → update-GGCBM7U4.js} +91 -40
  13. package/lib/learn-host/.prettierignore +4 -0
  14. package/lib/learn-host/.prettierrc +8 -0
  15. package/lib/learn-host/CHANGELOG.md +14 -0
  16. package/lib/learn-host/README.md +114 -0
  17. package/lib/learn-host/eslint.config.mjs +6 -0
  18. package/lib/learn-host/next-env.d.ts +1 -1
  19. package/lib/learn-host/next.config.ts +4 -0
  20. package/lib/learn-host/package-lock.json +6039 -391
  21. package/lib/learn-host/package.json +30 -15
  22. package/lib/learn-host/public/logo.svg +1 -0
  23. package/lib/learn-host/src/app/[category]/[slug]/page.tsx +36 -32
  24. package/lib/learn-host/src/app/[category]/page.tsx +2 -3
  25. package/lib/learn-host/src/app/apple-icon.svg +1 -0
  26. package/lib/learn-host/src/app/globals.css +74 -14
  27. package/lib/learn-host/src/app/icon.svg +1 -0
  28. package/lib/learn-host/src/app/layout.tsx +29 -9
  29. package/lib/learn-host/src/app/page.tsx +9 -11
  30. package/lib/learn-host/src/components/Breadcrumbs.tsx +12 -4
  31. package/lib/learn-host/src/components/DocCard.tsx +28 -10
  32. package/lib/learn-host/src/components/MarkdownRenderer.tsx +6 -2
  33. package/lib/learn-host/src/components/MobileNav.tsx +43 -35
  34. package/lib/learn-host/src/components/PagefindSearch.tsx +177 -54
  35. package/lib/learn-host/src/components/Sidebar.tsx +27 -29
  36. package/lib/learn-host/src/components/TableOfContents.tsx +62 -0
  37. package/lib/learn-host/src/components/TagBadge.tsx +1 -1
  38. package/lib/learn-host/src/components/ThemeToggle.tsx +36 -9
  39. package/lib/learn-host/src/components/layout/Footer.tsx +41 -0
  40. package/lib/learn-host/src/components/layout/Header.tsx +117 -0
  41. package/lib/learn-host/src/lib/docs.ts +98 -8
  42. package/lib/learn-host/src/lib/types.ts +7 -1
  43. package/lib/learn-host/tsconfig.json +8 -2
  44. package/lib/learn-host/tsconfig.tsbuildinfo +1 -0
  45. package/lib/learn-mcp/CHANGELOG.md +12 -0
  46. package/lib/learn-mcp/README.md +169 -0
  47. package/lib/learn-mcp/package.json +2 -1
  48. package/lib/learn-mcp/src/index.ts +1 -1
  49. package/lib/learn-mcp/src/lib/docs.ts +1 -3
  50. package/lib/learn-mcp/src/lib/knowledge.ts +2 -1
  51. package/lib/learn-mcp/src/tools/get-review-list.ts +1 -4
  52. package/lib/learn-mcp/src/tools/search-docs.ts +1 -4
  53. package/package.json +14 -6
  54. package/dist/chunk-VHZQ6KEU.js +0 -73
  55. package/lib/learn-host/src/app/search/page.tsx +0 -19
  56. package/lib/learn-host/src/components/SearchBar.tsx +0 -36
@@ -1,31 +1,46 @@
1
1
  {
2
2
  "name": "coding-friend-learn-host",
3
- "version": "1.0.0",
3
+ "version": "0.0.2",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "next dev",
6
+ "dev": "next dev -p 3333",
7
7
  "build": "next build",
8
8
  "postbuild": "pagefind --site .next/server/app --output-path public/_pagefind",
9
- "start": "next start"
9
+ "start": "next start",
10
+ "lint": "eslint src/",
11
+ "lint:fix": "eslint src/ --fix",
12
+ "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
13
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\""
10
14
  },
11
15
  "dependencies": {
12
- "next": "^15.1.0",
13
- "react": "^19.0.0",
14
- "react-dom": "^19.0.0",
16
+ "@radix-ui/react-dialog": "^1.1.15",
17
+ "@radix-ui/react-visually-hidden": "^1.2.4",
18
+ "babel-plugin-react-compiler": "^1.0.0",
19
+ "cmdk": "^1.1.1",
20
+ "gray-matter": "^4.0.3",
21
+ "next": "^16.1.6",
22
+ "next-themes": "^0.4.0",
23
+ "react": "^19.2.4",
24
+ "react-dom": "^19.2.4",
15
25
  "react-markdown": "^9.0.0",
16
- "remark-gfm": "^4.0.0",
17
26
  "rehype-highlight": "^7.0.0",
18
- "gray-matter": "^4.0.3",
19
- "next-themes": "^0.4.0"
27
+ "rehype-slug": "^6.0.0",
28
+ "remark-gfm": "^4.0.0"
20
29
  },
21
30
  "devDependencies": {
22
- "typescript": "^5.7.0",
23
- "@types/node": "^22.0.0",
24
- "@types/react": "^19.0.0",
25
- "@types/react-dom": "^19.0.0",
26
- "tailwindcss": "^4.0.0",
31
+ "@eslint/js": "^9.39.3",
27
32
  "@tailwindcss/postcss": "^4.0.0",
28
33
  "@tailwindcss/typography": "^0.5.0",
29
- "pagefind": "^1.3.0"
34
+ "@types/node": "^22.0.0",
35
+ "@types/react": "^19.2.0",
36
+ "@types/react-dom": "^19.2.0",
37
+ "eslint": "^9.39.3",
38
+ "eslint-config-next": "^16.1.6",
39
+ "eslint-config-prettier": "^10.1.8",
40
+ "pagefind": "^1.3.0",
41
+ "prettier": "^3.8.1",
42
+ "prettier-plugin-tailwindcss": "^0.7.2",
43
+ "tailwindcss": "^4.0.0",
44
+ "typescript": "^5.7.0"
30
45
  }
31
46
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
@@ -1,6 +1,7 @@
1
- import { getAllDocs, getDocBySlug } from "@/lib/docs";
1
+ import { getAllDocs, getDocBySlug, extractHeadings } from "@/lib/docs";
2
2
  import Breadcrumbs from "@/components/Breadcrumbs";
3
3
  import MarkdownRenderer from "@/components/MarkdownRenderer";
4
+ import TableOfContents from "@/components/TableOfContents";
4
5
  import TagBadge from "@/components/TagBadge";
5
6
  import { notFound } from "next/navigation";
6
7
 
@@ -12,7 +13,6 @@ export async function generateStaticParams() {
12
13
  }
13
14
 
14
15
  export const dynamicParams = true;
15
- export const revalidate = 10;
16
16
 
17
17
  export default async function DocPage({
18
18
  params,
@@ -24,39 +24,43 @@ export default async function DocPage({
24
24
  if (!doc) notFound();
25
25
 
26
26
  const categoryDisplay = category.replace(/[_-]/g, " ");
27
+ const headings = extractHeadings(doc.content);
27
28
 
28
29
  return (
29
- <article data-pagefind-body>
30
- <Breadcrumbs
31
- crumbs={[
32
- { label: categoryDisplay, href: `/${category}/` },
33
- { label: doc.frontmatter.title },
34
- ]}
35
- />
36
-
37
- <header className="mb-8">
38
- <h1 className="text-3xl font-bold mb-2">{doc.frontmatter.title}</h1>
39
- <div className="flex flex-wrap items-center gap-3 text-sm text-gray-500 dark:text-gray-400">
40
- <span className="capitalize px-2 py-0.5 bg-gray-100 dark:bg-gray-800 rounded">
41
- {categoryDisplay}
42
- </span>
43
- {doc.frontmatter.created && (
44
- <span>Created: {doc.frontmatter.created}</span>
30
+ <div className="flex w-full">
31
+ <article className="min-w-0 flex-1" data-pagefind-body>
32
+ <Breadcrumbs
33
+ crumbs={[
34
+ { label: categoryDisplay, href: `/${category}/` },
35
+ { label: doc.frontmatter.title },
36
+ ]}
37
+ />
38
+
39
+ <header className="mb-8">
40
+ <h1 className="mb-2 text-3xl font-bold">{doc.frontmatter.title}</h1>
41
+ {(doc.frontmatter.created || doc.frontmatter.updated) && (
42
+ <div className="flex flex-wrap items-center gap-3 text-sm text-slate-500 dark:text-slate-400">
43
+ {doc.frontmatter.created && (
44
+ <span>Created: {doc.frontmatter.created}</span>
45
+ )}
46
+ {doc.frontmatter.updated && (
47
+ <span>Updated: {doc.frontmatter.updated}</span>
48
+ )}
49
+ </div>
45
50
  )}
46
- {doc.frontmatter.updated && (
47
- <span>Updated: {doc.frontmatter.updated}</span>
51
+ {doc.frontmatter.tags.length > 0 && (
52
+ <div className="mt-3 flex flex-wrap gap-1.5">
53
+ {doc.frontmatter.tags.map((tag) => (
54
+ <TagBadge key={tag} tag={tag} />
55
+ ))}
56
+ </div>
48
57
  )}
49
- </div>
50
- {doc.frontmatter.tags.length > 0 && (
51
- <div className="flex flex-wrap gap-1.5 mt-3">
52
- {doc.frontmatter.tags.map((tag) => (
53
- <TagBadge key={tag} tag={tag} />
54
- ))}
55
- </div>
56
- )}
57
- </header>
58
-
59
- <MarkdownRenderer content={doc.content} />
60
- </article>
58
+ </header>
59
+
60
+ <MarkdownRenderer content={doc.content} />
61
+ </article>
62
+
63
+ <TableOfContents headings={headings} />
64
+ </div>
61
65
  );
62
66
  }
@@ -7,7 +7,6 @@ export async function generateStaticParams() {
7
7
  }
8
8
 
9
9
  export const dynamicParams = true;
10
- export const revalidate = 10;
11
10
 
12
11
  export default async function CategoryPage({
13
12
  params,
@@ -21,8 +20,8 @@ export default async function CategoryPage({
21
20
  return (
22
21
  <div>
23
22
  <Breadcrumbs crumbs={[{ label: displayName }]} />
24
- <h1 className="text-2xl font-bold capitalize mb-1">{displayName}</h1>
25
- <p className="text-gray-500 dark:text-gray-400 mb-6">
23
+ <h1 className="mb-1 text-2xl font-bold capitalize">{displayName}</h1>
24
+ <p className="mb-6 text-slate-500 dark:text-slate-400">
26
25
  {docs.length} {docs.length === 1 ? "doc" : "docs"}
27
26
  </p>
28
27
 
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
@@ -4,28 +4,88 @@
4
4
  @custom-variant dark (&:where(.dark, .dark *));
5
5
 
6
6
  @theme {
7
- --color-primary: #3b82f6;
8
- --color-primary-dark: #60a5fa;
9
- --color-surface: #ffffff;
10
- --color-surface-dark: #111827;
11
- --color-surface-secondary: #f9fafb;
12
- --color-surface-secondary-dark: #1f2937;
13
- --color-border: #e5e7eb;
14
- --color-border-dark: #374151;
15
- --color-text: #111827;
16
- --color-text-dark: #f9fafb;
17
- --color-text-muted: #6b7280;
18
- --color-text-muted-dark: #9ca3af;
7
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
8
+
9
+ /* Warm slate scale (dark palette — from website) */
10
+ --color-navy-800: #31353f;
11
+ --color-navy-900: #2a2d38;
12
+ --color-navy-950: #23262e;
13
+
14
+ /* Violet accent (from website) */
15
+ --color-accent: #7c3aed;
16
+ --color-accent-light: #a78bfa;
17
+ --color-accent-dark: #6d28d9;
18
+
19
+ --color-surface: #2a2d38;
20
+ --color-surface-secondary: #31353f;
21
+ --color-border: rgba(160, 160, 160, 0.11);
22
+ --color-text: #f8fafc;
23
+ --color-text-muted: #94a3b8;
24
+ --color-link: #58adfb;
19
25
  }
20
26
 
21
27
  html {
22
28
  scroll-behavior: smooth;
23
29
  }
24
30
 
31
+ @media (prefers-reduced-motion: reduce) {
32
+ html {
33
+ scroll-behavior: auto;
34
+ }
35
+ *,
36
+ *::before,
37
+ *::after {
38
+ animation-duration: 0.01ms !important;
39
+ animation-iteration-count: 1 !important;
40
+ transition-duration: 0.01ms !important;
41
+ }
42
+ }
43
+
44
+ /* Scrollbar */
45
+ *::-webkit-scrollbar {
46
+ width: 8px;
47
+ height: 8px;
48
+ }
49
+ *::-webkit-scrollbar-thumb {
50
+ background: #d1d5db4c;
51
+ border-radius: 10px;
52
+ }
53
+ *::-webkit-scrollbar-thumb:hover {
54
+ background: #d1d5dbce;
55
+ }
56
+ *::-webkit-scrollbar-track {
57
+ background: transparent;
58
+ }
59
+
60
+ /* Hide scrollbar utility */
61
+ .scrollbar-none::-webkit-scrollbar {
62
+ display: none;
63
+ }
64
+
25
65
  /* Syntax highlighting */
26
- pre code.hljs {
27
- padding: 1rem;
66
+ pre code {
28
67
  border-radius: 0.5rem;
29
68
  font-size: 0.875rem;
30
69
  line-height: 1.7;
70
+
71
+ &.hljs {
72
+ padding: 0 !important;
73
+ }
74
+ }
75
+
76
+ pre {
77
+ padding: 1rem;
78
+ background: #0d1117;
79
+ }
80
+
81
+ :where(code):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
82
+ font-weight: 500;
83
+ }
84
+
85
+ :not(pre) > code {
86
+ background: none !important;
87
+ border: 1px solid #a0a0a06b;
88
+ font-size: 85%;
89
+ padding: 1px 5px;
90
+ border-radius: 4px;
31
91
  }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="512" height="512" x="0" y="0" viewBox="0 0 64 64" style="enable-background:new 0 0 512 512" xml:space="preserve" class=""><g><linearGradient id="a" x1="78.644" x2="25.425" y1="74.123" y2="11.863" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#b45309"></stop><stop offset="1" stop-color="#f59e0b"></stop></linearGradient><path fill="url(#a)" fill-rule="evenodd" d="M2 14C2 7.373 7.373 2 14 2h36c6.627 0 12 5.373 12 12v28c0 6.627-5.373 12-12 12H39.908l-6.403 7.317a2 2 0 0 1-3.01 0L24.093 54H14C7.373 54 2 48.627 2 42z" clip-rule="evenodd" opacity="1" data-original="url(#a)" class=""></path><path fill="#ffffff" d="M32 14a2 2 0 0 1 1.95 1.556l1.107 4.867a6 6 0 0 0 4.52 4.52l4.867 1.107a2 2 0 0 1 0 3.9l-4.867 1.107a6 6 0 0 0-4.52 4.52l-1.107 4.866a2 2 0 0 1-3.9 0l-1.107-4.866a6 6 0 0 0-4.52-4.52l-4.866-1.107a2 2 0 0 1 0-3.9l4.866-1.107a6 6 0 0 0 4.52-4.52l1.107-4.866A2 2 0 0 1 32 14z"></path></g></svg>
@@ -1,36 +1,56 @@
1
1
  import type { Metadata } from "next";
2
2
  import { ThemeProvider } from "next-themes";
3
3
  import { getAllCategories } from "@/lib/docs";
4
+ import Header from "@/components/layout/Header";
5
+ import Footer from "@/components/layout/Footer";
4
6
  import Sidebar from "@/components/Sidebar";
5
7
  import MobileNav from "@/components/MobileNav";
6
8
  import "./globals.css";
7
9
  import "highlight.js/styles/github-dark.css";
8
10
 
9
- export const revalidate = 10;
10
-
11
11
  export const metadata: Metadata = {
12
12
  title: "Learning Notes",
13
13
  description: "Personal learning knowledge base",
14
14
  };
15
15
 
16
- export default function RootLayout({ children }: { children: React.ReactNode }) {
16
+ export default function RootLayout({
17
+ children,
18
+ }: {
19
+ children: React.ReactNode;
20
+ }) {
17
21
  const categories = getAllCategories();
18
22
 
19
23
  return (
20
- <html lang="en" suppressHydrationWarning>
21
- <body className="bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
24
+ <html lang="en" data-scroll-behavior="smooth" suppressHydrationWarning>
25
+ <head>
26
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
27
+ <link
28
+ rel="preconnect"
29
+ href="https://fonts.gstatic.com"
30
+ crossOrigin="anonymous"
31
+ />
32
+ <link
33
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap"
34
+ rel="stylesheet"
35
+ />
36
+ </head>
37
+ <body className="dark:bg-navy-900 bg-white text-slate-900 antialiased dark:text-slate-50">
22
38
  <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
23
- <div data-pagefind-ignore>
39
+ <Header categories={categories} />
40
+ <div data-pagefind-ignore className="md:hidden">
24
41
  <MobileNav categories={categories} />
25
42
  </div>
26
43
  <div className="flex">
27
44
  <div data-pagefind-ignore>
28
45
  <Sidebar categories={categories} />
29
46
  </div>
30
- <main className="flex-1 min-h-screen p-6 md:p-8 max-w-4xl">
31
- {children}
32
- </main>
47
+ <div className="flex min-w-0 flex-1 justify-center md:pl-64 lg:pl-[300px]">
48
+ <main className="min-h-screen w-full max-w-5xl p-6 pb-24 md:p-8 md:pb-24">
49
+ {children}
50
+ </main>
51
+ </div>
33
52
  </div>
53
+ <Footer />
34
54
  </ThemeProvider>
35
55
  </body>
36
56
  </html>
@@ -3,8 +3,6 @@ import DocCard from "@/components/DocCard";
3
3
  import TagBadge from "@/components/TagBadge";
4
4
  import Link from "next/link";
5
5
 
6
- export const revalidate = 10;
7
-
8
6
  export default function HomePage() {
9
7
  const categories = getAllCategories();
10
8
  const docs = getAllDocs();
@@ -13,25 +11,25 @@ export default function HomePage() {
13
11
 
14
12
  return (
15
13
  <div>
16
- <h1 className="text-3xl font-bold mb-2">Learning Notes</h1>
17
- <p className="text-gray-500 dark:text-gray-400 mb-8">
14
+ <h1 className="mb-2 text-3xl font-bold">Learning Notes</h1>
15
+ <p className="mb-8 text-slate-500 dark:text-slate-400">
18
16
  {docs.length} docs across {categories.length} categories
19
17
  </p>
20
18
 
21
19
  {/* Categories */}
22
20
  <section className="mb-8">
23
- <h2 className="text-lg font-semibold mb-3">Categories</h2>
24
- <div className="grid grid-cols-2 sm:grid-cols-3 gap-3">
21
+ <h2 className="mb-3 text-lg font-semibold">Categories</h2>
22
+ <div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
25
23
  {categories.map((cat) => (
26
24
  <Link
27
25
  key={cat.name}
28
26
  href={`/${cat.name}/`}
29
- className="p-3 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600 transition-colors"
27
+ className="rounded-lg border border-slate-200 p-3 transition-colors hover:border-violet-400/50 dark:border-[#a0a0a01c] dark:hover:border-violet-400/50"
30
28
  >
31
- <div className="font-medium capitalize text-gray-900 dark:text-gray-100">
29
+ <div className="font-medium text-slate-900 capitalize dark:text-slate-100">
32
30
  {cat.name.replace(/[_-]/g, " ")}
33
31
  </div>
34
- <div className="text-sm text-gray-500 dark:text-gray-400">
32
+ <div className="text-sm text-slate-500 dark:text-slate-400">
35
33
  {cat.docCount} {cat.docCount === 1 ? "doc" : "docs"}
36
34
  </div>
37
35
  </Link>
@@ -42,7 +40,7 @@ export default function HomePage() {
42
40
  {/* Tags */}
43
41
  {tags.length > 0 && (
44
42
  <section className="mb-8">
45
- <h2 className="text-lg font-semibold mb-3">Tags</h2>
43
+ <h2 className="mb-3 text-lg font-semibold">Tags</h2>
46
44
  <div className="flex flex-wrap gap-2">
47
45
  {tags.slice(0, 20).map(({ tag }) => (
48
46
  <TagBadge key={tag} tag={tag} />
@@ -53,7 +51,7 @@ export default function HomePage() {
53
51
 
54
52
  {/* Recent Docs */}
55
53
  <section>
56
- <h2 className="text-lg font-semibold mb-3">Recently Updated</h2>
54
+ <h2 className="mb-3 text-lg font-semibold">Recently Updated</h2>
57
55
  <div className="grid gap-3">
58
56
  {recentDocs.map((doc) => (
59
57
  <DocCard key={`${doc.category}/${doc.slug}`} doc={doc} />
@@ -7,19 +7,27 @@ interface Crumb {
7
7
 
8
8
  export default function Breadcrumbs({ crumbs }: { crumbs: Crumb[] }) {
9
9
  return (
10
- <nav className="flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 mb-4">
11
- <Link href="/" className="hover:text-blue-600 dark:hover:text-blue-400">
10
+ <nav className="mb-4 flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400">
11
+ <Link
12
+ href="/"
13
+ className="hover:text-violet-600 dark:hover:text-violet-400"
14
+ >
12
15
  Home
13
16
  </Link>
14
17
  {crumbs.map((crumb, i) => (
15
18
  <span key={i} className="flex items-center gap-2">
16
19
  <span>/</span>
17
20
  {crumb.href ? (
18
- <Link href={crumb.href} className="hover:text-blue-600 dark:hover:text-blue-400 capitalize">
21
+ <Link
22
+ href={crumb.href}
23
+ className="capitalize hover:text-violet-600 dark:hover:text-violet-400"
24
+ >
19
25
  {crumb.label}
20
26
  </Link>
21
27
  ) : (
22
- <span className="text-gray-700 dark:text-gray-300">{crumb.label}</span>
28
+ <span className="text-slate-700 dark:text-slate-300">
29
+ {crumb.label}
30
+ </span>
23
31
  )}
24
32
  </span>
25
33
  ))}
@@ -1,32 +1,50 @@
1
+ "use client";
2
+
1
3
  import Link from "next/link";
4
+ import { useRouter } from "next/navigation";
2
5
  import TagBadge from "./TagBadge";
3
6
  import type { DocMeta } from "@/lib/types";
4
7
 
5
8
  export default function DocCard({ doc }: { doc: DocMeta }) {
9
+ const router = useRouter();
10
+ const href = `/${doc.category}/${doc.slug}/`;
11
+
6
12
  return (
7
- <Link
8
- href={`/${doc.category}/${doc.slug}/`}
9
- className="block p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-blue-300 dark:hover:border-blue-600 hover:shadow-sm transition-all bg-white dark:bg-gray-800/50"
13
+ <div
14
+ role="link"
15
+ tabIndex={0}
16
+ onClick={() => router.push(href)}
17
+ onKeyDown={(e) => {
18
+ if (e.key === "Enter") router.push(href);
19
+ }}
20
+ className="dark:bg-navy-800/50 block cursor-pointer rounded-lg border border-slate-200 bg-white p-4 transition-all hover:border-violet-400/50 dark:border-[#a0a0a01c] dark:hover:border-violet-400/50"
10
21
  >
11
- <h3 className="font-semibold text-gray-900 dark:text-gray-100 mb-1">
12
- {doc.frontmatter.title}
22
+ <h3 className="mb-1 font-semibold text-slate-900 dark:text-slate-100">
23
+ <Link href={href} className="hover:underline">
24
+ {doc.frontmatter.title}
25
+ </Link>
13
26
  </h3>
14
- <p className="text-sm text-gray-500 dark:text-gray-400 mb-2 line-clamp-2">
27
+ <p className="mb-2 line-clamp-2 text-sm text-slate-500 dark:text-slate-400">
15
28
  {doc.excerpt}
16
29
  </p>
17
30
  <div className="flex items-center justify-between">
18
- <div className="flex gap-1 flex-wrap">
31
+ <div
32
+ className="flex flex-wrap items-center gap-2"
33
+ onClick={(e) => e.stopPropagation()}
34
+ >
19
35
  {doc.frontmatter.tags.slice(0, 3).map((tag) => (
20
36
  <TagBadge key={tag} tag={tag} />
21
37
  ))}
22
38
  {doc.frontmatter.tags.length > 3 && (
23
- <span className="text-xs text-gray-400">+{doc.frontmatter.tags.length - 3}</span>
39
+ <span className="text-xs text-slate-400">
40
+ +{doc.frontmatter.tags.length - 3}
41
+ </span>
24
42
  )}
25
43
  </div>
26
- <span className="text-xs text-gray-400">
44
+ <span className="text-xs text-slate-400">
27
45
  {doc.frontmatter.updated || doc.frontmatter.created}
28
46
  </span>
29
47
  </div>
30
- </Link>
48
+ </div>
31
49
  );
32
50
  }
@@ -1,11 +1,15 @@
1
1
  import ReactMarkdown from "react-markdown";
2
2
  import remarkGfm from "remark-gfm";
3
3
  import rehypeHighlight from "rehype-highlight";
4
+ import rehypeSlug from "rehype-slug";
4
5
 
5
6
  export default function MarkdownRenderer({ content }: { content: string }) {
6
7
  return (
7
- <div className="prose prose-gray dark:prose-invert max-w-none prose-headings:scroll-mt-20 prose-a:text-blue-600 dark:prose-a:text-blue-400 prose-code:before:content-none prose-code:after:content-none">
8
- <ReactMarkdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeHighlight]}>
8
+ <div className="prose prose-slate prose-headings:scroll-mt-20 prose-a:text-link prose-code:before:content-none prose-code:after:content-none dark:prose-invert dark:prose-a:text-link max-w-none">
9
+ <ReactMarkdown
10
+ remarkPlugins={[remarkGfm]}
11
+ rehypePlugins={[rehypeHighlight, rehypeSlug]}
12
+ >
9
13
  {content}
10
14
  </ReactMarkdown>
11
15
  </div>
@@ -1,55 +1,63 @@
1
1
  "use client";
2
2
 
3
3
  import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
4
5
  import { useState } from "react";
5
6
  import type { CategoryInfo } from "@/lib/types";
6
- import SearchBar from "./SearchBar";
7
- import ThemeToggle from "./ThemeToggle";
8
7
 
9
- export default function MobileNav({ categories }: { categories: CategoryInfo[] }) {
8
+ export default function MobileNav({
9
+ categories,
10
+ }: {
11
+ categories: CategoryInfo[];
12
+ }) {
13
+ const pathname = usePathname();
10
14
  const [open, setOpen] = useState(false);
11
15
 
12
16
  return (
13
- <div className="md:hidden">
14
- <div className="flex items-center justify-between p-4 border-b border-gray-200 dark:border-gray-700">
15
- <Link href="/" className="font-bold text-gray-900 dark:text-gray-100">
16
- Learning Notes
17
- </Link>
18
- <div className="flex items-center gap-2">
19
- <ThemeToggle />
20
- <button
21
- onClick={() => setOpen(!open)}
22
- className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800"
23
- aria-label="Toggle menu"
24
- >
25
- <svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
26
- {open ? (
27
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
28
- ) : (
29
- <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h16" />
30
- )}
31
- </svg>
32
- </button>
33
- </div>
34
- </div>
17
+ <div className="dark:bg-navy-950 border-b border-slate-200 bg-slate-50 dark:border-[#a0a0a01c]">
18
+ <button
19
+ onClick={() => setOpen(!open)}
20
+ className="flex w-full cursor-pointer items-center justify-between px-4 py-3 text-sm font-medium text-slate-700 dark:text-slate-300"
21
+ >
22
+ <span>Navigation</span>
23
+ <svg
24
+ className={`h-4 w-4 transition-transform ${open ? "rotate-180" : ""}`}
25
+ fill="none"
26
+ viewBox="0 0 24 24"
27
+ stroke="currentColor"
28
+ >
29
+ <path
30
+ strokeLinecap="round"
31
+ strokeLinejoin="round"
32
+ strokeWidth={2}
33
+ d="M19 9l-7 7-7-7"
34
+ />
35
+ </svg>
36
+ </button>
35
37
 
36
38
  {open && (
37
- <div className="p-4 border-b border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
38
- <SearchBar />
39
- <nav className="mt-4 space-y-1">
40
- {categories.map((cat) => (
39
+ <nav className="max-h-[60vh] space-y-0.5 overflow-y-auto px-4 pb-4">
40
+ {categories.map((cat) => {
41
+ const isActive = pathname === `/${cat.name}/`;
42
+ return (
41
43
  <Link
42
44
  key={cat.name}
43
45
  href={`/${cat.name}/`}
44
46
  onClick={() => setOpen(false)}
45
- className="flex items-center justify-between px-3 py-2 text-sm rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300"
47
+ className={`flex items-center justify-between rounded-md px-3 py-1.5 text-sm capitalize ${
48
+ isActive
49
+ ? "font-medium text-violet-600 dark:text-violet-400"
50
+ : "text-slate-600 dark:text-slate-400"
51
+ }`}
46
52
  >
47
- <span className="capitalize">{cat.name.replace(/[_-]/g, " ")}</span>
48
- <span className="text-xs text-gray-400">{cat.docCount}</span>
53
+ <span>{cat.name.replace(/[_-]/g, " ")}</span>
54
+ <span className="text-xs text-slate-400 dark:text-slate-500">
55
+ {cat.docCount}
56
+ </span>
49
57
  </Link>
50
- ))}
51
- </nav>
52
- </div>
58
+ );
59
+ })}
60
+ </nav>
53
61
  )}
54
62
  </div>
55
63
  );