diff --git a/client/README.md b/client/README.md
index 58449df1..e5437d7c 100644
--- a/client/README.md
+++ b/client/README.md
@@ -9,6 +9,10 @@ Codify is a comprehensive web application that offers courses and roadmaps to va
- User authentication and personalized profiles
- Comprehensive admin panel for content and user management
- Optimized responsive design for seamless mobile and desktop experiences
+- New unified Hero section with stable layout (no button jumping) and video background
+- Desktop navigation now shows primary links (Home, About, Editor, Courses, Roadmaps, Notes, Questions, Bookmarks, Contributors, Contact) without needing the hamburger menu
+- Accessible focus states (focus-visible rings) added to all navigation links & CTAs
+- Playwright end-to-end tests for responsive navbar behavior
## Technologies Used
- **Frontend:** React, React Router, CSS 💻
@@ -52,6 +56,22 @@ https://github.com/user-attachments/assets/9ba51e5e-8e16-48f1-96d3-fe1e497541a0
## Usage
Once the application is running, navigate to `http://localhost:5173` in your web browser to access the app. You can create an account, log in, and start learning about bitwise operations.
+### Running E2E Tests (Playwright)
+1. From the `client` directory install dependencies (including Playwright): this will auto-install browsers on first run.
+2. Run tests:
+```
+pnpm test:e2e (or) npm run test:e2e
+```
+Optional:
+```
+npm run test:e2e:ui # Interactive UI mode
+npm run test:e2e:headed # Run in headed browsers
+```
+
+The navbar test validates:
+- Desktop (xl) shows all primary links and hides the hamburger.
+- Mobile view shows hamburger and reveals links after opening the menu.
+
## Contributing
Contributions are welcome! Please follow these steps:
1. Fork the repository.
diff --git a/client/package-lock.json b/client/package-lock.json
index 12b06f71..78361051 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -35,6 +35,7 @@
"swiper": "^11.2.10"
},
"devDependencies": {
+ "@playwright/test": "^1.48.2",
"@types/react": "^18.3.3",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.7.0",
@@ -1272,6 +1273,22 @@
"node": ">=14"
}
},
+ "node_modules/@playwright/test": {
+ "version": "1.55.0",
+ "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.55.0.tgz",
+ "integrity": "sha512-04IXzPwHrW69XusN/SIdDdKZBzMfOT9UNT/YiJit/xpy2VuAoB8NHc8Aplb96zsWDddLnbkPL3TsmrS04ZU2xQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright": "1.55.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/@remix-run/router": {
"version": "1.18.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.18.0.tgz",
@@ -5071,6 +5088,53 @@
"node": ">= 6"
}
},
+ "node_modules/playwright": {
+ "version": "1.55.0",
+ "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.55.0.tgz",
+ "integrity": "sha512-sdCWStblvV1YU909Xqx0DhOjPZE4/5lJsIS84IfN9dAZfcl/CIZ5O8l3o0j7hPMjDvqoTF8ZUcc+i/GL5erstA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "playwright-core": "1.55.0"
+ },
+ "bin": {
+ "playwright": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "fsevents": "2.3.2"
+ }
+ },
+ "node_modules/playwright-core": {
+ "version": "1.55.0",
+ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.55.0.tgz",
+ "integrity": "sha512-GvZs4vU3U5ro2nZpeiwyb0zuFaqb9sUiAJuyrWpcGouD8y9/HLgGbNRjIph7zU9D3hnPaisMl9zG9CgFi/biIg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "playwright-core": "cli.js"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/playwright/node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
"node_modules/possible-typed-array-names": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz",
diff --git a/client/package.json b/client/package.json
index bf0945c0..62b83945 100644
--- a/client/package.json
+++ b/client/package.json
@@ -7,7 +7,10 @@
"dev": "vite",
"build": "vite build",
"lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0",
- "preview": "vite preview"
+ "preview": "vite preview",
+ "test:e2e": "playwright test",
+ "test:e2e:ui": "playwright test --ui",
+ "test:e2e:headed": "playwright test --headed"
},
"dependencies": {
"@codemirror/lang-cpp": "^6.0.3",
@@ -47,6 +50,7 @@
"eslint-plugin-react-refresh": "^0.4.7",
"postcss": "^8.4.41",
"tailwindcss": "^3.4.17",
- "vite": "^6.3.5"
+ "vite": "^6.3.5",
+ "@playwright/test": "^1.48.2"
}
}
diff --git a/client/playwright.config.js b/client/playwright.config.js
new file mode 100644
index 00000000..d6760f81
--- /dev/null
+++ b/client/playwright.config.js
@@ -0,0 +1,25 @@
+// @ts-check
+import { defineConfig, devices } from '@playwright/test';
+
+export default defineConfig({
+ testDir: './tests',
+ timeout: 30 * 1000,
+ expect: { timeout: 5000 },
+ fullyParallel: true,
+ retries: 0,
+ reporter: [['list']],
+ use: {
+ baseURL: process.env.PLAYWRIGHT_BASE_URL || 'http://localhost:5173',
+ trace: 'retain-on-failure'
+ },
+ projects: [
+ {
+ name: 'Desktop Chrome',
+ use: { ...devices['Desktop Chrome'] }
+ },
+ {
+ name: 'Mobile Chrome',
+ use: { ...devices['Pixel 7'] }
+ }
+ ]
+});
diff --git a/client/src/components/Hero.jsx b/client/src/components/Hero.jsx
new file mode 100644
index 00000000..14cdc87e
--- /dev/null
+++ b/client/src/components/Hero.jsx
@@ -0,0 +1,116 @@
+import React from 'react';
+import { motion } from 'framer-motion';
+import { useTheme } from '../context/ThemeContext';
+
+/**
+ * Hero section extracted from Home page for clarity & reusability.
+ * Adjusted to avoid layout shift / button jumping by:
+ * - Providing explicit min-height using CSS variable offset for navbar.
+ * - Reserving space for async content (no conditional height changes).
+ * - Using flex + gap with consistent sizing.
+ */
+function Hero() {
+ const { theme } = useTheme();
+ const isDark = theme === 'dark';
+
+ return (
+
+ {/* Video Background */}
+
+
+ {/* Overlay */}
+
+
+ {/* Ambient Shapes */}
+
+
+ {/* Core Content */}
+
+
+
+ 🚀 Join 1000+ learners worldwide
+
+
+
+ Master Coding with
+ Interactive Learning
+
+
+
+ Discover the perfect learning path with hands-on projects, expert guidance, and a community of passionate developers.
+
+
+
+
+ Start Learning Free
+
+
+
+ Watch Demo
+
+
+
+
+
+
Trusted by developers from 100+ countries
+
+
+
+ {/* Scroll Indicator */}
+
+
+
+ );
+}
+
+export default Hero;
diff --git a/client/src/components/MobileMenu.jsx b/client/src/components/MobileMenu.jsx
index d552458f..818bb740 100644
--- a/client/src/components/MobileMenu.jsx
+++ b/client/src/components/MobileMenu.jsx
@@ -5,6 +5,9 @@ import { RiCloseLargeLine } from 'react-icons/ri';
import { FaBook, FaBookReader, FaBookmark, FaEnvelope, FaGraduationCap, FaHome, FaRoad, FaSignInAlt, FaSignOutAlt, FaUser, FaUserPlus, FaUserTie, FaArrowUp, FaArrowDown, FaQuestionCircle } from 'react-icons/fa';
import { useTheme } from '../context/ThemeContext';
import { FaCode } from "react-icons/fa";
+import ThemeSwitcher from './ThemeSwitcher';
+import ThemeColorSelector from './ThemeColorSelector';
+import { NAV_LINKS } from "../constants/navLinks";
function MobileMenu({ isOpen, onClose, isLoggedIn, userdata }) {
const { theme } = useTheme();
@@ -110,214 +113,27 @@ function MobileMenu({ isOpen, onClose, isLoggedIn, userdata }) {
-