diff --git a/package-lock.json b/package-lock.json index ac2f927..c87708f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,11 +8,18 @@ "name": "antigravity_dev", "version": "0.1.0", "dependencies": { + "@types/react-google-recaptcha": "^2.1.9", + "axios": "^1.13.2", + "lucide-react": "^0.554.0", "next": "16.0.3", "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", - "tailwindcss-animate": "^1.0.7" + "react-google-recaptcha": "^3.1.0", + "selenium-webdriver": "^4.38.0", + "sitemap": "^9.0.0", + "tailwindcss-animate": "^1.0.7", + "xml2js": "^0.6.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -278,6 +285,12 @@ "node": ">=6.9.0" } }, + "node_modules/@bazel/runfiles": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz", + "integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==", + "license": "Apache-2.0" + }, "node_modules/@emnapi/core": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz", @@ -1551,7 +1564,6 @@ "version": "20.19.25", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz", "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", - "dev": true, "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -1561,7 +1573,6 @@ "version": "19.2.6", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.6.tgz", "integrity": "sha512-p/jUvulfgU7oKtj6Xpk8cA2Y1xKTtICGpJYeJXz2YVO2UcvjQgeRMLDGfDeqeRW2Ta+0QNFwcc8X3GH8SxZz6w==", - "dev": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -1577,6 +1588,24 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/react-google-recaptcha": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz", + "integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==", + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, + "node_modules/@types/sax": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz", + "integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.47.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz", @@ -2203,6 +2232,12 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2397,6 +2432,12 @@ "node": ">= 0.4" } }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -2423,6 +2464,17 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz", + "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2531,7 +2583,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -2631,6 +2682,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2645,6 +2708,12 @@ "dev": true, "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2664,7 +2733,6 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, "license": "MIT" }, "node_modules/damerau-levenshtein": { @@ -2789,6 +2857,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2816,7 +2893,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", @@ -2928,7 +3004,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2938,7 +3013,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -2976,7 +3050,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -2989,7 +3062,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -3604,6 +3676,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, "node_modules/for-each": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", @@ -3620,11 +3712,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -3685,7 +3792,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -3710,7 +3816,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", @@ -3798,7 +3903,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3877,7 +3981,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -3890,7 +3993,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" @@ -3906,7 +4008,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -3932,6 +4033,15 @@ "hermes-estree": "0.25.1" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3942,6 +4052,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "license": "MIT" + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -3969,6 +4085,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -4445,7 +4567,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4524,6 +4645,18 @@ "node": ">=4.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4568,6 +4701,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.30.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", @@ -4856,7 +4998,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -4875,6 +5016,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.554.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", + "integrity": "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -4889,7 +5039,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4919,6 +5068,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -5091,7 +5261,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -5278,6 +5447,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "license": "(MIT AND Zlib)" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5386,11 +5561,16 @@ "node": ">= 0.8.0" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.4.0", @@ -5398,6 +5578,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -5438,6 +5624,19 @@ "node": ">=0.10.0" } }, + "node_modules/react-async-script": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz", + "integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==", + "license": "MIT", + "dependencies": { + "hoist-non-react-statics": "^3.3.0", + "prop-types": "^15.5.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-dom": { "version": "19.2.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", @@ -5450,11 +5649,44 @@ "react": "^19.2.0" } }, + "node_modules/react-google-recaptcha": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz", + "integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.5.0", + "react-async-script": "^1.2.0" + }, + "peerDependencies": { + "react": ">=16.4.1" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true, + "license": "MIT" + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "license": "MIT" }, "node_modules/reflect.getprototypeof": { @@ -5597,6 +5829,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/safe-push-apply": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", @@ -5632,12 +5870,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sax": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz", + "integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==", + "license": "BlueOak-1.0.0" + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, + "node_modules/selenium-webdriver": { + "version": "4.38.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.38.0.tgz", + "integrity": "sha512-5/UXXFSQmn7FGQkbcpAqvfhzflUdMWtT7QqpEgkFD6Q6rDucxB5EUfzgjmr6JbUj30QodcW3mDXehzoeS/Vy5w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.3.1", + "jszip": "^3.10.1", + "tmp": "^0.2.5", + "ws": "^8.18.3" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -5697,6 +5966,12 @@ "node": ">= 0.4" } }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "license": "MIT" + }, "node_modules/sharp": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", @@ -5854,6 +6129,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sitemap": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.0.tgz", + "integrity": "sha512-J/SU27FJ+I52TcDLKZzPRRVQUMj0Pp1i/HLb2lrkU+hrMLM+qdeRjdacrNxnSW48Waa3UcEOGOdX1+0Lob7TgA==", + "license": "MIT", + "dependencies": { + "@types/node": "^24.9.2", + "@types/sax": "^1.2.1", + "arg": "^5.0.0", + "sax": "^1.4.1" + }, + "bin": { + "sitemap": "dist/esm/cli.js" + }, + "engines": { + "node": ">=20.19.5", + "npm": ">=10.8.2" + } + }, + "node_modules/sitemap/node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/sitemap/node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "license": "MIT" + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5884,6 +6193,15 @@ "node": ">= 0.4" } }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/string.prototype.includes": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", @@ -6146,6 +6464,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tmp": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", + "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==", + "license": "MIT", + "engines": { + "node": ">=14.14" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6356,7 +6683,6 @@ "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, "license": "MIT" }, "node_modules/unrs-resolver": { @@ -6435,6 +6761,12 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -6550,6 +6882,49 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xml2js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz", + "integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==", + "license": "MIT", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "license": "MIT", + "engines": { + "node": ">=4.0" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index 8d9103b..d06df5c 100644 --- a/package.json +++ b/package.json @@ -9,11 +9,18 @@ "lint": "eslint" }, "dependencies": { + "@types/react-google-recaptcha": "^2.1.9", + "axios": "^1.13.2", + "lucide-react": "^0.554.0", "next": "16.0.3", "next-themes": "^0.4.6", "react": "19.2.0", "react-dom": "19.2.0", - "tailwindcss-animate": "^1.0.7" + "react-google-recaptcha": "^3.1.0", + "selenium-webdriver": "^4.38.0", + "sitemap": "^9.0.0", + "tailwindcss-animate": "^1.0.7", + "xml2js": "^0.6.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", diff --git a/public/assets/images/about/about-banner.webp b/public/assets/images/about/about-banner.webp new file mode 100644 index 0000000..ee4b1fc Binary files /dev/null and b/public/assets/images/about/about-banner.webp differ diff --git a/public/assets/images/about/contact-banner.webp b/public/assets/images/about/contact-banner.webp new file mode 100644 index 0000000..6495329 Binary files /dev/null and b/public/assets/images/about/contact-banner.webp differ diff --git a/public/assets/images/about/lifestyle-banner.webp b/public/assets/images/about/lifestyle-banner.webp new file mode 100644 index 0000000..ebbb733 Binary files /dev/null and b/public/assets/images/about/lifestyle-banner.webp differ diff --git a/public/assets/images/home/back.webp b/public/assets/images/home/back.webp new file mode 100644 index 0000000..80e17fd Binary files /dev/null and b/public/assets/images/home/back.webp differ diff --git a/public/assets/images/home/bottom.webp b/public/assets/images/home/bottom.webp new file mode 100644 index 0000000..418b54e Binary files /dev/null and b/public/assets/images/home/bottom.webp differ diff --git a/public/assets/images/home/experience.webp b/public/assets/images/home/experience.webp new file mode 100644 index 0000000..47b2359 Binary files /dev/null and b/public/assets/images/home/experience.webp differ diff --git a/public/assets/images/home/faq-2.webp b/public/assets/images/home/faq-2.webp new file mode 100644 index 0000000..8b93243 Binary files /dev/null and b/public/assets/images/home/faq-2.webp differ diff --git a/public/assets/images/home/faq-3.webp b/public/assets/images/home/faq-3.webp new file mode 100644 index 0000000..889ac80 Binary files /dev/null and b/public/assets/images/home/faq-3.webp differ diff --git a/public/assets/images/home/faq.webp b/public/assets/images/home/faq.webp new file mode 100644 index 0000000..55332fe Binary files /dev/null and b/public/assets/images/home/faq.webp differ diff --git a/public/assets/images/home/front.webp b/public/assets/images/home/front.webp new file mode 100644 index 0000000..a7baa04 Binary files /dev/null and b/public/assets/images/home/front.webp differ diff --git a/public/assets/images/home/left.webp b/public/assets/images/home/left.webp new file mode 100644 index 0000000..9bdad0c Binary files /dev/null and b/public/assets/images/home/left.webp differ diff --git a/public/assets/images/home/ready.webp b/public/assets/images/home/ready.webp new file mode 100644 index 0000000..34e9b46 Binary files /dev/null and b/public/assets/images/home/ready.webp differ diff --git a/public/assets/images/home/right.webp b/public/assets/images/home/right.webp new file mode 100644 index 0000000..c273da9 Binary files /dev/null and b/public/assets/images/home/right.webp differ diff --git a/public/assets/images/home/top.webp b/public/assets/images/home/top.webp new file mode 100644 index 0000000..c20c0fd Binary files /dev/null and b/public/assets/images/home/top.webp differ diff --git a/public/assets/images/home/why/customer-first.webp b/public/assets/images/home/why/customer-first.webp new file mode 100644 index 0000000..909f691 Binary files /dev/null and b/public/assets/images/home/why/customer-first.webp differ diff --git a/public/assets/images/home/why/location.webp b/public/assets/images/home/why/location.webp new file mode 100644 index 0000000..a3e1762 Binary files /dev/null and b/public/assets/images/home/why/location.webp differ diff --git a/public/assets/images/home/why/smart-home.webp b/public/assets/images/home/why/smart-home.webp new file mode 100644 index 0000000..3461441 Binary files /dev/null and b/public/assets/images/home/why/smart-home.webp differ diff --git a/public/assets/images/home/why/transparent-deals.webp b/public/assets/images/home/why/transparent-deals.webp new file mode 100644 index 0000000..57b7887 Binary files /dev/null and b/public/assets/images/home/why/transparent-deals.webp differ diff --git a/public/assets/images/projects/barca.webp b/public/assets/images/projects/barca.webp new file mode 100644 index 0000000..e2d9be1 Binary files /dev/null and b/public/assets/images/projects/barca.webp differ diff --git a/public/assets/images/projects/godrej-hoskote.webp b/public/assets/images/projects/godrej-hoskote.webp new file mode 100644 index 0000000..02fd85f Binary files /dev/null and b/public/assets/images/projects/godrej-hoskote.webp differ diff --git a/public/assets/images/projects/godrej-lakeside.webp b/public/assets/images/projects/godrej-lakeside.webp new file mode 100644 index 0000000..37527bb Binary files /dev/null and b/public/assets/images/projects/godrej-lakeside.webp differ diff --git a/public/assets/images/projects/godrej-tiara.webp b/public/assets/images/projects/godrej-tiara.webp new file mode 100644 index 0000000..047fdb2 Binary files /dev/null and b/public/assets/images/projects/godrej-tiara.webp differ diff --git a/public/assets/images/projects/godrej-woods.webp b/public/assets/images/projects/godrej-woods.webp new file mode 100644 index 0000000..1c6b579 Binary files /dev/null and b/public/assets/images/projects/godrej-woods.webp differ diff --git a/public/assets/images/projects/projects-banner.webp b/public/assets/images/projects/projects-banner.webp new file mode 100644 index 0000000..b80f9c4 Binary files /dev/null and b/public/assets/images/projects/projects-banner.webp differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..9e061ad --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://skyandsoil.metatronnest.com/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..f4ad4ac --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1 @@ +https://skyandsoil.metatronnest.com/https://skyandsoil.metatronnest.com/about/https://skyandsoil.metatronnest.com/projects/https://skyandsoil.metatronnest.com/lifestyle/https://skyandsoil.metatronnest.com/contact/https://skyandsoil.metatronnest.com/compare/https://skyandsoil.metatronnest.com/properties/1/https://skyandsoil.metatronnest.com/properties/2/https://skyandsoil.metatronnest.com/properties/3/https://skyandsoil.metatronnest.com/properties/4/https://skyandsoil.metatronnest.com/properties/5/ \ No newline at end of file diff --git a/script/generate-sitemap.cjs b/script/generate-sitemap.cjs new file mode 100644 index 0000000..a2d2834 --- /dev/null +++ b/script/generate-sitemap.cjs @@ -0,0 +1,62 @@ +const fs = require("fs"); +const path = require("path"); +const { SitemapStream, streamToPromise } = require("sitemap"); + +const hostname = "https://skyandsoil.metatronnest.com"; +const addTrailingSlash = true; + +const shouldAddSlash = (url) => { + if (url === "/") return false; + if (/\.[a-z0-9]{2,6}(\?.*)?$/i.test(url)) return false; + return true; +}; + +const formatUrl = (url) => { + if (!url.startsWith("/")) url = "/" + url; + if (addTrailingSlash && shouldAddSlash(url) && !url.endsWith("/")) return url + "/"; + if (!addTrailingSlash && url.endsWith("/") && url !== "/") return url.slice(0, -1); + return url; +}; + +// āœ… Static pages +const staticLinks = [ + { url: "/" }, + { url: "/about/" }, + { url: "/projects/" }, + { url: "/lifestyle/" }, + { url: "/contact/" }, + { url: "/compare/" }, +]; + +// āœ… Dynamic properties (IDs from src/data/properties.ts) +const propertyIds = [1, 2, 3, 4, 5]; +const propertyLinks = propertyIds.map(id => ({ url: `/properties/${id}/` })); + +// Combine static + dynamic links +const allLinks = [...staticLinks, ...propertyLinks].map(link => ({ + url: formatUrl(link.url), +})); + +async function generateSitemap() { + try { + const sitemap = new SitemapStream({ hostname }); + const writeStream = fs.createWriteStream(path.resolve(__dirname, "../public/sitemap.xml")); + + sitemap.pipe(writeStream); + + console.log("šŸ“¦ Writing URLs to sitemap:"); + allLinks.forEach(link => { + console.log(" -", hostname + link.url); + sitemap.write(link); + }); + + sitemap.end(); + await streamToPromise(sitemap); + + console.log("āœ… sitemap.xml created successfully!"); + } catch (error) { + console.error("āŒ Error creating sitemap.xml:", error); + } +} + +generateSitemap(); diff --git a/script/image_alt_issues.csv b/script/image_alt_issues.csv new file mode 100644 index 0000000..a4213bc --- /dev/null +++ b/script/image_alt_issues.csv @@ -0,0 +1 @@ +Page URL,Image Src,Alt Text,Issue Type diff --git a/script/seo-test-selenium.cjs b/script/seo-test-selenium.cjs new file mode 100644 index 0000000..9c94b6e --- /dev/null +++ b/script/seo-test-selenium.cjs @@ -0,0 +1,261 @@ +// šŸš€ Full SEO + Broken Link + 404 + Accessibility + Image Alt CSV Export +// Run with: node seo_full_audit.js + +const { Builder, By } = require("selenium-webdriver"); +const chrome = require("selenium-webdriver/chrome"); +const axios = require("axios"); +const xml2js = require("xml2js"); +const fs = require("fs"); +const path = require("path"); + +// CSV file for Image Alt issues +const csvPath = path.join(__dirname, "image_alt_issues.csv"); +fs.writeFileSync(csvPath, "Page URL,Image Src,Alt Text,Issue Type\n", "utf8"); + +// ========================== +// 1ļøāƒ£ Fetch URLs from sitemap.xml +// ========================== +async function getUrlsFromSitemap(sitemapUrl) { + try { + const res = await axios.get(sitemapUrl); + const parsed = await xml2js.parseStringPromise(res.data); + return parsed.urlset.url.map((u) => u.loc[0]); + } catch (err) { + console.error("āŒ Failed to load sitemap:", err.message); + return []; + } +} + +// ========================== +// 2ļøāƒ£ Check HTTP Status +// ========================== +async function checkLinkStatus(url) { + try { + const res = await axios.get(url, { + timeout: 10000, + validateStatus: () => true, + }); + + if (res.status === 200 && res.data.toLowerCase().includes("page not found")) { + return "Soft 404"; + } + + return res.status; + } catch (err) { + return err.response ? err.response.status : "āŒ No Response"; + } +} + +// ========================== +// 3ļøāƒ£ Main SEO + Accessibility + Image Alt Audit +// ========================== +async function checkSEO(url, siteDomain) { + const options = new chrome.Options(); + options.addArguments("--headless", "--no-sandbox", "--disable-gpu"); + + const driver = await new Builder() + .forBrowser("chrome") + .setChromeOptions(options) + .build(); + + try { + const pageStatus = await checkLinkStatus(url); + if (pageStatus === 404 || pageStatus === "Soft 404") { + console.log(`\n🚫 ${url} → āŒ Page not found (${pageStatus})`); + return; + } + + await driver.get(url); + const pageSource = await driver.getPageSource(); + + // Basic SEO Elements + const title = await driver.getTitle(); + const descElem = await driver.findElements(By.css('meta[name="description"]')); + const canonicalElem = await driver.findElements(By.css('link[rel="canonical"]')); + const robotsElem = await driver.findElements(By.css('meta[name="robots"]')); + const viewportElem = await driver.findElements(By.css('meta[name="viewport"]')); + const charset = await driver.findElements(By.css('meta[charset]')); + const htmlTag = await driver.findElement(By.css("html")); + const langAttr = await htmlTag.getAttribute("lang").catch(() => ""); + const h1Tags = await driver.findElements(By.css("h1")); + const h2Tags = await driver.findElements(By.css("h2")); + + // Meta Description + let descContent = descElem.length > 0 ? await descElem[0].getAttribute("content") : ""; + const descLength = descContent.length; + const descStatus = + descLength === 0 + ? "āŒ Missing" + : descLength < 50 + ? `āš ļø Too short (${descLength})` + : descLength > 160 + ? `āš ļø Too long (${descLength})` + : "āœ… Perfect"; + + // Title length check + const titleLength = title.length; + const titleStatus = + titleLength === 0 + ? "āŒ Missing" + : titleLength < 30 + ? `āš ļø Too short (${titleLength})` + : titleLength > 65 + ? `āš ļø Too long (${titleLength})` + : "āœ… Perfect"; + + // Canonical + const canonicalURL = + canonicalElem.length > 0 ? await canonicalElem[0].getAttribute("href") : "āŒ Missing"; + + // šŸ–¼ļø Image Accessibility Audit + const imgs = await driver.findElements(By.css("img")); + let missingAlt = 0; + let emptyAlt = 0; + let duplicateAlt = []; + const altTextMap = new Map(); + + for (const img of imgs) { + const src = await img.getAttribute("src"); + const alt = (await img.getAttribute("alt"))?.trim() ?? null; + + if (alt === null) { + missingAlt++; + fs.appendFileSync(csvPath, `"${url}","${src}","","Missing Alt"\n`, "utf8"); + continue; + } + + if (alt === "") { + emptyAlt++; + fs.appendFileSync(csvPath, `"${url}","${src}","(empty)","Empty Alt"\n`, "utf8"); + } + + if (altTextMap.has(alt)) { + altTextMap.set(alt, altTextMap.get(alt) + 1); + } else { + altTextMap.set(alt, 1); + } + } + + for (const [altText, count] of altTextMap.entries()) { + if (altText && count > 1) { + duplicateAlt.push({ altText, count }); + fs.appendFileSync( + csvPath, + `"${url}","","${altText}","Duplicate Alt (${count} times)"\n`, + "utf8" + ); + } + } + + // Detect tracking & schema tags + const hasGTM = pageSource.includes("googletagmanager.com/gtm.js"); + const hasClarity = pageSource.includes("clarity.ms/tag"); + const hasFBPixel = pageSource.includes("fbevents.js") || pageSource.includes("fbq("); + const hasAnalytics = pageSource.includes("www.googletagmanager.com/gtag/js"); + + const ogTags = await driver.findElements(By.css("meta[property^='og:']")); + const twitterTags = await driver.findElements(By.css("meta[name^='twitter:']")); + const schemaScripts = await driver.findElements(By.css('script[type="application/ld+json"]')); + + // Links check + const anchorTags = await driver.findElements(By.css("a[href]")); + const brokenLinks = []; + for (const a of anchorTags) { + const href = await a.getAttribute("href"); + if (!href || href.startsWith("#") || href.startsWith("mailto:")) continue; + + const fullUrl = href.startsWith("http") + ? href + : `${siteDomain}${href.startsWith("/") ? href : `/${href}`}`; + + if (fullUrl.includes(siteDomain)) { + const status = await checkLinkStatus(fullUrl); + if (status === 404 || status === "Soft 404" || status === "āŒ No Response") { + brokenLinks.push({ link: fullUrl, status }); + } + } + } + + // Lazy loading check + const images = await driver.findElements(By.css("img, video, iframe")); + const lazyLoadCount = await Promise.all( + images.map(async (img) => { + const loading = await img.getAttribute("loading"); + return loading === "lazy"; + }) + ); + const lazyLoaded = lazyLoadCount.filter((v) => v).length; + + // Console Summary + console.log(`\nšŸ” Checking: ${url}`); + console.log("-------------------------------------------"); + console.log("Title:", titleStatus); + console.log("Meta Description:", descStatus); + console.log("Canonical URL:", canonicalURL); + console.log("Meta Robots:", robotsElem.length > 0 ? "āœ… Found" : "āš ļø Missing"); + console.log("Viewport:", viewportElem.length > 0 ? "āœ… Found" : "āš ļø Missing"); + console.log("Charset:", charset.length > 0 ? "āœ… Found" : "āŒ Missing"); + console.log("HTML lang:", langAttr ? `āœ… ${langAttr}` : "āš ļø Missing"); + console.log("H1 Tags:", h1Tags.length > 0 ? `āœ… ${h1Tags.length}` : "āŒ Missing"); + console.log("H2 Tags:", h2Tags.length > 0 ? `ā„¹ļø ${h2Tags.length}` : "āš ļø None"); + console.log("Images:", imgs.length); + console.log( + "Missing Alt:", + missingAlt > 0 ? `āŒ ${missingAlt}` : "āœ… None" + ); + console.log( + "Empty Alt:", + emptyAlt > 0 ? `āš ļø ${emptyAlt}` : "āœ… None" + ); + console.log( + "Duplicate Alt:", + duplicateAlt.length > 0 ? `āš ļø ${duplicateAlt.length}` : "āœ… None" + ); + console.log("Lazy Loaded Images:", lazyLoaded > 0 ? `āœ… ${lazyLoaded}` : "āš ļø None"); + console.log("Open Graph Tags:", ogTags.length > 0 ? "āœ… Found" : "āš ļø Missing"); + console.log("Twitter Tags:", twitterTags.length > 0 ? "āœ… Found" : "āš ļø Missing"); + console.log("Schema Markup:", schemaScripts.length > 0 ? "āœ… Found" : "āš ļø Missing"); + console.log("Google Analytics:", hasAnalytics ? "āœ… Found" : "āš ļø Missing"); + console.log("GTM:", hasGTM ? "āœ… Found" : "āš ļø Missing"); + console.log("Clarity:", hasClarity ? "āœ… Found" : "āš ļø Missing"); + console.log("Facebook Pixel:", hasFBPixel ? "āœ… Found" : "āš ļø Missing"); + + if (brokenLinks.length > 0) { + console.log("\nāŒ Broken Links:"); + brokenLinks.forEach((b) => console.log(` → ${b.link} [${b.status}]`)); + } else { + console.log("āœ… No broken links found."); + } + + } catch (err) { + console.error(`āŒ Error on ${url}:`, err.message); + } finally { + await driver.quit(); + } +} + +// ========================== +// 4ļøāƒ£ Run Full Site Audit +// ========================== +(async () => { + const sitemapUrl = "http://localhost:3000/sitemap.xml"; // your sitemap + const siteDomain = "https://skyandsoil.metatronnest.com"; // your domain + + console.log("šŸ“„ Fetching URLs from sitemap..."); + const urls = await getUrlsFromSitemap(sitemapUrl); + + if (urls.length === 0) { + console.error("āŒ No URLs found in sitemap."); + return; + } + + console.log(`āœ… Found ${urls.length} URLs in sitemap.`); + console.log("šŸš€ Starting Full SEO + Accessibility + Broken Link Audit..."); + + for (const url of urls) { + await checkSEO(url, siteDomain); + } + + console.log("\nāœ… Full SEO Audit Completed!"); + console.log(`šŸ“ CSV Report: ${csvPath}`); +})(); diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 55fdc50..820c533 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -22,6 +22,7 @@ export default function AboutPage() { { label: "Home", href: "/" }, { label: "About" } ]} + backgroundImage="/assets/images/about/about-banner.webp" />
diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index 788188f..a35f335 100644 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -20,6 +20,7 @@ export default function ContactPage() { { label: "Home", href: "/" }, { label: "Contact" } ]} + backgroundImage="/assets/images/about/contact-banner.webp" />
diff --git a/src/app/globals.css b/src/app/globals.css index 07201ec..61329cf 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -17,6 +17,7 @@ --color-noir-slate: #1C1C1C; --color-porcelain-white: #FFFCF0; --color-limestone: #F3F1E6; + --color-gray-900: lab(8.11897% .811279 -12.254); --font-sans: var(--font-inter); diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2219019..c18e521 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -8,12 +8,25 @@ const inter = Inter({ }); export const metadata: Metadata = { + metadataBase: new URL("https://skyandsoil.metatronnest.com"), title: { default: "Sky and Soil | Premium Real Estate in North Bengaluru", template: "%s | Sky and Soil" }, description: "Discover luxury apartments, villas, and plots in North Bengaluru. Sky and Soil connects you with nature-inspired living spaces and Godrej Properties.", keywords: ["Real Estate", "Bengaluru", "Luxury Homes", "Godrej Properties", "North Bengaluru", "Villas", "Apartments"], + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + }, + }, + + alternates: { + canonical: "./", + }, }; import { ThemeProvider } from "@/components/ThemeProvider"; @@ -28,8 +41,16 @@ export default function RootLayout({ }>) { return ( + + {/* āœ… Meta Robots */} + + + {/* Canonical handled by metadata above */} + +
diff --git a/src/app/properties/[id]/layout.tsx b/src/app/properties/[id]/layout.tsx new file mode 100644 index 0000000..d6c21a7 --- /dev/null +++ b/src/app/properties/[id]/layout.tsx @@ -0,0 +1,34 @@ +import { Metadata } from "next"; +import { properties } from "@/data/properties"; + +// Required for static site generation with dynamic routes +export function generateStaticParams() { + return properties.map((property) => ({ + id: property.id.toString(), + })); +} + +export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise { + const resolvedParams = await params; + const property = properties.find(p => p.id === parseInt(resolvedParams.id)); + + if (!property) { + return { + title: "Property Not Found | Sky and Soil", + description: "The requested property could not be found." + }; + } + + return { + title: `${property.title} | Sky and Soil`, + description: `Explore ${property.title} in ${property.location}. ${property.overview.bhk} ${property.category} starting at ${property.price}.`, + }; +} + +export default function PropertyLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/properties/[id]/metadata.ts b/src/app/properties/[id]/metadata.ts new file mode 100644 index 0000000..5d3da8a --- /dev/null +++ b/src/app/properties/[id]/metadata.ts @@ -0,0 +1,26 @@ +import { Metadata } from "next"; +import { properties } from "@/data/properties"; + +// Required for static site generation with dynamic routes +export function generateStaticParams() { + return properties.map((property) => ({ + id: property.id.toString(), + })); +} + +export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise { + const resolvedParams = await params; + const property = properties.find(p => p.id === parseInt(resolvedParams.id)); + + if (!property) { + return { + title: "Property Not Found | Sky and Soil", + description: "The requested property could not be found." + }; + } + + return { + title: `${property.title} | Sky and Soil`, + description: `Explore ${property.title} in ${property.location}. ${property.overview.bhk} ${property.category} starting at ${property.price}.`, + }; +} diff --git a/src/app/properties/[id]/page.tsx b/src/app/properties/[id]/page.tsx index 48939b5..9bd8bd0 100644 --- a/src/app/properties/[id]/page.tsx +++ b/src/app/properties/[id]/page.tsx @@ -1,4 +1,6 @@ -import { use } from "react"; +"use client"; + +import { use, useState, ChangeEvent, FormEvent, useEffect } from "react"; import Header from "@/components/Header"; import Footer from "@/components/Footer"; import Image from "next/image"; @@ -7,31 +9,19 @@ import PropertyNav from "@/components/PropertyNav"; import InnerBanner from "@/components/InnerBanner"; import { properties } from "@/data/properties"; import { notFound } from "next/navigation"; +import axios from "axios"; -import { Metadata } from "next"; - -// Required for static site generation with dynamic routes -export function generateStaticParams() { - return properties.map((property) => ({ - id: property.id.toString(), - })); +interface FormData { + name: string; + email: string; + phone: string; + message: string; } -export async function generateMetadata({ params }: { params: Promise<{ id: string }> }): Promise { - const resolvedParams = await params; - const property = properties.find(p => p.id === parseInt(resolvedParams.id)); - - if (!property) { - return { - title: "Property Not Found | Sky and Soil", - description: "The requested property could not be found." - }; - } - - return { - title: `${property.title} | Sky and Soil Real Estate`, - description: `Explore ${property.title} in ${property.location}. ${property.overview.bhk} ${property.category} starting at ${property.price}.`, - }; +interface FormErrors { + name?: string; + email?: string; + phone?: string; } const sections = [ @@ -43,10 +33,98 @@ const sections = [ { id: "pricing", label: "Pricing" }, ]; -export default async function PropertyDetailPage({ params }: { params: Promise<{ id: string }> }) { - const resolvedParams = await params; +export default function PropertyDetailPage({ params }: { params: Promise<{ id: string }> }) { + const resolvedParams = use(params); const property = properties.find(p => p.id === parseInt(resolvedParams.id)); + const [formData, setFormData] = useState({ + name: "", + email: "", + phone: "", + message: "", + }); + + const [formErrors, setFormErrors] = useState({}); + const [alert, setAlert] = useState<{ show: boolean; type: string; message: string }>({ + show: false, + type: "", + message: "", + }); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleChange = (e: ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ ...prev, [name]: value })); + // Clear error when user types + if (formErrors[name as keyof FormErrors]) { + setFormErrors(prev => ({ ...prev, [name]: undefined })); + } + }; + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + + const errors: FormErrors = {}; + if (!formData.name.trim()) errors.name = "Name is required."; + if (!formData.phone.trim()) errors.phone = "Phone number is required."; + if (!formData.email.trim()) errors.email = "Email is required."; + + setFormErrors(errors); + if (Object.keys(errors).length > 0) return; + + setIsSubmitting(true); + + const emailData = { + name: formData.name, + phone: formData.phone, + email: formData.email, + subject: `Property Inquiry: ${property?.title} from ${formData.name}`, + message: `Property Inquiry

