diff --git a/package-lock.json b/package-lock.json index d600dce..7818574 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.6.1", + "axios": "^1.19.0", "framer-motion": "^12.42.2", "gsap": "^3.15.0", "lenis": "^1.3.25", @@ -18,7 +19,9 @@ "next": "16.2.10", "react": "19.2.4", "react-dom": "19.2.4", - "three": "^0.185.1" + "selenium-webdriver": "^4.46.0", + "three": "^0.185.1", + "xml2js": "^0.6.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -29,6 +32,7 @@ "eslint": "^9", "eslint-config-next": "16.2.10", "sharp": "^0.35.3", + "sitemap": "^9.0.1", "tailwindcss": "^4", "typescript": "^5" } @@ -295,6 +299,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/@dimforge/rapier3d-compat": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", @@ -1813,6 +1823,16 @@ "@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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/stats.js": { "version": "0.17.4", "resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", @@ -2499,6 +2519,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", @@ -2532,6 +2564,13 @@ "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==", + "dev": true, + "license": "MIT" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -2726,6 +2765,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", @@ -2752,6 +2797,18 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -2915,7 +2972,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", @@ -3028,6 +3084,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", @@ -3042,6 +3110,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-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -3145,7 +3219,6 @@ "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -3202,6 +3275,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-gpu": { "version": "5.0.70", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", @@ -3244,7 +3326,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", @@ -3375,7 +3456,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" @@ -3385,7 +3465,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" @@ -3423,7 +3502,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0" @@ -3436,7 +3514,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", @@ -4042,6 +4119,26 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "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", @@ -4058,6 +4155,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/framer-motion": { "version": "12.42.2", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", @@ -4089,7 +4202,6 @@ "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" @@ -4153,7 +4265,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", @@ -4178,7 +4289,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", @@ -4272,7 +4382,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" @@ -4350,7 +4459,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" @@ -4363,7 +4471,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" @@ -4379,7 +4486,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", - "dev": true, "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -4411,6 +4517,19 @@ "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", "license": "Apache-2.0" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4474,6 +4593,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", @@ -5072,6 +5197,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", @@ -5496,7 +5633,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" @@ -5541,6 +5677,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.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", @@ -5609,7 +5766,6 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, "license": "MIT" }, "node_modules/nanoid": { @@ -6468,6 +6624,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", @@ -6581,6 +6743,12 @@ "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/promise-worker-transferable": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", @@ -6603,6 +6771,15 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -6677,6 +6854,27 @@ } } }, + "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": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -6829,6 +7027,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", @@ -6864,12 +7068,46 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sax": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.6.1.tgz", + "integrity": "sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==", + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.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.46.0", + "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.46.0.tgz", + "integrity": "sha512-UlTkgnx9y+bf3QxFsrgUyMqC2oy1Yf+qcOs7xIC/XfsUPv/ow7Sicx6SJ7AeOn2/Z+xF1M8SLqyn983wipI82w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/SeleniumHQ" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/selenium" + } + ], + "license": "Apache-2.0", + "dependencies": { + "@bazel/runfiles": "^6.5.0", + "jszip": "^3.10.1", + "tmp": "^0.2.7", + "ws": "^8.21.0" + }, + "engines": { + "node": ">= 20.0.0" + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -6929,6 +7167,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.35.3", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", @@ -7089,6 +7333,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sitemap": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.1.tgz", + "integrity": "sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==", + "dev": true, + "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.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/sitemap/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "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", @@ -7145,6 +7426,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", @@ -7447,6 +7737,15 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tmp": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.7.tgz", + "integrity": "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw==", + "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", @@ -7815,6 +8114,12 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.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/utility-types": { "version": "3.11.0", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", @@ -7949,6 +8254,49 @@ "node": ">=0.10.0" } }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "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 bdbd336..8578d13 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,14 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint" + "lint": "eslint", + "sitemap": "node scripts/generate-sitemap.cjs", + "seo-audit": "node scripts/seo-test-selenium.cjs" }, "dependencies": { "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.6.1", + "axios": "^1.19.0", "framer-motion": "^12.42.2", "gsap": "^3.15.0", "lenis": "^1.3.25", @@ -19,7 +22,9 @@ "next": "16.2.10", "react": "19.2.4", "react-dom": "19.2.4", - "three": "^0.185.1" + "selenium-webdriver": "^4.46.0", + "three": "^0.185.1", + "xml2js": "^0.6.2" }, "devDependencies": { "@tailwindcss/postcss": "^4", @@ -30,6 +35,7 @@ "eslint": "^9", "eslint-config-next": "16.2.10", "sharp": "^0.35.3", + "sitemap": "^9.0.1", "tailwindcss": "^4", "typescript": "^5" } diff --git a/public/images/blog/blog-2-big.webp b/public/images/blog/blog-2-big.webp index 7d48fb5..0ef48ff 100644 Binary files a/public/images/blog/blog-2-big.webp and b/public/images/blog/blog-2-big.webp differ diff --git a/public/images/blog/blog-3-big.webp b/public/images/blog/blog-3-big.webp index bdf57aa..fdbd1eb 100644 Binary files a/public/images/blog/blog-3-big.webp and b/public/images/blog/blog-3-big.webp differ diff --git a/public/images/blog/blog-card-2.webp b/public/images/blog/blog-card-2.webp index fd9179b..1cddf27 100644 Binary files a/public/images/blog/blog-card-2.webp and b/public/images/blog/blog-card-2.webp differ diff --git a/public/images/blog/blog-card-3.webp b/public/images/blog/blog-card-3.webp index dcb30f7..c2a15b6 100644 Binary files a/public/images/blog/blog-card-3.webp and b/public/images/blog/blog-card-3.webp differ diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..c4b7f36 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1 @@ +https://mydosaplace.ca/daily1.0https://mydosaplace.ca/menu/weekly0.9https://mydosaplace.ca/about/monthly0.7https://mydosaplace.ca/contact/monthly0.7https://mydosaplace.ca/reservation/weekly0.8https://mydosaplace.ca/blog/weekly0.7https://mydosaplace.ca/blog/dosa-in-waterloo-guide/weekly0.6https://mydosaplace.ca/blog/south-indian-restaurant-waterloo/weekly0.6https://mydosaplace.ca/blog/vegan-friendly-south-indian-food/weekly0.6 \ No newline at end of file diff --git a/scripts/generate-sitemap.cjs b/scripts/generate-sitemap.cjs new file mode 100644 index 0000000..f9cc329 --- /dev/null +++ b/scripts/generate-sitemap.cjs @@ -0,0 +1,91 @@ +const fs = require("fs"); +const path = require("path"); +const { SitemapStream, streamToPromise } = require("sitemap"); + +// āœ… My Dosa Place — Production domain +const hostname = "https://mydosaplace.ca"; + +// āœ… Next.js is configured with trailingSlash: true (see next.config.ts) +const addTrailingSlash = true; + +// šŸ”§ Utility: Add trailing slash only for "directory-like" URLs +const shouldAddSlash = (url) => { + // Keep "/" as-is + if (url === "/") return false; + // Don't touch file-like URLs (has extension, e.g. .xml, .json) + if (/\.[a-z0-9]{2,6}(\?.*)?$/i.test(url)) return false; + return true; +}; + +const formatUrl = (url) => { + // Normalize to leading slash + 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; +}; + +const staticLinks = [ + // Home — highest priority, changes daily + { url: "/", changefreq: "daily", priority: 1.0 }, + + // Core pages + { url: "/menu/", changefreq: "weekly", priority: 0.9 }, + { url: "/about/", changefreq: "monthly", priority: 0.7 }, + { url: "/contact/", changefreq: "monthly", priority: 0.7 }, + { url: "/reservation/",changefreq: "weekly", priority: 0.8 }, + + // Blog listing + { url: "/blog/", changefreq: "weekly", priority: 0.7 }, +]; + +const blogPosts = [ + { slug: "dosa-in-waterloo-guide" }, + { slug: "south-indian-restaurant-waterloo" }, + { slug: "vegan-friendly-south-indian-food" }, +]; + +const blogLinks = blogPosts.map((post) => ({ + url: `/blog/${post.slug}/`, + changefreq: "weekly", + priority: 0.6, +})); + + +const allLinks = [...staticLinks, ...blogLinks].map((link) => ({ + ...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("\nāœ… sitemap.xml created successfully!"); + console.log(` Total URLs: ${allLinks.length}`); + console.log(` Output: public/sitemap.xml`); + } catch (error) { + console.error("āŒ Error creating sitemap.xml:", error); + } +} + +generateSitemap(); diff --git a/scripts/image_alt_issues.csv b/scripts/image_alt_issues.csv new file mode 100644 index 0000000..a4213bc --- /dev/null +++ b/scripts/image_alt_issues.csv @@ -0,0 +1 @@ +Page URL,Image Src,Alt Text,Issue Type diff --git a/scripts/seo-test-selenium.cjs b/scripts/seo-test-selenium.cjs new file mode 100644 index 0000000..ad04c21 --- /dev/null +++ b/scripts/seo-test-selenium.cjs @@ -0,0 +1,266 @@ +// šŸš€ 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 && + ( + /404/i.test(res.data.match(/]*>(.*?)<\/title>/)?.[1] ?? "") + ) + ) { + 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"; + const siteDomain = "http://localhost:3000"; + + 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/layout.tsx b/src/app/about/layout.tsx new file mode 100644 index 0000000..3a62bba --- /dev/null +++ b/src/app/about/layout.tsx @@ -0,0 +1,17 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "About Us | Authentic South Indian Restaurant in Waterloo", + description: "Learn the story behind My Dosa Place — Waterloo's authentic South Indian restaurant serving heritage recipes, pure veg dishes & warm hospitality.", + alternates: { + canonical: "/about/", + }, +}; + +export default function AboutLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 61dd133..cde09fd 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -79,7 +79,7 @@ export default function AboutPage() {
{/* Faint temple sketch background */}
- + About Us Heritage Background
@@ -158,7 +158,7 @@ export default function AboutPage() {
{/* My Dosa Place */} @@ -219,7 +219,7 @@ export default function AboutPage() { {/* Leaves bg bottom right */}
- + Decorative Clean Leaf
@@ -297,7 +297,7 @@ export default function AboutPage() {
{/* Background image overlay */}
- + Our Values Background Texture
{/* Intricate Gold Border */} @@ -356,7 +356,7 @@ export default function AboutPage() {
Card Frame
- + Testimonial Section Background
@@ -478,7 +478,7 @@ export default function AboutPage() { {/* Background texture for the whole section */}
- + FAQ Section Background
@@ -709,7 +709,7 @@ export default function AboutPage() { {/* Background Image: cta-bg.webp */}
- + Call to Action Background
{/* Center Content */} diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index 73bd94e..d3f3888 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -1,3 +1,4 @@ +import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; import { notFound } from "next/navigation"; @@ -6,6 +7,27 @@ import { BLOG_POSTS } from "@/utils/blogData"; import InnerBanner from "@/components/InnerBanner"; import AnimatedSection from "@/components/AnimatedSection"; +export async function generateMetadata({ + params, +}: { + params: Promise<{ slug: string }>; +}): Promise { + const { slug } = await params; + const post = BLOG_POSTS.find((p) => p.slug === slug); + + if (post) { + return { + title: post.metaTitle || post.title, + description: post.metaDesc || post.desc, + alternates: { + canonical: `/blog/${slug}/`, + }, + }; + } + + return {}; +} + export function generateStaticParams() { return BLOG_POSTS.map((post) => ({ slug: post.slug, diff --git a/src/app/blog/layout.tsx b/src/app/blog/layout.tsx new file mode 100644 index 0000000..ef6ee6a --- /dev/null +++ b/src/app/blog/layout.tsx @@ -0,0 +1,17 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Blog | South Indian Food Guides & Stories, Waterloo", + description: "Dosa-making secrets, South Indian spice guides & festival menus from Waterloo's favourite vegetarian restaurant. Read our latest articles.", + alternates: { + canonical: "/blog/", + }, +}; + +export default function BlogLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx index 5647c44..c5be64e 100644 --- a/src/app/blog/page.tsx +++ b/src/app/blog/page.tsx @@ -32,7 +32,7 @@ export default function BlogPage() {
{/* Subtle background texture */}
- + Blog Story Background
diff --git a/src/app/contact/layout.tsx b/src/app/contact/layout.tsx new file mode 100644 index 0000000..1e8fb0e --- /dev/null +++ b/src/app/contact/layout.tsx @@ -0,0 +1,17 @@ +import { Metadata } from "next"; + +export const metadata: Metadata = { + title: "Contact & Reservations | My Dosa Place, Waterloo", + description: "Visit My Dosa Place at 922 Erb St W, Waterloo. Call (519) 888-7459 to reserve a table, order takeout, or ask about catering.", + alternates: { + canonical: "/contact/", + }, +}; + +export default function ContactLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/src/app/contact/page.tsx b/src/app/contact/page.tsx index 1f4f628..37e4093 100644 --- a/src/app/contact/page.tsx +++ b/src/app/contact/page.tsx @@ -51,7 +51,7 @@ export default function ContactPage() {
Background @@ -306,7 +306,7 @@ export default function ContactPage() {
Background @@ -377,7 +377,7 @@ export default function ContactPage() {
Background @@ -426,7 +426,7 @@ export default function ContactPage() {
diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 25346fb..379bfa4 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -16,13 +16,21 @@ const montserrat = Montserrat({ }); export const metadata: Metadata = { - title: "My Dosa Place - Pure Vegetarian South Indian Heritage Dining", - description: "Experience the authentic culinary legacy of South India at My Dosa Place in Waterloo, ON. A premium dining experience featuring ghee-roasted dosas, sambar, and home-ground chutneys.", + metadataBase: new URL("https://mydosaplace.ca"), + title: "Best Dosa & South Indian Restaurant in Waterloo | My Dosa Place", + description: "My Dosa Place is Waterloo's 100% pure vegetarian South Indian restaurant ,authentic masala dosa, thali & filter coffee. Dine in, takeout or catering.", keywords: ["My Dosa Place", "Waterloo South Indian Restaurant", "Pure Vegetarian Indian", "Waterloo Dosa", "Traditional Indian Feast", "Waterloo Dining"], authors: [{ name: "My Dosa Place" }], + alternates: { + canonical: "/", + }, + robots: { + index: true, + follow: true, + }, openGraph: { - title: "My Dosa Place - Pure Vegetarian South Indian Heritage Dining", - description: "Experience the authentic culinary legacy of South India at My Dosa Place in Waterloo, ON.", + title: "Best Dosa & South Indian Restaurant in Waterloo | My Dosa Place", + description: "My Dosa Place is Waterloo's 100% pure vegetarian South Indian restaurant ,authentic masala dosa, thali & filter coffee. Dine in, takeout or catering.", images: ["/images/chettinad_exterior.png"], } }; @@ -39,7 +47,7 @@ export default function RootLayout({ lang="en" className={`${cinzel.variable} ${montserrat.variable} h-full antialiased`} > - +