diff --git a/.env.example b/.env.example index 0aff126..2fc80e3 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1 @@ PORT=3000 -FIREBASE_DATABASE_URL= diff --git a/eslint.config.js b/eslint.config.js index 539d2b2..1ba65c6 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -1,15 +1,15 @@ -import js from '@eslint/js'; -import globals from 'globals'; -import importPlugin from 'eslint-plugin-import'; +import js from "@eslint/js"; +import globals from "globals"; +import importPlugin from "eslint-plugin-import"; export default [ js.configs.recommended, { - files: ['**/*.{js,mjs,cjs}'], + files: ["**/*.{js,mjs,cjs}"], languageOptions: { globals: globals.node, - ecmaVersion: 'latest', - sourceType: 'module' + ecmaVersion: "latest", + sourceType: "module" }, plugins: { import: importPlugin @@ -17,43 +17,43 @@ export default [ rules: { // Airbnb-style rules - 'func-style': ['error', 'expression'], + "func-style": ["error", "expression"], - 'indent': ['error', 2], - 'linebreak-style': ['error', 'unix'], - 'quotes': ['error', 'single'], - 'semi': ['error', 'always'], - 'no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], - 'no-console': 'warn', - 'no-var': 'error', - 'prefer-const': 'error', - 'prefer-arrow-callback': 'error', - 'arrow-spacing': ['error', { 'before': true, 'after': true }], - 'comma-dangle': ['error', 'never'], - 'eol-last': ['error', 'always'], - 'no-multiple-empty-lines': ['error', { 'max': 1 }], - 'object-curly-spacing': ['error', 'always'], - 'array-bracket-spacing': ['error', 'never'], - 'space-before-blocks': 'error', - 'keyword-spacing': ['error', { 'before': true, 'after': true }], - 'space-infix-ops': 'error', - 'no-trailing-spaces': 'error', - 'eqeqeq': ['error', 'always'], - 'no-multi-spaces': 'error', - 'no-param-reassign': ['error', { 'props': false }], - 'prefer-template': 'error', - 'template-curly-spacing': ['error', 'never'], - 'no-useless-concat': 'error', + "indent": ["error", 2], + "linebreak-style": ["error", "unix"], + "quotes": ["error", "double"], + "semi": ["error", "always"], + "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], + "no-console": "warn", + "no-var": "error", + "prefer-const": "error", + "prefer-arrow-callback": "error", + "arrow-spacing": ["error", { "before": true, "after": true }], + "comma-dangle": ["error", "never"], + "eol-last": ["error", "always"], + "no-multiple-empty-lines": ["error", { "max": 1 }], + "object-curly-spacing": ["error", "always"], + "array-bracket-spacing": ["error", "never"], + "space-before-blocks": "error", + "keyword-spacing": ["error", { "before": true, "after": true }], + "space-infix-ops": "error", + "no-trailing-spaces": "error", + "eqeqeq": ["error", "always"], + "no-multi-spaces": "error", + "no-param-reassign": ["error", { "props": false }], + "prefer-template": "error", + "template-curly-spacing": ["error", "never"], + "no-useless-concat": "error", // Import rules (Airbnb-style) - 'import/no-unresolved': 'off', - 'import/named': 'error', - 'import/default': 'error', - 'import/namespace': 'error', - 'import/no-duplicates': 'error', - 'import/order': ['error', { - 'groups': ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], - 'newlines-between': 'never' + "import/no-unresolved": "off", + "import/named": "error", + "import/default": "error", + "import/namespace": "error", + "import/no-duplicates": "error", + "import/order": ["error", { + "groups": ["builtin", "external", "internal", "parent", "sibling", "index"], + "newlines-between": "never" }] } } diff --git a/server.js b/server.js index 20ec6bc..b06eef9 100644 --- a/server.js +++ b/server.js @@ -1,4 +1,4 @@ -import app from './src/app.js'; +import app from "./src/app.js"; const port = process.env.PORT || 3000; diff --git a/src/app.js b/src/app.js index 6fd4417..ff94d86 100644 --- a/src/app.js +++ b/src/app.js @@ -1,17 +1,20 @@ -import path from 'path'; -import { fileURLToPath } from 'url'; -import express from 'express'; +import path from "path"; +import { fileURLToPath } from "url"; +import express from "express"; +import router from "./routes/route.js"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const app = express(); -app.set('view engine', 'ejs'); -app.set('views', path.join(__dirname, 'views')); +app.set("view engine", "ejs"); +app.set("views", path.join(__dirname, "views")); -app.use(express.static(path.join(__dirname, 'public'))); +app.use(express.static(path.join(__dirname, "public"))); app.use(express.json()); app.use(express.urlencoded({ extended: true })); +app.use(router); + export default app; diff --git a/src/controllers/pagesController.js b/src/controllers/pagesController.js new file mode 100644 index 0000000..37dde11 --- /dev/null +++ b/src/controllers/pagesController.js @@ -0,0 +1,86 @@ +import path from 'path'; +import { fileURLToPath } from 'url'; +import fs from 'fs/promises'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const loadMockData = async () => { + const dataPath = path.join(__dirname, '..', 'data', 'mockData.json'); + const rawData = await fs.readFile(dataPath, 'utf-8'); + return JSON.parse(rawData); +}; + +export const getHomePage = async (req, res) => { + try { + const data = await loadMockData(); + res.render('index', { + stats: data.stats, + features: data.features, + }); + } catch { + res.status(500).send('Error loading page'); + } +}; + +export const getDomainsPage = async (req, res) => { + try { + const data = await loadMockData(); + res.render('domains', { + domains: data.domains, + }); + } catch { + res.status(500).send('Error loading domains'); + } +}; + +export const getSubdomainPage = async (req, res) => { + try { + const { domainId } = req.params; + const data = await loadMockData(); + const domain = data.domains.find((d) => d.id === domainId); + + if (!domain) { + return res.status(404).send('Domain not found'); + } + + res.render('subdomain', { domain }); + } catch { + res.status(500).send('Error loading subdomain'); + } +}; + +export const getTopicsPage = async (req, res) => { + try { + const { subdomainId } = req.params; + const data = await loadMockData(); + const topic = data.topics[subdomainId]; + + if (!topic) { + return res.status(404).send('Topics not found'); + } + + res.render('topicsPage', { + topic, + subdomainId, + }); + } catch { + res.status(500).send('Error loading topics'); + } +}; + +export const getSuggestTopicPage = async (req, res) => { + try { + const { domainId, subdomainId } = req.params; + const data = await loadMockData(); + const topic = data.topics[subdomainId]; + + res.render('suggestTopic', { + domainId, + subdomainId, + subdomainName: topic ? topic.name : subdomainId, + }); + } catch { + res.status(500).send('Error loading page'); + } +}; diff --git a/src/controllers/topicsController.js b/src/controllers/topicsController.js new file mode 100644 index 0000000..2931e47 --- /dev/null +++ b/src/controllers/topicsController.js @@ -0,0 +1,51 @@ +import path from "path"; +import { fileURLToPath } from "url"; +import fs from "fs/promises"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +export const getTopics = async (req, res) => { + try { + const { category } = req.params; + const dataPath = path.join(__dirname, "..", "data", "topicsData.json"); + const rawData = await fs.readFile(dataPath, "utf-8"); + const allTopics = JSON.parse(rawData); + + const categoryData = allTopics[category]; + + if (!categoryData) { + return res.status(404).send("Topic category not found"); + } + + res.render("topics", { + categoryName: categoryData.name, + subtitle: categoryData.subtitle, + topics: categoryData.topics, + categorySlug: category + }); + } catch (error) { + console.error("Error loading topics:", error); + res.status(500).send("Error loading topics"); + } +}; + +export const getAllCategories = async (req, res) => { + try { + const dataPath = path.join(__dirname, "..", "data", "topicsData.json"); + const rawData = await fs.readFile(dataPath, "utf-8"); + const allTopics = JSON.parse(rawData); + + const categories = Object.keys(allTopics).map((key) => ({ + slug: key, + name: allTopics[key].name, + subtitle: allTopics[key].subtitle, + topicCount: allTopics[key].topics.length + })); + + res.render("categories", { categories }); + } catch (error) { + console.error("Error loading categories:", error); + res.status(500).send("Error loading categories"); + } +}; diff --git a/src/data/mockData.json b/src/data/mockData.json new file mode 100644 index 0000000..6021b54 --- /dev/null +++ b/src/data/mockData.json @@ -0,0 +1,308 @@ +{ + "stats": { + "techStacks": "10+", + "communityTopics": "2.5K+", + "contributors": "500+" + }, + "features": [ + { + "icon": "👥", + "title": "Community Driven", + "description": "Learn from topics and questions shared by experienced developers who've already mastered the stack" + }, + { + "icon": "👍", + "title": "Curated by Votes", + "description": "Best topics rise to the top through community voting, ensuring quality learning paths" + }, + { + "icon": "📝", + "title": "Contribute & Help", + "description": "Share your knowledge by suggesting topics and questions to help others learn" + }, + { + "icon": "📊", + "title": "Track Progress", + "description": "Monitor your learning journey and see which topics you've mastered" + } + ], + "domains": [ + { + "id": "mern", + "name": "MERN Stack", + "description": "MongoDB, Express.js, React, Node.js", + "icon": "code", + "topicCount": 48, + "subdomains": [ + { + "id": "mongodb", + "name": "MongoDB", + "description": "NoSQL database for modern apps", + "topicCount": 12, + "contributors": 23 + }, + { + "id": "expressjs", + "name": "Express.js", + "description": "Fast, minimalist web framework", + "topicCount": 10, + "contributors": 19 + }, + { + "id": "react", + "name": "React", + "description": "Library for building user interfaces", + "topicCount": 15, + "contributors": 31 + }, + { + "id": "nodejs", + "name": "Node.js", + "description": "JavaScript runtime environment", + "topicCount": 11, + "contributors": 21 + } + ] + }, + { + "id": "flutter", + "name": "Flutter", + "description": "Cross-platform mobile development", + "icon": "mobile", + "topicCount": 36, + "subdomains": [ + { + "id": "dart", + "name": "Dart", + "description": "Programming language for Flutter", + "topicCount": 8, + "contributors": 15 + }, + { + "id": "widgets", + "name": "Widgets", + "description": "Building blocks of Flutter UI", + "topicCount": 14, + "contributors": 22 + }, + { + "id": "state", + "name": "State Management", + "description": "Provider, Bloc, Riverpod patterns", + "topicCount": 10, + "contributors": 18 + }, + { + "id": "native", + "name": "Native Features", + "description": "Platform channels and plugins", + "topicCount": 4, + "contributors": 12 + } + ] + }, + { + "id": "django", + "name": "Django", + "description": "Python web framework", + "icon": "globe", + "topicCount": 42, + "subdomains": [ + { + "id": "python", + "name": "Python", + "description": "Core Python programming", + "topicCount": 10, + "contributors": 25 + }, + { + "id": "models", + "name": "Models & ORM", + "description": "Database models and queries", + "topicCount": 12, + "contributors": 20 + }, + { + "id": "views", + "name": "Views & Templates", + "description": "Request handling and rendering", + "topicCount": 11, + "contributors": 18 + }, + { + "id": "rest", + "name": "REST Framework", + "description": "Building APIs with DRF", + "topicCount": 9, + "contributors": 16 + } + ] + } + ], + "topics": { + "mongodb": { + "name": "MongoDB", + "parentDomain": "mern", + "parentName": "MERN Stack", + "subtitle": "Explore community-curated topics and master your skills", + "items": [ + { + "id": "intro-setup", + "title": "Introduction and Setup", + "description": "Learn how to set up your environment and understand the core architecture.", + "difficulty": "Beginner" + }, + { + "id": "core-concepts", + "title": "Core Concepts", + "description": "Deep dive into variables, types, and fundamental programming patterns.", + "difficulty": "Beginner" + }, + { + "id": "data-structures", + "title": "Data Structures", + "description": "Master collections, maps, and efficient data handling techniques.", + "difficulty": "Intermediate" + }, + { + "id": "advanced-patterns", + "title": "Advanced Patterns", + "description": "Explore architectural patterns and advanced language features.", + "difficulty": "Advanced" + }, + { + "id": "performance-optimization", + "title": "Performance Optimization", + "description": "Techniques for building faster, more efficient applications.", + "difficulty": "Advanced" + }, + { + "id": "best-practices", + "title": "Best Practices", + "description": "Industry standard coding conventions and community guidelines.", + "difficulty": "Intermediate" + }, + { + "id": "testing-strategies", + "title": "Testing Strategies", + "description": "Unit testing, integration testing, and automated quality assurance.", + "difficulty": "Intermediate" + }, + { + "id": "deployment", + "title": "Deployment", + "description": "Release management, CI/CD pipelines, and production setup.", + "difficulty": "Intermediate" + } + ] + }, + "expressjs": { + "name": "Express.js", + "parentDomain": "mern", + "parentName": "MERN Stack", + "subtitle": "Build robust APIs and web applications", + "items": [ + { + "id": "getting-started", + "title": "Getting Started", + "description": "Install Express and create your first server.", + "difficulty": "Beginner" + }, + { + "id": "routing", + "title": "Routing", + "description": "Handle different HTTP methods and URL patterns.", + "difficulty": "Beginner" + }, + { + "id": "middleware", + "title": "Middleware", + "description": "Request processing pipeline and custom middleware.", + "difficulty": "Intermediate" + }, + { + "id": "error-handling", + "title": "Error Handling", + "description": "Graceful error handling and custom error pages.", + "difficulty": "Intermediate" + }, + { + "id": "authentication", + "title": "Authentication", + "description": "JWT, sessions, and OAuth integration.", + "difficulty": "Advanced" + } + ] + }, + "react": { + "name": "React", + "parentDomain": "mern", + "parentName": "MERN Stack", + "subtitle": "Build modern user interfaces", + "items": [ + { + "id": "jsx-basics", + "title": "JSX Basics", + "description": "Understanding JSX syntax and expressions.", + "difficulty": "Beginner" + }, + { + "id": "components", + "title": "Components", + "description": "Functional and class components.", + "difficulty": "Beginner" + }, + { + "id": "hooks", + "title": "Hooks", + "description": "useState, useEffect, and custom hooks.", + "difficulty": "Intermediate" + }, + { + "id": "context", + "title": "Context API", + "description": "Global state management with Context.", + "difficulty": "Intermediate" + }, + { + "id": "performance", + "title": "Performance", + "description": "Memoization and optimization techniques.", + "difficulty": "Advanced" + } + ] + }, + "nodejs": { + "name": "Node.js", + "parentDomain": "mern", + "parentName": "MERN Stack", + "subtitle": "JavaScript runtime environment", + "items": [ + { + "id": "fundamentals", + "title": "Fundamentals", + "description": "Event loop, modules, and core concepts.", + "difficulty": "Beginner" + }, + { + "id": "file-system", + "title": "File System", + "description": "Reading, writing, and managing files.", + "difficulty": "Beginner" + }, + { + "id": "streams", + "title": "Streams", + "description": "Handling large data with streams.", + "difficulty": "Intermediate" + }, + { + "id": "security", + "title": "Security", + "description": "Best practices for secure applications.", + "difficulty": "Advanced" + } + ] + } + } +} diff --git a/src/data/topicsData.json b/src/data/topicsData.json new file mode 100644 index 0000000..b4ce2ec --- /dev/null +++ b/src/data/topicsData.json @@ -0,0 +1,141 @@ +{ + + "mern": { + "name": "MERN Stack", + "subtitle": "Master full-stack development with MongoDB, Express, React, and Node.js", + "topics": [ + { + "id": "getting-started", + "title": "Getting Started", + "description": "Set up your MERN development environment and create your first app.", + "difficulty": "Beginner" + }, + { + "id": "getting-started", + "title": "Getting Started", + "description": "Set up your MERN development environment and create your first app.", + "difficulty": "Beginner" + }, + { + "id": "react-fundamentals", + "title": "React Fundamentals", + "description": "Learn components, hooks, and state management in React.", + "difficulty": "Beginner" + }, + { + "id": "express-apis", + "title": "Express APIs", + "description": "Build RESTful APIs with Express and Node.js.", + "difficulty": "Intermediate" + }, + { + "id": "mongodb-integration", + "title": "MongoDB Integration", + "description": "Connect and interact with MongoDB databases.", + "difficulty": "Intermediate" + }, + { + "id": "authentication", + "title": "Authentication & Security", + "description": "Implement JWT authentication and secure your applications.", + "difficulty": "Advanced" + }, + { + "id": "state-management", + "title": "State Management", + "description": "Redux, Context API, and advanced state patterns.", + "difficulty": "Advanced" + } + ] + }, + "Datascience": { + "name": "datascience", + "subtitle": "Master full-stack development with MongoDB, Express, React, and Node.js", + "topics": [ + { + "id": "getting-started", + "title": "Getting Started", + "description": "Set up your MERN development environment and create your first app.", + "difficulty": "Beginner" + }, + { + "id": "getting-started", + "title": "Getting Started", + "description": "Set up your MERN development environment and create your first app.", + "difficulty": "Beginner" + }, + { + "id": "react-fundamentals", + "title": "React Fundamentals", + "description": "Learn components, hooks, and state management in React.", + "difficulty": "Beginner" + }, + { + "id": "express-apis", + "title": "Express APIs", + "description": "Build RESTful APIs with Express and Node.js.", + "difficulty": "Intermediate" + }, + { + "id": "mongodb-integration", + "title": "MongoDB Integration", + "description": "Connect and interact with MongoDB databases.", + "difficulty": "Intermediate" + }, + { + "id": "authentication", + "title": "Authentication & Security", + "description": "Implement JWT authentication and secure your applications.", + "difficulty": "Advanced" + }, + { + "id": "state-management", + "title": "State Management", + "description": "Redux, Context API, and advanced state patterns.", + "difficulty": "Advanced" + } + ] + }, + "flutter": { + "name": "Flutter", + "subtitle": "Build beautiful native mobile apps for iOS and Android", + "topics": [ + { + "id": "dart-basics", + "title": "Dart Basics", + "description": "Learn Dart programming language fundamentals.", + "difficulty": "Beginner" + }, + { + "id": "widgets", + "title": "Widgets & UI", + "description": "Master Flutter widgets and create stunning user interfaces.", + "difficulty": "Beginner" + }, + { + "id": "navigation", + "title": "Navigation & Routing", + "description": "Implement navigation patterns and deep linking.", + "difficulty": "Intermediate" + }, + { + "id": "state-management", + "title": "State Management", + "description": "Provider, Bloc, Riverpod, and other state solutions.", + "difficulty": "Intermediate" + }, + { + "id": "native-features", + "title": "Native Features", + "description": "Access device cameras, GPS, sensors, and platform channels.", + "difficulty": "Advanced" + }, + { + "id": "publishing", + "title": "Publishing Apps", + "description": "Deploy to App Store and Google Play Store.", + "difficulty": "Advanced" + } + ] + } +} diff --git a/src/public/css/main.css b/src/public/css/main.css new file mode 100644 index 0000000..e2ff630 --- /dev/null +++ b/src/public/css/main.css @@ -0,0 +1,774 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap'); + +:root { + --primary: #2563eb; + --primary-dark: #1d4ed8; + --primary-light: #3b82f6; + --secondary: #0ea5e9; + --bg-gradient-start: #1e3a5f; + --bg-gradient-end: #0f172a; + --text-dark: #111827; + --text-muted: #6b7280; + --text-light: #9ca3af; + --bg-light: #f8fafc; + --bg-card: #ffffff; + --border: #e5e7eb; + --success: #10b981; + --warning: #f59e0b; + --danger: #ef4444; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; + background: var(--bg-light); + color: var(--text-dark); + line-height: 1.6; +} + +a { + text-decoration: none; + color: inherit; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 1.5rem; +} + +.header { + background: var(--bg-card); + padding: 1rem 0; + border-bottom: 1px solid var(--border); + position: sticky; + top: 0; + z-index: 100; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.25rem; + font-weight: 700; + color: var(--primary); +} + +.logo-icon { + background: linear-gradient(135deg, var(--primary), var(--secondary)); + color: white; + width: 36px; + height: 36px; + border-radius: 10px; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.9rem; +} + +.nav-links { + display: flex; + align-items: center; + gap: 2rem; +} + +.nav-link { + color: var(--text-muted); + font-weight: 500; + font-size: 0.95rem; + transition: color 0.2s; +} + +.nav-link:hover { + color: var(--primary); +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: linear-gradient(135deg, #fbbf24, #f59e0b); + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + cursor: pointer; +} + +.hero { + background: linear-gradient(135deg, var(--bg-gradient-start), var(--bg-gradient-end)); + color: white; + padding: 5rem 0 4rem; + text-align: center; +} + +.hero-badge { + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); + padding: 0.5rem 1rem; + border-radius: 50px; + font-size: 0.85rem; + margin-bottom: 2rem; + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.hero h1 { + font-size: 3.5rem; + font-weight: 800; + margin-bottom: 0.5rem; + line-height: 1.1; +} + +.hero h1 span { + background: linear-gradient(135deg, #60a5fa, #34d399); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.hero-subtitle { + color: rgba(255, 255, 255, 0.8); + font-size: 1.1rem; + max-width: 600px; + margin: 1.5rem auto 2.5rem; +} + +.hero-buttons { + display: flex; + gap: 1rem; + justify-content: center; + margin-bottom: 3rem; +} + +.btn { + padding: 0.875rem 1.75rem; + border-radius: 8px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + transition: all 0.3s ease; + border: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; +} + +.btn-primary { + background: var(--primary); + color: white; + box-shadow: 0 4px 14px rgba(37, 99, 235, 0.4); +} + +.btn-primary:hover { + background: var(--primary-dark); + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(37, 99, 235, 0.5); +} + +.btn-secondary { + background: transparent; + color: white; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.btn-secondary:hover { + background: rgba(255, 255, 255, 0.1); + border-color: rgba(255, 255, 255, 0.5); +} + +.stats { + display: flex; + justify-content: center; + gap: 4rem; +} + +.stat-item { + text-align: center; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + color: var(--secondary); +} + +.stat-label { + font-size: 0.85rem; + color: rgba(255, 255, 255, 0.7); + margin-top: 0.25rem; +} + +.features { + padding: 4rem 0; +} + +.features-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + max-width: 900px; + margin: 0 auto; +} + +.feature-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 2rem; + transition: all 0.3s ease; +} + +.feature-card:hover { + border-color: var(--primary-light); + box-shadow: 0 8px 30px rgba(37, 99, 235, 0.1); + transform: translateY(-4px); +} + +.feature-icon { + width: 48px; + height: 48px; + background: linear-gradient(135deg, #eff6ff, #dbeafe); + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-bottom: 1rem; +} + +.feature-card h3 { + font-size: 1.1rem; + font-weight: 600; + margin-bottom: 0.5rem; + color: var(--text-dark); +} + +.feature-card p { + font-size: 0.9rem; + color: var(--text-muted); + line-height: 1.6; +} + +.page-container { + min-height: 100vh; + background: var(--bg-light); +} + +.page-content { + padding: 2rem 0 4rem; +} + +.back-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--text-dark); + font-weight: 500; + font-size: 0.95rem; + margin-bottom: 2rem; + transition: all 0.2s; +} + +.back-link:hover { + color: var(--primary); + transform: translateX(-4px); +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 2.5rem; + gap: 2rem; +} + +.page-header h1 { + font-size: 2.25rem; + font-weight: 700; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.page-header p { + color: var(--text-muted); + font-size: 1rem; +} + +.domain-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +.domain-card { + background: var(--bg-card); + border: 2px solid var(--border); + border-radius: 16px; + padding: 1.75rem; + transition: all 0.3s ease; + position: relative; +} + +.domain-card:hover { + border-color: var(--primary-light); + box-shadow: 0 12px 32px rgba(37, 99, 235, 0.12); + transform: translateY(-4px); +} + +.domain-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.domain-icon { + width: 52px; + height: 52px; + border-radius: 14px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; +} + +.domain-icon.code { + background: linear-gradient(135deg, #dbeafe, #bfdbfe); + color: var(--primary); +} + +.domain-icon.mobile { + background: linear-gradient(135deg, #d1fae5, #a7f3d0); + color: #059669; +} + +.domain-icon.globe { + background: linear-gradient(135deg, #dcfce7, #bbf7d0); + color: #16a34a; +} + +.topic-count { + text-align: right; +} + +.topic-count .count { + font-size: 1.75rem; + font-weight: 700; + color: var(--text-dark); +} + +.topic-count .label { + font-size: 0.75rem; + color: var(--text-muted); + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.domain-card h3 { + font-size: 1.25rem; + font-weight: 600; + color: var(--primary); + margin-bottom: 0.5rem; +} + +.domain-card .description { + font-size: 0.9rem; + color: var(--text-muted); + margin-bottom: 1.5rem; +} + +.view-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--primary); + font-weight: 600; + font-size: 0.9rem; + transition: all 0.2s; +} + +.view-link:hover { + gap: 0.75rem; +} + +.subdomain-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; +} + +.subdomain-card { + background: var(--bg-card); + border: 2px solid var(--border); + border-radius: 16px; + padding: 1.75rem; + transition: all 0.3s ease; + display: flex; + justify-content: space-between; + align-items: center; +} + +.subdomain-card:hover { + border-color: var(--primary-light); + box-shadow: 0 8px 24px rgba(37, 99, 235, 0.1); +} + +.subdomain-info h3 { + font-size: 1.2rem; + font-weight: 600; + color: var(--primary); + margin-bottom: 0.5rem; +} + +.subdomain-info .description { + font-size: 0.9rem; + color: var(--text-muted); + margin-bottom: 1rem; +} + +.subdomain-stats { + display: flex; + align-items: center; + gap: 1rem; + font-size: 0.85rem; +} + +.subdomain-stats .topics { + background: #dbeafe; + color: var(--primary); + padding: 0.25rem 0.75rem; + border-radius: 20px; + font-weight: 600; +} + +.subdomain-stats .contributors { + color: var(--text-muted); + display: flex; + align-items: center; + gap: 0.35rem; +} + +.subdomain-arrow { + color: var(--text-light); + font-size: 1.25rem; + transition: all 0.2s; +} + +.subdomain-card:hover .subdomain-arrow { + color: var(--primary); + transform: translateX(4px); +} + +.topics-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1.5rem; +} + +.topic-card { + background: var(--bg-card); + border: 2px solid var(--border); + border-radius: 16px; + padding: 1.75rem; + transition: all 0.3s ease; + position: relative; + overflow: hidden; +} + +.topic-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--primary), var(--secondary)); + transform: scaleX(0); + transform-origin: left; + transition: transform 0.3s ease; +} + +.topic-card:hover::before { + transform: scaleX(1); +} + +.topic-card:hover { + border-color: var(--primary-light); + box-shadow: 0 12px 32px rgba(37, 99, 235, 0.12); + transform: translateY(-4px); +} + +.topic-card-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 0.75rem; + gap: 1rem; +} + +.topic-card h3 { + font-size: 1.1rem; + font-weight: 600; + color: var(--text-dark); +} + +.difficulty-badge { + padding: 0.35rem 0.75rem; + border-radius: 20px; + font-size: 0.7rem; + font-weight: 600; + white-space: nowrap; +} + +.difficulty-badge.beginner { + background: #d1fae5; + color: #065f46; +} + +.difficulty-badge.intermediate { + background: #fef3c7; + color: #92400e; +} + +.difficulty-badge.advanced { + background: #fee2e2; + color: #991b1b; +} + +.topic-card .description { + font-size: 0.9rem; + color: var(--text-muted); + margin-bottom: 1.5rem; + line-height: 1.6; +} + +.topic-view-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: var(--primary); + font-weight: 600; + font-size: 0.9rem; + transition: all 0.2s; +} + +.topic-view-link:hover { + gap: 0.75rem; +} + +.suggest-btn { + background: linear-gradient(135deg, var(--primary), var(--primary-dark)); + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.3s ease; + box-shadow: 0 4px 14px rgba(37, 99, 235, 0.3); +} + +.suggest-btn:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(37, 99, 235, 0.4); +} + +.form-container { + max-width: 720px; + margin: 0 auto; +} + +.info-banner { + background: linear-gradient(135deg, #eff6ff, #dbeafe); + border: 1px solid #bfdbfe; + border-radius: 12px; + padding: 1.25rem 1.5rem; + margin-bottom: 2rem; + display: flex; + gap: 1rem; +} + +.info-banner .icon { + width: 24px; + height: 24px; + background: var(--primary); + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.8rem; + flex-shrink: 0; +} + +.info-banner h4 { + font-size: 0.95rem; + font-weight: 600; + color: var(--text-dark); + margin-bottom: 0.25rem; +} + +.info-banner p { + font-size: 0.85rem; + color: var(--text-muted); +} + +.form-card { + background: var(--bg-card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 2rem; +} + +.form-group { + margin-bottom: 1.5rem; +} + +.form-label { + display: block; + font-weight: 600; + font-size: 0.95rem; + color: var(--text-dark); + margin-bottom: 0.5rem; +} + +.form-label .required { + color: var(--danger); +} + +.form-input { + width: 100%; + padding: 0.875rem 1rem; + border: 2px solid var(--border); + border-radius: 10px; + font-size: 0.95rem; + font-family: inherit; + transition: all 0.2s; +} + +.form-input:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); +} + +.form-input::placeholder { + color: var(--text-light); +} + +.form-hint { + font-size: 0.8rem; + color: var(--text-muted); + margin-top: 0.5rem; +} + +.form-select { + width: 100%; + padding: 0.875rem 1rem; + border: 2px solid var(--border); + border-radius: 10px; + font-size: 0.95rem; + font-family: inherit; + background: white; + cursor: pointer; + transition: all 0.2s; + appearance: none; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%236b7280' d='M6 8L1 3h10z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 1rem center; +} + +.form-select:focus { + outline: none; + border-color: var(--primary); + box-shadow: 0 0 0 4px rgba(37, 99, 235, 0.1); +} + +.form-buttons { + display: flex; + gap: 1rem; + margin-top: 2rem; +} + +.form-buttons .btn { + flex: 1; + justify-content: center; +} + +.btn-cancel { + background: var(--bg-light); + color: var(--text-dark); + border: 1px solid var(--border); +} + +.btn-cancel:hover { + background: #f1f5f9; +} + +@media (max-width: 1024px) { + .domain-grid, + .topics-grid { + grid-template-columns: repeat(2, 1fr); + } + + .features-grid { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .hero h1 { + font-size: 2.5rem; + } + + .stats { + gap: 2rem; + } + + .domain-grid, + .subdomain-grid, + .topics-grid { + grid-template-columns: 1fr; + } + + .page-header { + flex-direction: column; + } + + .suggest-btn { + width: 100%; + justify-content: center; + } + + .nav-links { + display: none; + } + + .hero-buttons { + flex-direction: column; + align-items: center; + } + + .form-buttons { + flex-direction: column; + } +} diff --git a/src/public/css/topics.css b/src/public/css/topics.css new file mode 100644 index 0000000..6dab2bb --- /dev/null +++ b/src/public/css/topics.css @@ -0,0 +1,294 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + background: linear-gradient(135deg, #f5f7fa 0%, #e8eef5 100%); + min-height: 100vh; + color: #1a202c; +} + +.topics-container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem 1.5rem; +} + +.header-wrapper { + background: white; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); + position: sticky; + top: 0; + z-index: 100; +} + +.header { + max-width: 1200px; + margin: 0 auto; + padding: 1.5rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 1.25rem; + font-weight: 700; + color: #2563eb; + text-decoration: none; +} + +.logo-icon { + font-size: 1.5rem; +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + border: 2px solid #e5e7eb; +} + +.back-button { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: #4b5563; + text-decoration: none; + font-size: 0.95rem; + font-weight: 500; + margin-bottom: 1.5rem; + transition: all 0.2s ease; +} + +.back-button:hover { + color: #2563eb; + transform: translateX(-4px); +} + +.page-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 3rem; + gap: 2rem; +} + +.page-title-section h1 { + font-size: 2.5rem; + font-weight: 700; + color: #111827; + margin-bottom: 0.5rem; +} + +.page-title-section p { + font-size: 1rem; + color: #6b7280; + font-weight: 400; +} + +.suggest-btn { + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%); + color: white; + border: none; + padding: 0.75rem 1.5rem; + border-radius: 8px; + font-weight: 600; + font-size: 0.95rem; + cursor: pointer; + display: flex; + align-items: center; + gap: 0.5rem; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(37, 99, 235, 0.2); +} + +.suggest-btn:hover { + transform: translateY(-2px); + box-shadow: 0 8px 20px rgba(37, 99, 235, 0.3); +} + +.suggest-btn:active { + transform: translateY(0); +} + +.topics-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + gap: 1.5rem; + margin-bottom: 3rem; +} + +.topic-card { + background: white; + border-radius: 12px; + padding: 1.75rem; + border: 2px solid #e5e7eb; + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; +} + +.topic-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, #2563eb, #3b82f6); + transform: scaleX(0); + transform-origin: left; + transition: transform 0.3s ease; +} + +.topic-card:hover::before { + transform: scaleX(1); +} + +.topic-card:hover { + border-color: #3b82f6; + box-shadow: 0 12px 28px rgba(37, 99, 235, 0.15); + transform: translateY(-4px); +} + +.topic-card.highlighted { + border-color: #3b82f6; + background: linear-gradient(135deg, #eff6ff 0%, #ffffff 100%); +} + +.topic-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.topic-card h3 { + font-size: 1.25rem; + font-weight: 600; + color: #111827; + margin-bottom: 0.75rem; +} + +.difficulty-badge { + padding: 0.35rem 0.85rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + text-transform: capitalize; + white-space: nowrap; +} + +.difficulty-badge.beginner { + background: #d1fae5; + color: #065f46; +} + +.difficulty-badge.intermediate { + background: #fef3c7; + color: #92400e; +} + +.difficulty-badge.advanced { + background: #fee2e2; + color: #991b1b; +} + +.topic-card p { + color: #6b7280; + font-size: 0.95rem; + line-height: 1.6; + margin-bottom: 1.5rem; +} + +.view-topics-link { + display: inline-flex; + align-items: center; + gap: 0.5rem; + color: #2563eb; + text-decoration: none; + font-weight: 600; + font-size: 0.95rem; + transition: all 0.2s ease; +} + +.view-topics-link:hover { + color: #1d4ed8; + gap: 0.75rem; +} + +.arrow-icon { + transition: transform 0.2s ease; +} + +.view-topics-link:hover .arrow-icon { + transform: translateX(4px); +} + +@media (max-width: 768px) { + .topics-grid { + grid-template-columns: 1fr; + } + + .page-header { + flex-direction: column; + align-items: flex-start; + } + + .page-title-section h1 { + font-size: 2rem; + } + + .suggest-btn { + width: 100%; + justify-content: center; + } + + .topic-card { + padding: 1.5rem; + } +} + +@media (min-width: 769px) and (max-width: 1024px) { + .topics-grid { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1025px) { + .topics-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.category-card { + min-height: 180px; +} + +.category-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; +} + +.topic-count-badge { + padding: 0.35rem 0.85rem; + border-radius: 20px; + font-size: 0.75rem; + font-weight: 600; + background: #eff6ff; + color: #2563eb; + white-space: nowrap; +} + diff --git a/src/routes/route.js b/src/routes/route.js index e69de29..f8bb2b2 100644 --- a/src/routes/route.js +++ b/src/routes/route.js @@ -0,0 +1,18 @@ +import express from "express"; +import { + getHomePage, + getDomainsPage, + getSubdomainPage, + getTopicsPage, + getSuggestTopicPage +} from "../controllers/pagesController.js"; + +const router = express.Router(); + +router.get("/", getHomePage); +router.get("/domains", getDomainsPage); +router.get("/domains/:domainId", getSubdomainPage); +router.get("/domains/:domainId/:subdomainId", getTopicsPage); +router.get("/domains/:domainId/:subdomainId/add", getSuggestTopicPage); + +export default router; diff --git a/src/views/categories.ejs b/src/views/categories.ejs new file mode 100644 index 0000000..1a8e1ee --- /dev/null +++ b/src/views/categories.ejs @@ -0,0 +1,51 @@ + + + + + + + All Topics - BNext Dev + + + +
+
+ + User +
+
+ +
+ + Back + + + + +
+ <% categories.forEach((category) => { %> +
+
+

<%= category.name %>

+ + <%= category.topicCount %> topics + +
+

<%= category.subtitle %>

+ + Explore Topics + +
+ <% }); %> +
+
+ + diff --git a/src/views/domains.ejs b/src/views/domains.ejs new file mode 100644 index 0000000..833c093 --- /dev/null +++ b/src/views/domains.ejs @@ -0,0 +1,61 @@ + + + + + + + Choose Your Domain - BNext Dev + + + +
+
+ +
+ +
+
+ + +
+ <% domains.forEach(domain => { %> +
+
+
+ <% if (domain.icon === 'code') { %> + </> + <% } else if (domain.icon === 'mobile') { %> + 📱 + <% } else { %> + 🌐 + <% } %> +
+
+
<%= domain.topicCount %>
+
topics
+
+
+

<%= domain.name %>

+

<%= domain.description %>

+ + View Roadmap → + +
+ <% }); %> +
+
+
+
+ + diff --git a/src/views/index.ejs b/src/views/index.ejs new file mode 100644 index 0000000..78a9170 --- /dev/null +++ b/src/views/index.ejs @@ -0,0 +1,78 @@ + + + + + + + BNext Dev - Master Any Tech Stack With The Community + + + +
+
+ + +
👤
+
+
+ +
+
+
+ 🎓 The Future of Collaborative Learning • Join 500+ Contributors +
+ +

Master Any Stack
With The Community

+ +

+ Don't learn in isolation. Access real-world topics, interview questions, + and practical challenges shared by those who've already mastered them. +

+ + + +
+
+
<%= stats.techStacks %>
+
Tech Stacks
+
+
+
<%= stats.communityTopics %>
+
Community Topics
+
+
+
<%= stats.contributors %>
+
Contributors
+
+
+
+
+ +
+
+
+ <% features.forEach(feature => { %> +
+
<%= feature.icon %>
+

<%= feature.title %>

+

<%= feature.description %>

+
+ <% }); %> +
+
+
+ + diff --git a/src/views/subdomain.ejs b/src/views/subdomain.ejs new file mode 100644 index 0000000..c18179d --- /dev/null +++ b/src/views/subdomain.ejs @@ -0,0 +1,54 @@ + + + + + + + <%= domain.name %> - BNext Dev + + + +
+
+ +
+ +
+
+ + ← Back to Domains + + + + + +
+
+
+ + diff --git a/src/views/suggestTopic.ejs b/src/views/suggestTopic.ejs new file mode 100644 index 0000000..349378b --- /dev/null +++ b/src/views/suggestTopic.ejs @@ -0,0 +1,97 @@ + + + + + + + Suggest a New Topic - BNext Dev + + + +
+
+ +
+ +
+
+ + ← Back to Topics + + + + +
+
+
+

How Community Contributions Work

+

Your suggestion will be reviewed by the community through voting. High-quality, relevant topics will rise to the top and help others in their learning journey!

+
+
+ +
+
+
+ + +

Clear and concise titles work best

+
+ +
+ + +

Help learners understand the topic's value

+
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+
+ + diff --git a/src/views/topics.ejs b/src/views/topics.ejs new file mode 100644 index 0000000..fd64ac2 --- /dev/null +++ b/src/views/topics.ejs @@ -0,0 +1,54 @@ + + + + + + + <%= categoryName %> Topics - BNext Dev + + + +
+
+ + User +
+
+ +
+ + Back + + + + +
+ <% topics.forEach((topic, index) => { %> +
+
+

<%= topic.title %>

+ + <%= topic.difficulty %> + +
+

<%= topic.description %>

+ + View Topics + +
+ <% }); %> +
+
+ + diff --git a/src/views/topicsPage.ejs b/src/views/topicsPage.ejs new file mode 100644 index 0000000..078dad4 --- /dev/null +++ b/src/views/topicsPage.ejs @@ -0,0 +1,58 @@ + + + + + + + <%= topic.name %> Topics - BNext Dev + + + +
+
+ +
+ +
+
+ + ← Back + + + + +
+ <% topic.items.forEach(item => { %> +
+
+

<%= item.title %>

+ + <%= item.difficulty %> + +
+

<%= item.description %>

+ + View Topics → + +
+ <% }); %> +
+
+
+
+ +