+ Property: ${property?.title}
+ Location: ${property?.location}
+ Price: ${property?.price}

+ Customer Details:
+ Name: ${formData.name}
+ Phone: ${formData.phone}
+ Email: ${formData.email}
+ Message: ${formData.message || 'N/A'}`, + to: "hello@skyandsoil.com", + senderName: "Sky and Soil Property Inquiry", + }; + + try { + const res = await axios.post("https://mailserver.metatronnest.com/send", emailData, { + headers: { "Content-Type": "application/json" }, + }); + + setAlert({ + show: true, + type: "success", + message: res?.data?.message || "Request sent successfully! We will contact you soon.", + }); + + setFormData({ name: "", email: "", phone: "", message: "" }); + setFormErrors({}); + } catch (error) { + setAlert({ + show: true, + type: "danger", + message: "Failed to send request. Please try again later.", + }); + } finally { + setIsSubmitting(false); + } + }; + + useEffect(() => { + if (alert.show) { + const timer = setTimeout(() => { + setAlert(prev => ({ ...prev, show: false })); + }, 5000); + return () => clearTimeout(timer); + } + }, [alert.show]); + if (!property) { notFound(); } @@ -228,32 +306,61 @@ export default async function PropertyDetailPage({ params }: { params: Promise<{ {/* Contact Form */}

Get in Touch

-
- - - + + {alert.show && ( +
+ {alert.message} +
+ )} + + +
+ + {formErrors.name && {formErrors.name}} +
+
+ + {formErrors.email && {formErrors.email}} +
+
+ + {formErrors.phone && {formErrors.phone}} +