diff --git a/backend/.env.example b/backend/.env.example deleted file mode 100644 index fa719061..00000000 --- a/backend/.env.example +++ /dev/null @@ -1,15 +0,0 @@ -PORT=5000 -MONGO_URI=mongodb://localhost:27017/os-compass -JWT_SECRET=your_jwt_secret_here -SESSION_SECRET=your_session_secret_here -GITHUB_CLIENT_ID=your_github_client_id -GITHUB_CLIENT_SECRET=your_github_client_secret -GITHUB_CALLBACK_URL=http://localhost:5000/api/auth/github/callback -FRONTEND_URL=http://localhost:5500 -EMAIL_HOST=smtp.mailtrap.io -EMAIL_PORT=2525 -EMAIL_USER=your_user -EMAIL_PASS=your_pass -EMAIL_FROM=noreply@opensource-compass.org -GEMINI_API_KEY= -GEMINI_MODEL=gemini-2.5-flash diff --git a/backend/package-lock.json b/backend/package-lock.json index 75ec0189..11ceffa3 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -22,6 +22,9 @@ "nodemailer": "^7.0.13", "passport": "^0.7.0", "passport-github2": "^0.1.12" + }, + "devDependencies": { + "nodemon": "^3.1.14" } }, "node_modules/@google/generative-ai": { @@ -70,6 +73,20 @@ "node": ">= 0.6" } }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -87,6 +104,16 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, "node_modules/base64url": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/base64url/-/base64url-3.0.1.tgz", @@ -105,6 +132,19 @@ "bcrypt": "bin/bcrypt" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/body-parser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", @@ -129,6 +169,32 @@ "url": "https://opencollective.com/express" } }, + "node_modules/brace-expansion": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", + "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/bson": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/bson/-/bson-7.0.0.tgz", @@ -182,6 +248,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -502,6 +593,19 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -598,6 +702,21 @@ "node": ">= 0.8" } }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -644,6 +763,19 @@ "node": ">= 0.4" } }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -656,6 +788,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -731,6 +873,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -746,6 +895,52 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", @@ -907,6 +1102,22 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimatch": { + "version": "10.2.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", + "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/mongodb": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-7.0.0.tgz", @@ -1029,6 +1240,45 @@ "node": ">=6.0.0" } }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/oauth": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.10.2.tgz", @@ -1167,6 +1417,19 @@ "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -1186,6 +1449,13 @@ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "license": "MIT" }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1243,6 +1513,19 @@ "node": ">= 0.10" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -1426,6 +1709,19 @@ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", "license": "MIT" }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", @@ -1444,6 +1740,32 @@ "node": ">= 0.8" } }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1453,6 +1775,16 @@ "node": ">=0.6" } }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, "node_modules/tr46": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", @@ -1497,6 +1829,13 @@ "integrity": "sha512-IevTus0SbGwQzYh3+fRsAMTVVPOoIVufzacXcHPmdlle1jUpq7BRL+mw3dgeLanvGZdwwbWhRV6XrcFNdBmjWA==", "license": "MIT" }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/backend/package.json b/backend/package.json index ac12de85..ed0f5148 100644 --- a/backend/package.json +++ b/backend/package.json @@ -25,5 +25,8 @@ "passport": "^0.7.0", "passport-github2": "^0.1.12" }, - "description": "" + "description": "", + "devDependencies": { + "nodemon": "^3.1.14" + } } diff --git a/frontend/js/home.js b/frontend/js/home.js index c896e5b8..69dc3fe3 100644 --- a/frontend/js/home.js +++ b/frontend/js/home.js @@ -1,4 +1,74 @@ document.addEventListener('DOMContentLoaded', () => { + // Replace existing newsletter form handler with enhanced validation + const newsletterForm = document.querySelector('.newsletter-form'); + + if (newsletterForm) { + newsletterForm.addEventListener('submit', (e) => { + e.preventDefault(); + + const emailInput = newsletterForm.querySelector('input[type="email"]'); + const email = emailInput.value.trim(); + const messageDiv = document.getElementById('newsletterMessage') || createMessageDiv(newsletterForm); + + // Enhanced email validation - more comprehensive regex + const emailRegex = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/; + + // Clear previous messages + messageDiv.textContent = ''; + messageDiv.className = 'newsletter-message'; + + // Validate email format + if (!emailRegex.test(email)) { + showMessage(messageDiv, 'Please enter a valid email address (e.g., name@example.com)', 'error'); + emailInput.focus(); + return; + } + + try { + // Store in localStorage for demo + const subscribers = JSON.parse(localStorage.getItem('newsletter_subscribers') || '[]'); + + if (!subscribers.includes(email)) { + subscribers.push(email); + localStorage.setItem('newsletter_subscribers', JSON.stringify(subscribers)); + showMessage(messageDiv, `Thank you for subscribing! We'll send updates to ${email}`, 'success'); + emailInput.value = ''; + } else { + showMessage(messageDiv, 'This email is already subscribed!', 'info'); + } + } catch (error) { + console.error('Newsletter subscription error:', error); + showMessage(messageDiv, 'An error occurred. Please try again.', 'error'); + } + }); + } + + // Helper function to create message div if it doesn't exist + function createMessageDiv(form) { + let messageDiv = document.getElementById('newsletterMessage'); + if (!messageDiv) { + messageDiv = document.createElement('div'); + messageDiv.id = 'newsletterMessage'; + messageDiv.className = 'newsletter-message'; + form.appendChild(messageDiv); + } + return messageDiv; + } + + // Helper function to show messages + function showMessage(element, text, type) { + element.textContent = text; + element.classList.add(type); + + // Auto-hide success messages after 5 seconds + if (type === 'success') { + setTimeout(() => { + element.textContent = ''; + element.classList.remove(type); + }, 5000); + } + } + // Fade-in sections/cards const prefersReducedMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches; @@ -24,9 +94,8 @@ document.addEventListener('DOMContentLoaded', () => { } // Stats counter animation - const statItems = document.querySelectorAll('.stat-item'); - if (statItems.length && 'IntersectionObserver' in window) { - // Function to animate counter + const statNumbers = document.querySelectorAll('.stat-number'); + if (statNumbers.length && 'IntersectionObserver' in window) { function animateCounter(element, target, suffix, duration = 2000) { const startTime = performance.now(); const startValue = 0; @@ -34,45 +103,53 @@ document.addEventListener('DOMContentLoaded', () => { function updateCounter(currentTime) { const elapsed = currentTime - startTime; const progress = Math.min(elapsed / duration, 1); - + // Easing function for smooth animation const easeOutQuart = 1 - Math.pow(1 - progress, 4); - const currentValue = Math.floor(easeOutQuart * target); - - element.textContent = currentValue + suffix; + let currentValue = Math.floor(easeOutQuart * target); + + // Format large numbers + if (target >= 1000) { + currentValue = Math.floor(currentValue / 1000); + element.textContent = currentValue + 'K' + suffix; + } else { + element.textContent = currentValue + suffix; + } if (progress < 1) { requestAnimationFrame(updateCounter); } else { // Ensure final value is exact - element.textContent = target + suffix; + if (target >= 1000) { + element.textContent = Math.floor(target / 1000) + 'K' + suffix; + } else { + element.textContent = target + suffix; + } } } requestAnimationFrame(updateCounter); } - // Create observer for stats const statsObserver = new IntersectionObserver( (entries, observer) => { entries.forEach(entry => { if (entry.isIntersecting) { - const statItem = entry.target; - const numberElement = statItem.querySelector('h2'); - + const numberElement = entry.target; + if (numberElement && !numberElement.hasAttribute('data-animated')) { - const target = parseFloat(numberElement.getAttribute('data-target') || '0'); + const target = parseInt(numberElement.getAttribute('data-target') || '0'); const suffix = numberElement.getAttribute('data-suffix') || ''; - + // Mark as animated to prevent re-running numberElement.setAttribute('data-animated', 'true'); - + // Start animation with small delay setTimeout(() => { animateCounter(numberElement, target, suffix); }, 200); - - observer.unobserve(statItem); + + observer.unobserve(numberElement); } } }); @@ -83,8 +160,7 @@ document.addEventListener('DOMContentLoaded', () => { } ); - // Observe each stat item - statItems.forEach(item => statsObserver.observe(item)); + statNumbers.forEach(item => statsObserver.observe(item)); } // Lightweight parallax: move only the visual block (not the whole hero) @@ -107,18 +183,6 @@ document.addEventListener('DOMContentLoaded', () => { ); } } - - // Newsletter demo - const form = document.querySelector('.newsletter form'); - if (form) { - form.addEventListener('submit', (e) => { - e.preventDefault(); - const email = form.querySelector('input[type="email"]')?.value?.trim(); - if (!email) return; - alert(`Thanks! You'll get updates at ${email}.`); - form.reset(); - }); - } }); // =============================== @@ -190,6 +254,7 @@ if (scrollProgressBar) { cursor.style.opacity = ''; }); })(); + // =============================== // Back to Top Button // =============================== @@ -197,7 +262,6 @@ const scrollTopBtn = document.getElementById('scrollTopBtn'); const scrollThreshold = 300; if (scrollTopBtn) { - const toggleScrollButton = () => { if (window.scrollY > scrollThreshold) { scrollTopBtn.classList.add('show'); @@ -212,130 +276,11 @@ if (scrollTopBtn) { scrollTopBtn.addEventListener('click', () => { window.scrollTo({ top: 0, behavior: 'smooth' }); }); - } - -document.addEventListener("DOMContentLoaded", () => { - const counters = document.querySelectorAll(".stat-number"); - - const animateCounter = (el) => { - const target = +el.dataset.target; - const suffix = el.dataset.suffix || ""; - const duration = 1500; - const startTime = performance.now(); - - const update = (currentTime) => { - const progress = Math.min((currentTime - startTime) / duration, 1); - const value = Math.floor(progress * target); - - if (target >= 1000) { - el.textContent = `${Math.floor(value / 1000)}K${suffix}`; - } else { - el.textContent = `${value}${suffix}`; - } - - if (progress < 1) { - requestAnimationFrame(update); - } else { - el.textContent = - target >= 1000 - ? `${target / 1000}K${suffix}` - : `${target}${suffix}`; - } - }; - - requestAnimationFrame(update); - }; - - const observer = new IntersectionObserver( - (entries, obs) => { - entries.forEach((entry) => { - if (entry.isIntersecting) { - animateCounter(entry.target); - obs.unobserve(entry.target); - } - }); - }, - { threshold: 0.6 } - ); - - counters.forEach((counter) => observer.observe(counter)); -}); - -document.addEventListener("DOMContentLoaded", () => { - const journey = document.querySelector(".why-journey"); - - if (!journey) return; - - const observer = new IntersectionObserver( - ([entry]) => { - if (entry.isIntersecting) { - journey.classList.add("is-visible"); - observer.unobserve(journey); - } - }, - { threshold: 0.4 } - ); - - observer.observe(journey); -}); - -document.addEventListener("DOMContentLoaded", () => { - const counters = document.querySelectorAll(".counter"); - const statsSection = document.querySelector(".stats"); - - let hasAnimated = false; - - const animateCounter = (counter) => { - const target = +counter.getAttribute("data-target"); - const suffix = counter.getAttribute("data-suffix") || ""; - const duration = 1200; // animation duration - const startTime = performance.now(); - - const updateCounter = (currentTime) => { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - const value = Math.floor(progress * target); - - // Format large numbers - let displayValue = value; - if (target >= 1000) { - displayValue = Math.floor(value / 1000); - } - - counter.innerText = displayValue + suffix; - - if (progress < 1) { - requestAnimationFrame(updateCounter); - } else { - // Final accurate value - if (target >= 1000) { - counter.innerText = Math.floor(target / 1000) + suffix; - } else { - counter.innerText = target + suffix; - } - } - }; - - requestAnimationFrame(updateCounter); - }; - - const observer = new IntersectionObserver((entries) => { - entries.forEach(entry => { - if (entry.isIntersecting && !hasAnimated) { - counters.forEach(counter => animateCounter(counter)); - hasAnimated = true; - observer.disconnect(); - } - }); - }, { - threshold: 0.4 - }); - - observer.observe(statsSection); -}); - +// =============================== +// Modal Functions +// =============================== function openModal(program) { const modal = document.getElementById("programModal"); const title = document.getElementById("modalTitle"); @@ -370,29 +315,241 @@ function openModal(program) { "Submit quality PRs", "Show consistency" ] + }, + gssoc: { + title: "GirlScript Summer of Code", + basic: ` + 📅 Duration: 3 Months
+ 🎓 Eligibility: Students & beginners
+ 🏆 Perks: Certificate & swag
+ ⏳ Timeline: Feb–May + `, + skills: [ + "Basic programming knowledge", + "Git & GitHub basics", + "Willingness to learn" + ], + prepare: [ + "Join the community", + "Start with beginner issues", + "Follow project guidelines" + ], + tips: [ + "Be consistent", + "Ask for help when stuck", + "Document your learning" + ] + }, + hack: { + title: "Hacktoberfest", + basic: ` + 📅 Duration: 1 Month
+ 🌍 Eligibility: Everyone
+ 🎁 Perks: Swags & goodies
+ ⏳ Timeline: October + `, + skills: [ + "Basic Git knowledge", + "Ability to read code", + "Problem solving" + ], + prepare: [ + "Find repositories you like", + "Look for hacktoberfest-labeled issues", + "Understand contribution guidelines" + ], + tips: [ + "Quality over quantity", + "Don't spam PRs", + "Engage with maintainers" + ] + }, + swoc: { + title: "Social Winter of Code", + basic: ` + 📅 Duration: 3 Months
+ 👩‍💻 Eligibility: Beginners
+ 🏅 Perks: Certificate
+ ⏳ Timeline: Dec–Feb + `, + skills: [ + "Basic programming", + "Enthusiasm to learn", + "Git basics" + ], + prepare: [ + "Explore the platform", + "Join Discord/Slack", + "Find a mentor" + ], + tips: [ + "Start early", + "Be active in community", + "Complete tasks consistently" + ] + }, + linux: { + title: "Linux Foundation Mentorship", + basic: ` + 📅 Duration: Varies
+ 👨‍💻 Eligibility: Developers
+ 💰 Stipend: Paid mentorship
+ 🌐 Timeline: Yearly + `, + skills: [ + "Strong programming skills", + "Linux/Unix familiarity", + "Open source experience" + ], + prepare: [ + "Contribute to LF projects", + "Learn about the ecosystem", + "Connect with mentors" + ], + tips: [ + "Show long-term commitment", + "Build a portfolio", + "Network in the community" + ] + }, + outreachy: { + title: "Outreachy", + basic: ` + 📅 Duration: 3 Months
+ 🌍 Eligibility: Underrepresented groups
+ 💰 Stipend: Paid internship
+ ⏳ Timeline: Twice a year + `, + skills: [ + "Project-specific skills", + "Communication", + "Self-motivation" + ], + prepare: [ + "Make initial contributions", + "Complete the application", + "Engage with community" + ], + tips: [ + "Apply early", + "Be thorough in application", + "Show genuine interest" + ] + }, + mlh: { + title: "MLH Fellowship", + basic: ` + 📅 Duration: 12 Weeks
+ 🎓 Eligibility: Students
+ 💰 Stipend: Paid
+ ⏳ Timeline: Spring/Fall + `, + skills: [ + "Team collaboration", + "Project-based learning", + "Open source interest" + ], + prepare: [ + "Build personal projects", + "Join MLH events", + "Practice coding" + ], + tips: [ + "Show passion for tech", + "Be a team player", + "Learn continuously" + ] + }, + kde: { + title: "Season of KDE", + basic: ` + 📅 Duration: 3 Months
+ 🌍 Eligibility: Everyone
+ 🏆 Perks: Mentorship
+ ⏳ Timeline: Jan–Apr + `, + skills: [ + "Qt/C++ basics", + "Open source interest", + "Git skills" + ], + prepare: [ + "Try KDE applications", + "Join the community", + "Look at beginner bugs" + ], + tips: [ + "Start with documentation", + "Ask questions", + "Be patient" + ] + }, + hyperledger: { + title: "Hyperledger Mentorship", + basic: ` + 📅 Duration: 3 Months
+ 🔗 Focus: Blockchain
+ 💰 Stipend: Paid
+ ⏳ Timeline: Summer + `, + skills: [ + "Blockchain basics", + "Programming (Go/JavaScript)", + "Distributed systems interest" + ], + prepare: [ + "Learn Hyperledger projects", + "Join the community calls", + "Explore documentation" + ], + tips: [ + "Focus on one project", + "Show blockchain interest", + "Contribute early" + ] } }; const programData = data[program]; + + if (programData) { + title.innerHTML = programData.title; + basicInfo.innerHTML = programData.basic; - title.innerHTML = programData.title; - basicInfo.innerHTML = programData.basic; - - skills.innerHTML = ""; - prepare.innerHTML = ""; - tips.innerHTML = ""; + skills.innerHTML = ""; + prepare.innerHTML = ""; + tips.innerHTML = ""; - modal.style.display = "flex"; + modal.style.display = "flex"; + } } function closeModal() { document.getElementById("programModal").style.display = "none"; } -/* Accordion Toggle */ +// Accordion Toggle document.addEventListener("click", function (e) { if (e.target.classList.contains("accordion-header")) { const body = e.target.nextElementSibling; + + // Close other accordions + const allBodies = document.querySelectorAll('.accordion-body'); + allBodies.forEach(b => { + if (b !== body) { + b.style.display = 'none'; + } + }); + + // Toggle current body.style.display = body.style.display === "block" ? "none" : "block"; } -}); \ No newline at end of file +}); + +// Close modal when clicking outside +window.onclick = function(e) { + const modal = document.getElementById("programModal"); + if (e.target === modal) { + modal.style.display = "none"; + } +}; \ No newline at end of file diff --git a/index.html b/index.html index 4c0fb979..f00d15cf 100644 --- a/index.html +++ b/index.html @@ -110,8 +110,6 @@

- - @@ -169,229 +167,36 @@

🎥 Video for Beginners

"> - -
-

Open Source Programs

-

- Explore ongoing and upcoming open-source programs with timelines, - prerequisites, and official resources. -

- -
- -
-
-

Google Summer of Code

-

- A global program focused on bringing new contributors into open - source software development. -

-
- - - - Explore - -
+ +
+
- -
-
-

GSSoC

-

- GirlScript Summer of Code is a three-month-long Open Source program - by GirlScript Foundation. -

-
- - - - Explore - -
-
- -
-
-

Hacktoberfest

-

- A month-long celebration of open-source software run by - DigitalOcean. -

- -
- - - - Explore - -
-
- - -
-
-

Social Winter of Code

-

Beginner-focused winter open-source program.

- -
- - - - Explore - -
-
- - -
-
-

Linux Foundation

-

- Explore various mentorship opportunities within the massive Linux - ecosystem. -

-
- - - - Explore - -
-
- - -
-
-

Outreachy

-

- Provides internships to people subject to systemic bias and - underrepresented in tech. -

-
- - - - Explore - -
-
- - -
-
-

MLH Fellowship

-

- A remote internship-like open source program where you collaborate in pods - on real-world projects with mentorship. -

-
- - - - Explore - -
-
- - - -
-
-

Season of KDE

-

- A mentorship program by KDE that allows contributors to work on open - source desktop and Qt-based projects. -

-
- - - - Explore - -
-
- - -
-
-

Hyperledger Mentorship

-

- A blockchain-focused mentorship program under the Hyperledger open source - ecosystem with guided project contributions. -

-
- + +
+

Open Source Programs

+

+ Explore ongoing and upcoming open-source programs with timelines, + prerequisites, and official resources. +

- - Explore - -
-
+
+ +
+
+

Google Summer of Code

+

+ A global program focused on bringing new contributors into open + source software development. +

+
+ + + Explore + +
@@ -402,14 +207,14 @@

GSSoC

GirlScript Summer of Code is a three-month-long Open Source program by GirlScript Foundation.

- - - - + + Explore + +
@@ -420,14 +225,14 @@

Hacktoberfest

A month-long celebration of open-source software run by DigitalOcean.

- - - - + + Explore + + @@ -435,14 +240,14 @@

Hacktoberfest

Social Winter of Code

Beginner-focused winter open-source program.

- - - - + + Explore + + @@ -453,14 +258,14 @@

Linux Foundation

Explore various mentorship opportunities within the massive Linux ecosystem.

- - - - + + Explore + + @@ -471,14 +276,14 @@

Outreachy

Provides internships to people subject to systemic bias and underrepresented in tech.

- - - - + + Explore + + @@ -489,17 +294,16 @@

MLH Fellowship

A remote internship-like open source program where you collaborate in pods on real-world projects with mentorship.

- - - - + + Explore + + -
@@ -508,14 +312,14 @@

Season of KDE

A mentorship program by KDE that allows contributors to work on open source desktop and Qt-based projects.

- - - - + + Explore + +
@@ -526,14 +330,15 @@

Hyperledger Mentorship

A blockchain-focused mentorship program under the Hyperledger open source ecosystem with guided project contributions.

- - - - + + Explore + + @@ -567,36 +372,10 @@

Start with Strong Foundations

Grow with Mentorship

- Learn alongside maintainers and contributors who’ve already - walked the path you’re starting today. + Learn alongside maintainers and contributors who've already + walked the path you're starting today.

- - - -
- - - - - - - -
-