diff --git a/.claude/settings.json b/.claude/settings.json
index 3e603bb..4e595ab 100644
--- a/.claude/settings.json
+++ b/.claude/settings.json
@@ -1,23 +1,9 @@
{
- "extraKnownMarketplaces": {
- "playwright-skill": {
- "source": {
- "source": "github",
- "repo": "lackeyjb/playwright-skill"
- }
- }
- },
-
"permissions": {
- "deny": ["Read(**/.env)", "Read(**/.envrc)"],
- "ask": [],
-
"allow": [
"mcp__ide__getDiagnostics",
-
"Bash(claude mcp get:*)",
"Bash(claude mcp list)",
-
"mcp__playwright__browser_click",
"mcp__playwright__browser_close",
"mcp__playwright__browser_console_messages",
@@ -31,7 +17,6 @@
"mcp__playwright__browser_tabs",
"mcp__playwright__browser_take_screenshot",
"mcp__playwright__browser_wait_for",
-
"Bash(cat:*)",
"Bash(echo:*)",
"Bash(find:*)",
@@ -41,13 +26,11 @@
"Bash(tree:*)",
"Bash(wc:*)",
"Bash(xargs:*)",
-
"Bash(git log:*)",
"Bash(gh pr checks:*)",
"Bash(gh pr list:*)",
"Bash(gh run list:*)",
"Bash(gh run view:*)",
-
"Bash(npm run build)",
"Bash(npm run dev)",
"Bash(npm run start)",
@@ -61,7 +44,6 @@
"Bash(npx @biomejs/biome:*)",
"Bash(npx lefthook:*)",
"Bash(npx playwright:*)",
-
"Bash(vercel --help)",
"Bash(vercel env --help)",
"Bash(vercel env ls:*)",
@@ -71,7 +53,6 @@
"Bash(vercel open)",
"Bash(vercel project ls:*)",
"Bash(vercel whoami)",
-
"WebFetch(domain:biomejs.dev)",
"WebFetch(domain:docs.github.com)",
"WebFetch(domain:github.com)",
@@ -82,6 +63,36 @@
"WebFetch(domain:ui.shadcn.com)",
"WebFetch(domain:vercel.com)",
"WebFetch(domain:vitest.dev)"
- ]
+ ],
+ "deny": ["Read(**/.env)", "Read(**/.envrc)"],
+ "ask": []
+ },
+ "enabledPlugins": {
+ "playwright-skill@playwright-skill": false,
+ "skill-creator@my-claude-plugins": false,
+ "frontend-design@my-claude-plugins": false,
+ "tailwindcss@my-claude-plugins": false,
+ "shadcn-ui@my-claude-plugins": true,
+ "feature-dev@claude-plugins-official": true
+ },
+ "extraKnownMarketplaces": {
+ "playwright-skill": {
+ "source": {
+ "source": "github",
+ "repo": "lackeyjb/playwright-skill"
+ }
+ },
+ "my-claude-plugins": {
+ "source": {
+ "source": "github",
+ "repo": "michellepace/my-claude-plugins"
+ }
+ },
+ "claude-plugins-official": {
+ "source": {
+ "source": "github",
+ "repo": "anthropics/claude-plugins-official"
+ }
+ }
}
}
diff --git a/CLAUDE.md b/CLAUDE.md
index b035d8c..e41422b 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,5 +1,7 @@
# CLAUDE.md
+**DevFlow** — A community-driven platform for asking and answering programming questions. Get help, share knowledge, and collaborate with developers from around the world. (Similar to Stack Overflow)
+
The project uses British English - strictly.
## Tech Stack
@@ -29,6 +31,17 @@ vercel env ls # Check env vars are configured
vercel whoami # Verify CLI is authenticated
```
+## shadcn/ui CLI
+
+```bash
+npx shadcn@latest list @shadcn # List all available components
+npx shadcn@latest search @shadcn -q "nav" # Search components by query
+npx shadcn@latest view button card # Preview code before installing
+npx shadcn@latest add # Add component to project
+npx shadcn@latest add button --overwrite # Overwrite existing component
+npx shadcn@latest add @v0/ # Add from v0.dev registry
+```
+
## Coding Practices
- Only add `"use client"` when interactivity is needed
diff --git a/app/(auth)/layout.tsx b/app/(auth)/layout.tsx
index 10b6e03..6b5a4ed 100644
--- a/app/(auth)/layout.tsx
+++ b/app/(auth)/layout.tsx
@@ -1,6 +1,6 @@
const AuthLayout = ({ children }: { children: React.ReactNode }) => {
return (
-
+
{children}
);
diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx
index 703415d..1d879b5 100644
--- a/app/(auth)/sign-in/[[...sign-in]]/page.tsx
+++ b/app/(auth)/sign-in/[[...sign-in]]/page.tsx
@@ -4,9 +4,5 @@ import { ClerkSignIn } from "@/components/auth/clerk-signin";
export default async function SignInPage() {
await connection();
- return (
-
-
-
- );
+ return ;
}
diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx
index feee066..bdd9d31 100644
--- a/app/(auth)/sign-up/[[...sign-up]]/page.tsx
+++ b/app/(auth)/sign-up/[[...sign-up]]/page.tsx
@@ -4,9 +4,5 @@ import { ClerkSignUp } from "@/components/auth/clerk-signup";
export default async function SignUpPage() {
await connection();
- return (
-
-
-
- );
+ return ;
}
diff --git a/app/(root)/(no-right-sidebar)/community/page.tsx b/app/(root)/(no-right-sidebar)/community/page.tsx
new file mode 100644
index 0000000..863d62f
--- /dev/null
+++ b/app/(root)/(no-right-sidebar)/community/page.tsx
@@ -0,0 +1,3 @@
+export default function CommunityPage() {
+ return Community
;
+}
diff --git a/app/(root)/(no-right-sidebar)/jobs/page.tsx b/app/(root)/(no-right-sidebar)/jobs/page.tsx
new file mode 100644
index 0000000..ec6d850
--- /dev/null
+++ b/app/(root)/(no-right-sidebar)/jobs/page.tsx
@@ -0,0 +1,3 @@
+export default function JobsPage() {
+ return Find Jobs
;
+}
diff --git a/app/(root)/(no-right-sidebar)/layout.tsx b/app/(root)/(no-right-sidebar)/layout.tsx
new file mode 100644
index 0000000..34cae74
--- /dev/null
+++ b/app/(root)/(no-right-sidebar)/layout.tsx
@@ -0,0 +1,7 @@
+// Full-width layout without right sidebar
+// Layout: [LeftSidebar (sticky)] [Main Content]
+const NoRightSidebarLayout = ({ children }: { children: React.ReactNode }) => {
+ return {children};
+};
+
+export default NoRightSidebarLayout;
diff --git a/app/(root)/(no-right-sidebar)/profile/page.tsx b/app/(root)/(no-right-sidebar)/profile/page.tsx
new file mode 100644
index 0000000..e84a25b
--- /dev/null
+++ b/app/(root)/(no-right-sidebar)/profile/page.tsx
@@ -0,0 +1,3 @@
+export default function ProfilePage() {
+ return Profile
;
+}
diff --git a/app/(root)/(no-right-sidebar)/tags/page.tsx b/app/(root)/(no-right-sidebar)/tags/page.tsx
new file mode 100644
index 0000000..4f47ff8
--- /dev/null
+++ b/app/(root)/(no-right-sidebar)/tags/page.tsx
@@ -0,0 +1,3 @@
+export default function TagsPage() {
+ return Tags
;
+}
diff --git a/app/(root)/(with-right-sidebar)/ask-question/page.tsx b/app/(root)/(with-right-sidebar)/ask-question/page.tsx
new file mode 100644
index 0000000..cc92fe1
--- /dev/null
+++ b/app/(root)/(with-right-sidebar)/ask-question/page.tsx
@@ -0,0 +1,3 @@
+export default function AskQuestionPage() {
+ return Ask a Question
;
+}
diff --git a/app/(root)/(with-right-sidebar)/collections/page.tsx b/app/(root)/(with-right-sidebar)/collections/page.tsx
new file mode 100644
index 0000000..2a084a1
--- /dev/null
+++ b/app/(root)/(with-right-sidebar)/collections/page.tsx
@@ -0,0 +1,3 @@
+export default function CollectionsPage() {
+ return Collections
;
+}
diff --git a/app/(root)/(with-right-sidebar)/layout.tsx b/app/(root)/(with-right-sidebar)/layout.tsx
new file mode 100644
index 0000000..ad0517f
--- /dev/null
+++ b/app/(root)/(with-right-sidebar)/layout.tsx
@@ -0,0 +1,11 @@
+// TODO: Add for tag widgets, hot questions, etc.
+// Layout: [LeftSidebar (sticky)] [Main Content] [RightSidebar]
+const WithRightSidebarLayout = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ return {children};
+};
+
+export default WithRightSidebarLayout;
diff --git a/app/(root)/(with-right-sidebar)/page.tsx b/app/(root)/(with-right-sidebar)/page.tsx
new file mode 100644
index 0000000..4aa7c66
--- /dev/null
+++ b/app/(root)/(with-right-sidebar)/page.tsx
@@ -0,0 +1,31 @@
+const Home = () => (
+ <>
+ Hello Root page with heading H1
+
+ {/* Header boundary marker */}
+ HEADER: {"words ".repeat(50)}
+
+ {/* Flexbox demo: grow vs flex-none */}
+
+ {/* Fixed width - won't grow */}
+
+ flex-none
+
+
+ {/* Grows to fill available space */}
+
+ grow
+
+
+ {/* Fixed width - won't grow */}
+
+ flex-none
+
+
+
+ {/* Footer boundary marker */}
+ FOOTER: {"words ".repeat(50)}
+ >
+);
+
+export default Home;
diff --git a/app/(root)/page.tsx b/app/(root)/page.tsx
deleted file mode 100644
index c30eff3..0000000
--- a/app/(root)/page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-const Home = () => (
- <>
- Welcome to the world of Next.js
- >
-);
-
-export default Home;
diff --git a/app/globals.css b/app/globals.css
index ccf5d7f..80cc9ff 100644
--- a/app/globals.css
+++ b/app/globals.css
@@ -29,6 +29,7 @@
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
+ --color-mobile-nav: var(--mobile-nav);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
@@ -54,7 +55,14 @@
:root {
/* Creates CSS variables available to all elements (no utility generation) */
+ /* Only defined in root, not .dark */
--radius: 0.625rem;
+ --primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
+ --gradient-primary: linear-gradient(
+ 93.22deg,
+ var(--primary) -13.95%,
+ var(--primary-gradient-to) 99.54%
+ );
--background: oklch(0.994 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
@@ -64,7 +72,7 @@
--popover-foreground: oklch(0.129 0.042 264.695);
--primary: oklch(0.7089 0.1967 46.81);
--primary-foreground: oklch(1 0 0);
- --secondary: oklch(0.968 0.007 247.896);
+ --secondary: oklch(0.9147 0.0205 264.47); /* #DCE3F1 - Figma */
--secondary-foreground: oklch(0.208 0.042 265.755);
--muted: oklch(0.968 0.007 247.896);
--muted-foreground: oklch(0.554 0.046 257.417);
@@ -79,7 +87,7 @@
--chart-3: oklch(0.398 0.07 227.392);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);
- --sidebar: oklch(1 0 0); /* #FFFFFF */
+ --sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
@@ -87,15 +95,11 @@
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-ring: oklch(0.704 0.04 256.788);
- --primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
- --gradient-primary: linear-gradient(
- 93.22deg,
- var(--primary) -13.95%,
- var(--primary-gradient-to) 99.54%
- );
+ --mobile-nav: oklch(1 0 0); /* #FFFFFF */
/* Centralised logo theming */
--logo-full-themed: url("/images/logo-light.svg");
+ --auth-bg: url("/images/auth-bg-light.webp");
}
.dark {
@@ -108,7 +112,7 @@
--popover-foreground: oklch(0.984 0.003 247.858);
--primary: oklch(0.7089 0.1967 46.81);
--primary-foreground: oklch(1 0 0);
- --secondary: oklch(0.279 0.041 260.031);
+ --secondary: oklch(0.2728 0.0257 265.4); /* #212734 - Figma */
--secondary-foreground: oklch(0.984 0.003 247.858);
--muted: oklch(0.279 0.041 260.031);
--muted-foreground: oklch(0.704 0.04 256.788);
@@ -123,7 +127,7 @@
--chart-3: oklch(0.769 0.188 70.08);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);
- --sidebar: oklch(0.178 0.013 270.6); /* #0F1117 */
+ --sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
@@ -131,9 +135,11 @@
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-ring: oklch(0.551 0.027 264.364);
+ --mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
/* Centralised logo theming (dark) */
--logo-full-themed: url("/images/logo-dark.svg");
+ --auth-bg: url("/images/auth-bg-dark.webp");
}
@layer base {
@@ -187,3 +193,11 @@
@apply overflow-x-auto rounded-lg bg-muted p-4 text-sm;
}
}
+
+@layer utilities {
+ /* Custom SVG icons use hardcoded white fill. Since loads them as external
+ files, CSS currentColor won't work. Invert to black in light mode instead. */
+ .invert-colors {
+ @apply invert dark:invert-0;
+ }
+}
diff --git a/app/old.globals.css b/app/old.globals.css
deleted file mode 100644
index 1aff2bd..0000000
--- a/app/old.globals.css
+++ /dev/null
@@ -1,397 +0,0 @@
-@import "tailwindcss";
-
-body {
- font-family: "Inter", sans-serif;
-}
-
-:root {
- --radius: 8px;
-}
-
-@layer utilities {
- .background-light850_dark100 {
- @apply bg-light-850 dark:bg-dark-100;
- }
-
- .background-light900_dark200 {
- @apply bg-light-900 dark:bg-dark-200;
- }
-
- .background-light900_dark300 {
- @apply bg-light-900 dark:bg-dark-300;
- }
-
- .background-light800_darkgradient {
- @apply bg-light-800 dark:dark-gradient;
- }
-
- .background-light800_dark400 {
- @apply bg-light-800 dark:bg-dark-400 !important;
- }
-
- .background-light700_dark400 {
- @apply bg-light-700 dark:bg-dark-400;
- }
-
- .background-light700_dark300 {
- @apply bg-light-700 dark:bg-dark-300;
- }
-
- .background-light800_dark400 {
- @apply bg-light-800 dark:bg-dark-400;
- }
-
- .background-light800_dark300 {
- @apply bg-light-800 dark:bg-dark-300 !important;
- }
-
- .background-light800_dark200 {
- @apply bg-light-800 dark:bg-dark-200;
- }
-
- .background-dark400_light900 {
- @apply dark:bg-dark-400 bg-light-900 !important;
- }
-
- .text-dark100_light900 {
- @apply text-dark-100 dark:text-light-900 !important;
- }
-
- .text-dark200_light900 {
- @apply text-dark-200 dark:text-light-900;
- }
-
- .text-dark200_light800 {
- @apply text-dark-200 dark:text-light-800 !important;
- }
-
- .text-dark300_light700 {
- @apply text-dark-300 dark:text-light-700;
- }
-
- .text-dark400_light700 {
- @apply text-dark-400 dark:text-light-700;
- }
-
- .text-dark500_light700 {
- @apply text-dark-500 dark:text-light-700 !important;
- }
-
- .text-dark500_light500 {
- @apply text-dark-500 dark:text-light-500;
- }
-
- .text-dark500_light400 {
- @apply text-dark-500 dark:text-light-400;
- }
-
- .text-dark300_light900 {
- @apply text-dark-300 dark:text-light-900 !important;
- }
-
- .text-dark400_light800 {
- @apply text-dark-400 dark:text-light-800;
- }
-
- .text-light400_light500 {
- @apply text-light-400 dark:text-light-500 !important;
- }
-
- .text-dark400_light500 {
- @apply text-dark-400 dark:text-light-500;
- }
-
- .text-dark400_light900 {
- @apply text-dark-400 dark:text-light-900 !important;
- }
-
- .text-light400_light500 {
- @apply text-light-400 dark:text-light-500 !important;
- }
-
- .light-border {
- @apply border-light-800 dark:border-dark-300;
- }
-
- .light-border-2 {
- @apply border-light-700 dark:border-dark-400 !important;
- }
-
- .h1-bold {
- @apply text-[30px] font-bold leading-[42px] tracking-tighter;
- }
-
- .h2-bold {
- @apply text-[24px] font-bold leading-[31.2px];
- }
-
- .h2-semibold {
- @apply text-[24px] font-semibold leading-[31.2px];
- }
-
- .h3-bold {
- @apply text-[20px] font-bold leading-[26px];
- }
-
- .h3-semibold {
- @apply text-[20px] font-semibold leading-[24.8px];
- }
-
- .base-medium {
- @apply text-[18px] font-medium leading-[25.2px];
- }
-
- .base-semibold {
- @apply text-[18px] font-semibold leading-[25.2px];
- }
-
- .base-bold {
- @apply text-[18px] font-bold leading-[140%];
- }
-
- .paragraph-regular {
- @apply text-[16px] font-normal leading-[22.4px];
- }
-
- .paragraph-medium {
- @apply text-[16px] font-medium leading-[22.4px];
- }
-
- .paragraph-semibold {
- @apply text-[16px] font-semibold leading-[20.8px];
- }
-
- .body-regular {
- @apply text-[14px] font-normal leading-[19.6px];
- }
-
- .body-medium {
- @apply text-[14px] font-medium leading-[18.2px];
- }
-
- .body-semibold {
- @apply text-[14px] font-semibold leading-[18.2px];
- }
-
- .body-bold {
- @apply text-[14px] font-bold leading-[18.2px];
- }
-
- .small-regular {
- @apply text-[12px] font-normal leading-[15.6px];
- }
-
- .small-medium {
- @apply text-[12px] font-medium leading-[15.6px];
- }
-
- .small-semibold {
- @apply text-[12px] font-semibold leading-[15.6px];
- }
-
- .subtle-medium {
- @apply text-[10px] font-medium leading-[13px] !important;
- }
-
- .subtle-regular {
- @apply text-[10px] font-normal leading-[13px];
- }
-
- .placeholder {
- @apply placeholder:text-light-400 dark:placeholder:text-light-500;
- }
-
- .invert-colors {
- @apply invert dark:invert-0;
- }
-
- .shadow-light100_dark100 {
- @apply shadow-light-100 dark:shadow-dark-100;
- }
-
- .shadow-light100_darknone {
- @apply shadow-light-100 dark:shadow-none;
- }
-
- .primary-gradient {
- background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%);
- }
-
- .dark-gradient {
- background: linear-gradient(
- 232deg,
- rgba(23, 28, 35, 0.41) 0%,
- rgba(19, 22, 28, 0.7) 100%
- );
- }
-
- .light-gradient {
- background: linear-gradient(
- 132deg,
- rgba(247, 249, 255, 0.5) 0%,
- rgba(229, 237, 255, 0.25) 100%
- );
- }
-
- .primary-text-gradient {
- background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%);
- background-clip: text;
- -webkit-background-clip: text;
- -webkit-text-fill-color: transparent;
- }
-
- .flex-center {
- @apply flex justify-center items-center;
- }
-
- .flex-between {
- @apply flex justify-between items-center;
- }
-
- .flex-start {
- @apply flex justify-start items-center;
- }
-
- .card-wrapper {
- @apply bg-light-900 dark:dark-gradient shadow-light-100 dark:shadow-dark-100;
- }
-
- .btn {
- @apply bg-light-800 dark:bg-dark-300 !important;
- }
-
- .btn-secondary {
- @apply bg-light-800 dark:bg-dark-400 !important;
- }
-
- .btn-tertiary {
- @apply bg-light-700 dark:bg-dark-300 !important;
- }
-
- .no-focus {
- @apply focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 !important;
- }
-
- .markdown {
- @apply max-w-full prose dark:prose-p:text-light-700 dark:prose-ol:text-light-700 dark:prose-ul:text-light-500 dark:prose-strong:text-white dark:prose-headings:text-white prose-headings:text-dark-400 prose-h1:text-dark-300 prose-h2:text-dark-300 prose-p:text-dark-500 prose-ul:text-dark-500 prose-ol:text-dark-500;
- }
-
- .markdown-editor {
- @apply prose max-w-full prose-p:m-0 dark:prose-headings:text-white prose-headings:text-dark-400 prose-p:text-dark-500 dark:prose-p:text-light-700 prose-ul:text-dark-500 dark:prose-ul:text-light-700 prose-ol:text-dark-500 dark:prose-ol:text-light-700 dark:prose-strong:text-white prose-blockquote:text-dark-500 dark:prose-blockquote:text-light-700;
- }
-
- .tab {
- @apply min-h-full dark:bg-dark-400 bg-light-800 text-light-500 dark:data-[state=active]:bg-dark-300 data-[state=active]:bg-primary-100 data-[state=active]:text-primary-500 !important;
- }
-
- .dark-gradient {
- background: linear-gradient(
- 232deg,
- rgba(23, 28, 35, 0.41) 0%,
- rgba(19, 22, 28, 0.7) 100%
- );
- }
-}
-
-.custom-scrollbar::-webkit-scrollbar {
- width: 3px;
- height: 3px;
- border-radius: 2px;
-}
-
-.custom-scrollbar::-webkit-scrollbar-track {
- background: #ffffff;
-}
-
-.custom-scrollbar::-webkit-scrollbar-thumb {
- background: #888;
- border-radius: 50px;
-}
-
-.custom-scrollbar::-webkit-scrollbar-thumb:hover {
- background: #555;
-}
-
-/* Hide scrollbar for Chrome, Safari and Opera */
-.no-scrollbar::-webkit-scrollbar {
- display: none;
-}
-
-/* Hide scrollbar for IE, Edge and Firefox */
-.no-scrollbar {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
-}
-
-.active-theme {
- filter: invert(53%) sepia(98%) saturate(3332%) hue-rotate(0deg)
- brightness(104%) contrast(106%);
-}
-
-.hash-span {
- margin-top: -140px;
- padding-bottom: 140px;
- display: block;
-}
-
-.mdxeditor-toolbar {
- background: #ffffff;
-}
-
-.dark .mdxeditor-toolbar {
- background: #151821;
-}
-
-.dark .mdxeditor-toolbar button svg {
- color: #858ead;
-}
-
-.dark .mdxeditor-toolbar button:hover svg {
- color: #000;
-}
-
-.dark .mdxeditor-toolbar [role="separator"] {
- border-color: #555;
-}
-
-.markdown a {
- color: #1da1f2;
-}
-
-.markdown a,
-code {
- /* These are technically the same, but use both */
- overflow-wrap: break-word;
- word-wrap: break-word;
-
- -ms-word-break: break-all;
- /* Use break-word for better compatibility (non-standard but safer than break-all) */
- word-break: break-word;
-
- /* Adds a hyphen where the word breaks, if supported (No Blink) */
- -ms-hyphens: auto;
- -moz-hyphens: auto;
- -webkit-hyphens: auto;
- hyphens: auto;
-
- padding: 2px;
- color: #ff7000;
-}
-
-.markdown pre {
- display: grid;
- width: 100%;
-}
-
-.markdown pre code {
- width: 100%;
- display: block;
- overflow-x: auto;
-
- color: inherit;
-}
-
-[data-lexical-editor="true"] {
- height: 350px;
- overflow-y: auto;
-}
diff --git a/components/navigation/constants.ts b/components/navigation/constants.ts
new file mode 100644
index 0000000..1a66ff9
--- /dev/null
+++ b/components/navigation/constants.ts
@@ -0,0 +1,19 @@
+export type NavLink = {
+ imgURL: string;
+ route: string;
+ label: string;
+};
+
+export const NAV_LINKS = [
+ { imgURL: "/icons/home.svg", route: "/", label: "Home" },
+ { imgURL: "/icons/users.svg", route: "/community", label: "Community" },
+ { imgURL: "/icons/star.svg", route: "/collections", label: "Collections" },
+ { imgURL: "/icons/suitcase.svg", route: "/jobs", label: "Find Jobs" },
+ { imgURL: "/icons/tag.svg", route: "/tags", label: "Tags" },
+ {
+ imgURL: "/icons/question.svg",
+ route: "/ask-question",
+ label: "Ask a question",
+ },
+ { imgURL: "/icons/user.svg", route: "/profile", label: "Profile" },
+] as const satisfies readonly NavLink[];
diff --git a/components/navigation/full-logo.tsx b/components/navigation/full-logo.tsx
new file mode 100644
index 0000000..0c663a0
--- /dev/null
+++ b/components/navigation/full-logo.tsx
@@ -0,0 +1,27 @@
+import { cn } from "@/lib/utils";
+
+// Logo SVG dimensions - centralised to avoid magic numbers
+const LOGO_WIDTH = 137;
+const LOGO_HEIGHT = 23;
+
+interface ThemeLogoProps {
+ className?: string;
+}
+
+/**
+ * Theme-aware logo that switches between light/dark variants via CSS variable.
+ * Uses --logo-full-themed defined in globals.css.
+ */
+export function ThemeLogo({ className }: ThemeLogoProps) {
+ return (
+
+ );
+}
diff --git a/components/navigation/mobile-nav.tsx b/components/navigation/mobile-nav.tsx
new file mode 100644
index 0000000..947fd6e
--- /dev/null
+++ b/components/navigation/mobile-nav.tsx
@@ -0,0 +1,112 @@
+"use client";
+
+import {
+ SignedIn,
+ SignedOut,
+ SignInButton,
+ SignOutButton,
+ SignUpButton,
+} from "@clerk/nextjs";
+import { LogOut, Menu, X } from "lucide-react";
+import Link from "next/link";
+import { useState } from "react";
+import { NAV_LINKS } from "@/components/navigation/constants";
+import { ThemeLogo } from "@/components/navigation/full-logo";
+import { NavLink } from "@/components/navigation/nav-link";
+import { Button } from "@/components/ui/button";
+import {
+ Sheet,
+ SheetClose,
+ SheetContent,
+ SheetDescription,
+ SheetTitle,
+ SheetTrigger,
+} from "@/components/ui/sheet";
+
+export function MobileNav() {
+ const [open, setOpen] = useState(false);
+
+ return (
+
+
+
+
+
+ {/* Visually hidden title and description for accessibility */}
+ Navigation menu
+
+ Browse site pages and manage your account
+
+
+ {/* Logo */}
+
+
+
+
+
+
+ {/* Navigation Links */}
+
+
+ {/* Sign out - Only when signed in */}
+
+
+
+
+
+
+
+
+
+
+ {/* Auth Buttons - Only when signed out */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/components/navigation/nav-link.tsx b/components/navigation/nav-link.tsx
new file mode 100644
index 0000000..6cc5698
--- /dev/null
+++ b/components/navigation/nav-link.tsx
@@ -0,0 +1,41 @@
+"use client";
+
+import type { Route } from "next";
+import Image from "next/image";
+import Link from "next/link";
+import { usePathname } from "next/navigation";
+import type { NavLink as NavLinkType } from "@/components/navigation/constants";
+import { cn } from "@/lib/utils";
+
+type NavLinkProps = NavLinkType & {
+ // Passed by SheetClose asChild to close the sheet on click
+ onClick?: () => void;
+};
+
+export function NavLink({ imgURL, route, label, onClick }: NavLinkProps) {
+ const pathname = usePathname();
+ const isActive =
+ (pathname.startsWith(route) && route.length > 1) || pathname === route;
+
+ return (
+
+
+ {label}
+
+ );
+}
diff --git a/components/navigation/navbar/index.tsx b/components/navigation/navbar.tsx
similarity index 51%
rename from components/navigation/navbar/index.tsx
rename to components/navigation/navbar.tsx
index eeedaf7..1bc00a3 100644
--- a/components/navigation/navbar/index.tsx
+++ b/components/navigation/navbar.tsx
@@ -7,13 +7,15 @@ import {
} from "@clerk/nextjs";
import Image from "next/image";
import Link from "next/link";
+import { ThemeLogo } from "@/components/navigation/full-logo";
+import { MobileNav } from "@/components/navigation/mobile-nav";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
export const Navbar = () => (
- {/* Right: Theme toggle + Auth */}
-
+ {/* Right: Theme toggle + Auth + Mobile nav */}
+
-
-
-
-
-
-
-
-
-
-
-
+ {/* Desktop auth - removed from flex flow on mobile */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Mobile hamburger - visible only on mobile */}
+
);
diff --git a/components/navigation/theme-toggle.tsx b/components/navigation/theme-toggle.tsx
index b922db9..3901ec6 100644
--- a/components/navigation/theme-toggle.tsx
+++ b/components/navigation/theme-toggle.tsx
@@ -12,13 +12,13 @@ export function ThemeToggle() {
useEffect(() => setMounted(true), []);
if (!mounted) {
- return
;
+ return
;
}
return (