meta tags updated

This commit is contained in:
Selvi 2026-08-10 18:38:28 +05:30
parent 2700b79be6
commit 227887478e
30 changed files with 893 additions and 56 deletions

382
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": { "dependencies": {
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1", "@react-three/fiber": "^9.6.1",
"axios": "^1.19.0",
"framer-motion": "^12.42.2", "framer-motion": "^12.42.2",
"gsap": "^3.15.0", "gsap": "^3.15.0",
"lenis": "^1.3.25", "lenis": "^1.3.25",
@ -18,7 +19,9 @@
"next": "16.2.10", "next": "16.2.10",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "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": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@ -29,6 +32,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.2.10", "eslint-config-next": "16.2.10",
"sharp": "^0.35.3", "sharp": "^0.35.3",
"sitemap": "^9.0.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }
@ -295,6 +299,12 @@
"node": ">=6.9.0" "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": { "node_modules/@dimforge/rapier3d-compat": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz", "resolved": "https://registry.npmjs.org/@dimforge/rapier3d-compat/-/rapier3d-compat-0.12.0.tgz",
@ -1813,6 +1823,16 @@
"@types/react": "*" "@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": { "node_modules/@types/stats.js": {
"version": "0.17.4", "version": "0.17.4",
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.4.tgz", "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" "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": { "node_modules/ajv": {
"version": "6.15.0", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
@ -2532,6 +2564,13 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1" "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": { "node_modules/argparse": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -2726,6 +2765,12 @@
"node": ">= 0.4" "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": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -2752,6 +2797,18 @@
"node": ">=4" "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": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@ -2915,7 +2972,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -3028,6 +3084,18 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -3042,6 +3110,12 @@
"dev": true, "dev": true,
"license": "MIT" "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": { "node_modules/cross-env": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz",
@ -3145,7 +3219,6 @@
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"ms": "^2.1.3" "ms": "^2.1.3"
@ -3202,6 +3275,15 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/detect-gpu": {
"version": "5.0.70", "version": "5.0.70",
"resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz", "resolved": "https://registry.npmjs.org/detect-gpu/-/detect-gpu-5.0.70.tgz",
@ -3244,7 +3326,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.1", "call-bind-apply-helpers": "^1.0.1",
@ -3375,7 +3456,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -3385,7 +3465,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -3423,7 +3502,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0" "es-errors": "^1.3.0"
@ -3436,7 +3514,6 @@
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"es-errors": "^1.3.0", "es-errors": "^1.3.0",
@ -4042,6 +4119,26 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/for-each": {
"version": "0.3.5", "version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@ -4058,6 +4155,22 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/framer-motion": {
"version": "12.42.2", "version": "12.42.2",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz",
@ -4089,7 +4202,6 @@
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT", "license": "MIT",
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
@ -4153,7 +4265,6 @@
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"call-bind-apply-helpers": "^1.0.2", "call-bind-apply-helpers": "^1.0.2",
@ -4178,7 +4289,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dunder-proto": "^1.0.1", "dunder-proto": "^1.0.1",
@ -4272,7 +4382,6 @@
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -4350,7 +4459,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -4363,7 +4471,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"has-symbols": "^1.0.3" "has-symbols": "^1.0.3"
@ -4379,7 +4486,6 @@
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz",
"integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"function-bind": "^1.1.2" "function-bind": "^1.1.2"
@ -4411,6 +4517,19 @@
"integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==", "integrity": "sha512-VSIRpLfRwlAAdGL4wiTucx2ScRipo0ed1FBatWkyt832jC4CReKstga6yIhYVwGu9LOBjuX9wzmRMeQdBJtzEA==",
"license": "Apache-2.0" "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": { "node_modules/ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -4474,6 +4593,12 @@
"node": ">=0.8.19" "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": { "node_modules/internal-slot": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@ -5072,6 +5197,18 @@
"node": ">=4.0" "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": { "node_modules/keyv": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -5496,7 +5633,6 @@
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">= 0.4" "node": ">= 0.4"
@ -5541,6 +5677,27 @@
"node": ">=8.6" "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": { "node_modules/minimatch": {
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
@ -5609,7 +5766,6 @@
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
@ -6468,6 +6624,12 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/parent-module": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -6581,6 +6743,12 @@
"node": ">= 0.8.0" "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": { "node_modules/promise-worker-transferable": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz", "resolved": "https://registry.npmjs.org/promise-worker-transferable/-/promise-worker-transferable-1.0.4.tgz",
@ -6603,6 +6771,15 @@
"react-is": "^16.13.1" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "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": { "node_modules/reflect.getprototypeof": {
"version": "1.0.10", "version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@ -6829,6 +7027,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/safe-push-apply": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "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" "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": { "node_modules/scheduler": {
"version": "0.27.0", "version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT" "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": { "node_modules/semver": {
"version": "6.3.1", "version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -6929,6 +7167,12 @@
"node": ">= 0.4" "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": { "node_modules/sharp": {
"version": "0.35.3", "version": "0.35.3",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.3.tgz",
@ -7089,6 +7333,43 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -7145,6 +7426,15 @@
"node": ">= 0.4" "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": { "node_modules/string.prototype.includes": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "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" "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": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "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" "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": { "node_modules/utility-types": {
"version": "3.11.0", "version": "3.11.0",
"resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz", "resolved": "https://registry.npmjs.org/utility-types/-/utility-types-3.11.0.tgz",
@ -7949,6 +8254,49 @@
"node": ">=0.10.0" "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": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@ -6,11 +6,14 @@
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "eslint" "lint": "eslint",
"sitemap": "node scripts/generate-sitemap.cjs",
"seo-audit": "node scripts/seo-test-selenium.cjs"
}, },
"dependencies": { "dependencies": {
"@react-three/drei": "^10.7.7", "@react-three/drei": "^10.7.7",
"@react-three/fiber": "^9.6.1", "@react-three/fiber": "^9.6.1",
"axios": "^1.19.0",
"framer-motion": "^12.42.2", "framer-motion": "^12.42.2",
"gsap": "^3.15.0", "gsap": "^3.15.0",
"lenis": "^1.3.25", "lenis": "^1.3.25",
@ -19,7 +22,9 @@
"next": "16.2.10", "next": "16.2.10",
"react": "19.2.4", "react": "19.2.4",
"react-dom": "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": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
@ -30,6 +35,7 @@
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "16.2.10", "eslint-config-next": "16.2.10",
"sharp": "^0.35.3", "sharp": "^0.35.3",
"sitemap": "^9.0.1",
"tailwindcss": "^4", "tailwindcss": "^4",
"typescript": "^5" "typescript": "^5"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 16 KiB

1
public/sitemap.xml Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>https://mydosaplace.ca/</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>https://mydosaplace.ca/menu/</loc><changefreq>weekly</changefreq><priority>0.9</priority></url><url><loc>https://mydosaplace.ca/about/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url><url><loc>https://mydosaplace.ca/contact/</loc><changefreq>monthly</changefreq><priority>0.7</priority></url><url><loc>https://mydosaplace.ca/reservation/</loc><changefreq>weekly</changefreq><priority>0.8</priority></url><url><loc>https://mydosaplace.ca/blog/</loc><changefreq>weekly</changefreq><priority>0.7</priority></url><url><loc>https://mydosaplace.ca/blog/dosa-in-waterloo-guide/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>https://mydosaplace.ca/blog/south-indian-restaurant-waterloo/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>https://mydosaplace.ca/blog/vegan-friendly-south-indian-food/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url></urlset>

View File

@ -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();

View File

@ -0,0 +1 @@
Page URL,Image Src,Alt Text,Issue Type
1 Page URL Image Src Alt Text Issue Type

View File

@ -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[^>]*>(.*?)<\/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}`);
})();

17
src/app/about/layout.tsx Normal file
View File

@ -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}</>;
}

View File

@ -79,7 +79,7 @@ export default function AboutPage() {
<div className="relative w-full bg-[#fdfaf4] overflow-hidden pt-24 pb-24"> <div className="relative w-full bg-[#fdfaf4] overflow-hidden pt-24 pb-24">
{/* Faint temple sketch background */} {/* Faint temple sketch background */}
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image src="/images/about/bg/abt-bg.webp" alt="" fill priority className="object-cover object-left mix-blend-multiply" /> <Image src="/images/about/bg/abt-bg.webp" alt="About Us Heritage Background" fill priority className="object-cover object-left mix-blend-multiply" />
</div> </div>
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6"> <div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6">
@ -158,7 +158,7 @@ export default function AboutPage() {
<div className="absolute inset-0 rounded-t-full pointer-events-none" /> <div className="absolute inset-0 rounded-t-full pointer-events-none" />
{/* <Image {/* <Image
src="/images/story_arch.png" src="/images/story_arch.png"
alt="My Dosa Place" alt="My Dosa Place Story Logo"
fill fill
className="object-cover" className="object-cover"
/> */} /> */}
@ -219,7 +219,7 @@ export default function AboutPage() {
{/* Leaves bg bottom right */} {/* Leaves bg bottom right */}
<div className="absolute bottom-0 right-0 w-24 h-24 opacity-30 pointer-events-none rounded-br-3xl overflow-hidden"> <div className="absolute bottom-0 right-0 w-24 h-24 opacity-30 pointer-events-none rounded-br-3xl overflow-hidden">
<Image src="/images/menu_leaf_clean.png" alt="" fill className="object-cover" /> <Image src="/images/menu_leaf_clean.png" alt="Decorative Clean Leaf" fill className="object-cover" />
</div> </div>
</div> </div>
</AnimatedSection> </AnimatedSection>
@ -297,7 +297,7 @@ export default function AboutPage() {
<div className="relative w-full overflow-hidden"> <div className="relative w-full overflow-hidden">
{/* Background image overlay */} {/* Background image overlay */}
<div className="absolute inset-0 z-0 pointer-events-none select-none mix-blend-multiply opacity-50"> <div className="absolute inset-0 z-0 pointer-events-none select-none mix-blend-multiply opacity-50">
<Image src="/images/about/bg/our-values-bg.webp" alt="" fill className="object-cover object-center" /> <Image src="/images/about/bg/our-values-bg.webp" alt="Our Values Background Texture" fill className="object-cover object-center" />
</div> </div>
{/* Intricate Gold Border */} {/* Intricate Gold Border */}
@ -356,7 +356,7 @@ export default function AboutPage() {
<div className="absolute -inset-x-4 sm:-inset-x-6 -inset-y-2 z-0 pointer-events-none"> <div className="absolute -inset-x-4 sm:-inset-x-6 -inset-y-2 z-0 pointer-events-none">
<Image <Image
src="/images/card.png" src="/images/card.png"
alt="Card Frame" alt={`${v.label} Card Frame`}
fill fill
sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 25vw" sizes="(max-width: 768px) 100vw, (max-width: 1024px) 50vw, 25vw"
className="object-fill" className="object-fill"
@ -439,7 +439,7 @@ export default function AboutPage() {
*/} */}
<div className="relative w-full bg-[#fdfaf4] overflow-hidden"> <div className="relative w-full bg-[#fdfaf4] overflow-hidden">
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image src="/images/about/bg/testimonial-bg.webp" alt="" fill className="object-cover object-center opacity-60 mix-blend-multiply" /> <Image src="/images/about/bg/testimonial-bg.webp" alt="Testimonial Section Background" fill className="object-cover object-center opacity-60 mix-blend-multiply" />
</div> </div>
<div className="relative z-10 max-w-6xl mx-auto px-6 py-20 sm:py-24 text-center"> <div className="relative z-10 max-w-6xl mx-auto px-6 py-20 sm:py-24 text-center">
@ -478,7 +478,7 @@ export default function AboutPage() {
{/* Background texture for the whole section */} {/* Background texture for the whole section */}
<div className="absolute inset-0 pointer-events-none"> <div className="absolute inset-0 pointer-events-none">
<Image src="/images/about/bg/faq-bg.webp" alt="" fill className="object-cover object-center" /> <Image src="/images/about/bg/faq-bg.webp" alt="FAQ Section Background" fill className="object-cover object-center" />
</div> </div>
<div className="relative max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8 min-h-[850px] flex flex-col lg:flex-row rounded-xl lg:rounded-3xl overflow-hidden mb-12"> <div className="relative max-w-[1400px] mx-auto px-4 sm:px-6 lg:px-8 min-h-[850px] flex flex-col lg:flex-row rounded-xl lg:rounded-3xl overflow-hidden mb-12">
@ -709,7 +709,7 @@ export default function AboutPage() {
{/* Background Image: cta-bg.webp */} {/* Background Image: cta-bg.webp */}
<div className="absolute inset-0 pointer-events-none z-0"> <div className="absolute inset-0 pointer-events-none z-0">
<Image src="/images/about/bg/cta-bg.webp" alt="" fill priority className="object-cover object-center" /> <Image src="/images/about/bg/cta-bg.webp" alt="Call to Action Background" fill priority className="object-cover object-center" />
</div> </div>
{/* Center Content */} {/* Center Content */}

View File

@ -1,3 +1,4 @@
import type { Metadata } from "next";
import Image from "next/image"; import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { notFound } from "next/navigation"; import { notFound } from "next/navigation";
@ -6,6 +7,27 @@ import { BLOG_POSTS } from "@/utils/blogData";
import InnerBanner from "@/components/InnerBanner"; import InnerBanner from "@/components/InnerBanner";
import AnimatedSection from "@/components/AnimatedSection"; import AnimatedSection from "@/components/AnimatedSection";
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
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() { export function generateStaticParams() {
return BLOG_POSTS.map((post) => ({ return BLOG_POSTS.map((post) => ({
slug: post.slug, slug: post.slug,

17
src/app/blog/layout.tsx Normal file
View File

@ -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}</>;
}

View File

@ -32,7 +32,7 @@ export default function BlogPage() {
<div className="w-full bg-[#f5efe0] relative z-10"> <div className="w-full bg-[#f5efe0] relative z-10">
{/* Subtle background texture */} {/* Subtle background texture */}
<div className="absolute inset-0 pointer-events-none select-none"> <div className="absolute inset-0 pointer-events-none select-none">
<Image src="/images/story_bg.png" alt="" fill className="object-cover" /> <Image src="/images/story_bg.png" alt="Blog Story Background" fill className="object-cover" />
</div> </div>
<div className="max-w-6xl mx-auto px-6 py-12 lg:py-20 relative z-10"> <div className="max-w-6xl mx-auto px-6 py-12 lg:py-20 relative z-10">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 w-full max-w-7xl mx-auto"> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 w-full max-w-7xl mx-auto">

View File

@ -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}</>;
}

View File

@ -51,7 +51,7 @@ export default function ContactPage() {
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image <Image
src="/images/about/bg/contact-bg.webp" src="/images/about/bg/contact-bg.webp"
alt="Background" alt="Contact Hero Background"
fill fill
priority priority
className="object-cover object-center mix-blend-multiply" className="object-cover object-center mix-blend-multiply"
@ -140,7 +140,7 @@ export default function ContactPage() {
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image <Image
src="/images/contact/contact.webp" src="/images/contact/contact.webp"
alt="" alt="Contact Info Box Background"
fill fill
className="object-cover object-center" className="object-cover object-center"
/> />
@ -306,7 +306,7 @@ export default function ContactPage() {
<div className="absolute inset-0 pointer-events-none select-none"> <div className="absolute inset-0 pointer-events-none select-none">
<Image <Image
src="/images/about/bg/map-bg.webp" src="/images/about/bg/map-bg.webp"
alt="Background" alt="Contact Details Section Background"
fill fill
className="object-cover object-center" className="object-cover object-center"
/> />
@ -377,7 +377,7 @@ export default function ContactPage() {
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image <Image
src="/images/story_bg.png" src="/images/story_bg.png"
alt="Background" alt="Map Section Background"
fill fill
className="object-cover object-center" className="object-cover object-center"
/> />
@ -426,7 +426,7 @@ export default function ContactPage() {
<div className="absolute inset-0 pointer-events-none select-none opacity-60"> <div className="absolute inset-0 pointer-events-none select-none opacity-60">
<Image <Image
src="/images/footer_bg.png" src="/images/footer_bg.png"
alt="" alt="Footer Pattern Background"
fill fill
className="object-cover object-center" className="object-cover object-center"
/> />

View File

@ -16,13 +16,21 @@ const montserrat = Montserrat({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "My Dosa Place - Pure Vegetarian South Indian Heritage Dining", metadataBase: new URL("https://mydosaplace.ca"),
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.", 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"], keywords: ["My Dosa Place", "Waterloo South Indian Restaurant", "Pure Vegetarian Indian", "Waterloo Dosa", "Traditional Indian Feast", "Waterloo Dining"],
authors: [{ name: "My Dosa Place" }], authors: [{ name: "My Dosa Place" }],
alternates: {
canonical: "/",
},
robots: {
index: true,
follow: true,
},
openGraph: { openGraph: {
title: "My Dosa Place - Pure Vegetarian South Indian Heritage Dining", title: "Best Dosa & South Indian Restaurant in Waterloo | My Dosa Place",
description: "Experience the authentic culinary legacy of South India at My Dosa Place in Waterloo, ON.", 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"], images: ["/images/chettinad_exterior.png"],
} }
}; };
@ -39,7 +47,7 @@ export default function RootLayout({
lang="en" lang="en"
className={`${cinzel.variable} ${montserrat.variable} h-full antialiased`} className={`${cinzel.variable} ${montserrat.variable} h-full antialiased`}
> >
<body className="min-h-full bg-darkbg text-foreground"> <head>
<Script <Script
src="https://www.googletagmanager.com/gtag/js?id=G-MZHFDXF8QP" src="https://www.googletagmanager.com/gtag/js?id=G-MZHFDXF8QP"
strategy="afterInteractive" strategy="afterInteractive"
@ -53,6 +61,8 @@ export default function RootLayout({
gtag('config', 'G-MZHFDXF8QP'); gtag('config', 'G-MZHFDXF8QP');
`} `}
</Script> </Script>
</head>
<body className="min-h-full bg-darkbg text-foreground">
{children} {children}
<ScrollToTop /> <ScrollToTop />
</body> </body>

17
src/app/menu/layout.tsx Normal file
View File

@ -0,0 +1,17 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Dosa, Thali & South Indian Menu Waterloo | My Dosa Place",
description: "Explore our full South Indian menu in Waterloo — masala dosa, uthappam, vada, thali, tiffin & filter coffee. 100% pure vegetarian, made fresh daily.",
alternates: {
canonical: "/menu/",
},
};
export default function MenuLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
}

View File

@ -0,0 +1,18 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Book a Table | South Indian Restaurant in Waterloo | My Dosa Place",
description:
"Reserve your table at My Dosa Place, Waterloo's 100% pure vegetarian South Indian restaurant. Book online for dosa, thali, uthappam & filter coffee.",
alternates: {
canonical: "/reservation/",
},
};
export default function ReservationLayout({
children,
}: {
children: React.ReactNode;
}) {
return <>{children}</>;
}

View File

@ -82,7 +82,7 @@ export default function ReservationPage() {
<div className="absolute inset-0 z-0 pointer-events-none select-none"> <div className="absolute inset-0 z-0 pointer-events-none select-none">
<Image <Image
src="/images/contact/contact.webp" src="/images/contact/contact.webp"
alt="" alt="Contact Form Background"
fill fill
className="object-cover object-center" className="object-cover object-center"
/> />

View File

@ -240,7 +240,7 @@ export default function ChefShowcase() {
{/* Feature 1: Authentic Recipes */} {/* Feature 1: Authentic Recipes */}
<div className="flex flex-col items-center text-center px-4 py-3 md:py-0 select-text"> <div className="flex flex-col items-center text-center px-4 py-3 md:py-0 select-text">
<Image src="/images/home/chef/icons/chef-authentic.webp" alt="Authentic Recipes" width={45} height={45} className="object-contain mb-2.5" /> <Image src="/images/home/chef/icons/chef-authentic.webp" alt="Authentic Recipes Chef Icon" width={45} height={45} className="object-contain mb-2.5" />
<h5 className="font-serif text-base sm:text-sm font-bold text-[#bc9e65] tracking-wider uppercase mb-1">Authentic Recipes</h5> <h5 className="font-serif text-base sm:text-sm font-bold text-[#bc9e65] tracking-wider uppercase mb-1">Authentic Recipes</h5>
<p className="font-sans text-base sm:text-base text-stone-300 leading-relaxed max-w-[180px] mx-auto">Traditional recipes passed down through generations.</p> <p className="font-sans text-base sm:text-base text-stone-300 leading-relaxed max-w-[180px] mx-auto">Traditional recipes passed down through generations.</p>
</div> </div>

View File

@ -17,7 +17,7 @@ const PrivateGatheringsIcon = () => (
); );
const KudumbaDiningIcon = () => ( const KudumbaDiningIcon = () => (
<Image src="/images/home/royal/icons/kudumba-dining.webp" alt="Kudumba Dining" width={28} height={28} className="object-contain" /> <Image src="/images/home/royal/icons/kudumba-dining.webp" alt="Kudumba Dining Experience" width={28} height={28} className="object-contain" />
); );
export default function EventsAccordion() { export default function EventsAccordion() {

View File

@ -41,7 +41,7 @@ export default function Footer() {
{/* Brand Logo */} {/* Brand Logo */}
<Image <Image
src="/images/logo.png" src="/images/logo.png"
alt="My Dosa Place" alt="My Dosa Place Footer Logo"
width={200} width={200}
height={80} height={80}
className="w-40 sm:w-48 h-auto mb-2 pointer-events-none select-none" className="w-40 sm:w-48 h-auto mb-2 pointer-events-none select-none"

View File

@ -52,7 +52,7 @@ export default function Header({ visible }: { visible: boolean }) {
<Link href="/" className="mb-10 select-none" onClick={() => setIsMobileMenuOpen(false)}> <Link href="/" className="mb-10 select-none" onClick={() => setIsMobileMenuOpen(false)}>
<Image <Image
src="/images/logo.png" src="/images/logo.png"
alt="My Dosa Place" alt="My Dosa Place Mobile Logo"
width={160} width={160}
height={60} height={60}
className="h-14 w-auto pointer-events-none" className="h-14 w-auto pointer-events-none"
@ -114,7 +114,7 @@ export default function Header({ visible }: { visible: boolean }) {
<Link href="/" className="clickable flex items-center gap-2.5 select-none"> <Link href="/" className="clickable flex items-center gap-2.5 select-none">
<Image <Image
src="/images/logo.png" src="/images/logo.png"
alt="My Dosa Place" alt="My Dosa Place Desktop Logo"
width={160} width={160}
height={60} height={60}
className="h-10 sm:h-12 w-auto pointer-events-none" className="h-10 sm:h-12 w-auto pointer-events-none"

View File

@ -234,7 +234,7 @@ export default function MenuPageContent() {
}`} }`}
> >
<div className={`w-8 h-8 flex items-center justify-center rounded-full ${isActive ? 'opacity-100' : 'opacity-90'}`}> <div className={`w-8 h-8 flex items-center justify-center rounded-full ${isActive ? 'opacity-100' : 'opacity-90'}`}>
<Image src={`/images/menu/tab-icons/${tab.icon || 'tab-icon.png'}`} alt="" width={38} height={38} className="object-contain drop-shadow-sm" /> <Image src={`/images/menu/tab-icons/${tab.icon || 'tab-icon.png'}`} alt={`${tab.label} Tab Icon`} width={38} height={38} className="object-contain drop-shadow-sm" />
</div> </div>
{tab.label} {tab.label}
@ -263,7 +263,7 @@ export default function MenuPageContent() {
</div> </div>
) : ( ) : (
<div className="w-6 h-6 flex items-center justify-center"> <div className="w-6 h-6 flex items-center justify-center">
<Image src={`/images/menu/tab-icons/${TABS.find((t) => t.id === activeTab)?.icon || 'tab-icon.png'}`} alt="" width={22} height={22} className="object-contain" /> <Image src={`/images/menu/tab-icons/${TABS.find((t) => t.id === activeTab)?.icon || 'tab-icon.png'}`} alt="Active Category Icon" width={22} height={22} className="object-contain" />
</div> </div>
)} )}
<span className="font-serif font-bold text-xs tracking-wider text-[#0f3f26] uppercase"> <span className="font-serif font-bold text-xs tracking-wider text-[#0f3f26] uppercase">
@ -314,7 +314,7 @@ export default function MenuPageContent() {
{/* Floating Icon */} {/* Floating Icon */}
<div className="absolute -bottom-6 left-6 w-14 h-14 rounded-full flex items-center justify-center shadow-lg border-3 z-10 bg-[#fdfcf9] border-[#eaddc4] group-hover:border-[#bc9e65] transition-colors duration-500"> <div className="absolute -bottom-6 left-6 w-14 h-14 rounded-full flex items-center justify-center shadow-lg border-3 z-10 bg-[#fdfcf9] border-[#eaddc4] group-hover:border-[#bc9e65] transition-colors duration-500">
<Image src={(item as any).icon ? `/images/menu/${(item as any).icon}` : "/images/menu/card-icon.png"} alt="icon" width={38} height={38} className="object-contain" /> <Image src={(item as any).icon ? `/images/menu/${(item as any).icon}` : "/images/menu/card-icon.png"} alt={`${(item as any).name || 'Menu Item'} Icon`} width={38} height={38} className="object-contain" />
</div> </div>
</div> </div>
@ -435,7 +435,7 @@ export default function MenuPageContent() {
</div> </div>
) : ( ) : (
<div className="w-7 h-7 flex items-center justify-center rounded-full bg-transparent"> <div className="w-7 h-7 flex items-center justify-center rounded-full bg-transparent">
<Image src={`/images/menu/tab-icons/${tab.icon || 'tab-icon.png'}`} alt="" width={24} height={24} className="object-contain" /> <Image src={`/images/menu/tab-icons/${tab.icon || 'tab-icon.png'}`} alt={`${tab.label} Mobile Tab Icon`} width={24} height={24} className="object-contain" />
</div> </div>
)} )}
</div> </div>

View File

@ -362,7 +362,7 @@ const BottomFeatures = () => (
{/* Feature 2 */} {/* Feature 2 */}
<div className="flex items-center gap-3.5 justify-start sm:justify-center px-2 sm:px-4 py-2 lg:py-0"> <div className="flex items-center gap-3.5 justify-start sm:justify-center px-2 sm:px-4 py-2 lg:py-0">
<div className="w-10 h-10 rounded-full bg-[#188754]/8 flex items-center justify-center text-[#bc9e65] flex-shrink-0"> <div className="w-10 h-10 rounded-full bg-[#188754]/8 flex items-center justify-center text-[#bc9e65] flex-shrink-0">
<Image src="/images/home/menu/icons/menu-authentic.webp" alt="Authentic Recipes" width={24} height={24} className="object-contain" /> <Image src="/images/home/menu/icons/menu-authentic.webp" alt="Authentic Recipes Menu Icon" width={24} height={24} className="object-contain" />
</div> </div>
<div className="text-left"> <div className="text-left">
<h5 className="font-sans text-base sm:text-base font-bold text-stone-850 tracking-wider uppercase">Authentic Recipes</h5> <h5 className="font-sans text-base sm:text-base font-bold text-stone-850 tracking-wider uppercase">Authentic Recipes</h5>
@ -384,7 +384,7 @@ const BottomFeatures = () => (
{/* Feature 4 */} {/* Feature 4 */}
<div className="flex items-center gap-3.5 justify-start sm:justify-center px-2 sm:px-4 py-2 lg:py-0"> <div className="flex items-center gap-3.5 justify-start sm:justify-center px-2 sm:px-4 py-2 lg:py-0">
<div className="w-10 h-10 rounded-full bg-[#188754]/8 flex items-center justify-center text-[#bc9e65] flex-shrink-0"> <div className="w-10 h-10 rounded-full bg-[#188754]/8 flex items-center justify-center text-[#bc9e65] flex-shrink-0">
<Image src="/images/home/menu/icons/menu-love.webp" alt="Made With Love" width={24} height={24} className="object-contain" /> <Image src="/images/home/menu/icons/menu-love.webp" alt="Made With Love Menu Icon" width={24} height={24} className="object-contain" />
</div> </div>
<div className="text-left"> <div className="text-left">
<h5 className="font-sans text-base sm:text-base font-bold text-stone-850 tracking-wider uppercase">Made With Love</h5> <h5 className="font-sans text-base sm:text-base font-bold text-stone-850 tracking-wider uppercase">Made With Love</h5>

View File

@ -56,10 +56,10 @@ export default function RestaurantStory() {
</div> </div>
{/* Main Headline */} {/* Main Headline */}
<h2 className="font-serif text-3xl sm:text-4xl lg:text-5xl font-bold tracking-wider text-[#0a4a2b] leading-tight uppercase"> <h1 className="font-serif text-3xl sm:text-4xl lg:text-5xl font-bold tracking-wider text-[#0a4a2b] leading-tight uppercase">
Traditional South<br /> Traditional South<br />
<span className="text-[#0a4a2b] font-extrabold block mt-1">Indian Recipes</span> <span className="text-[#0a4a2b] font-extrabold block mt-1">Indian Recipes</span>
</h2> </h1>
{/* Traditional Gold Divider */} {/* Traditional Gold Divider */}
<StoryLotusDivider /> <StoryLotusDivider />
@ -105,7 +105,7 @@ export default function RestaurantStory() {
className="flex flex-col items-center text-center group select-text" className="flex flex-col items-center text-center group select-text"
> >
<div className="w-14 h-14 rounded-full border border-[#d8c396] flex items-center justify-center mb-3 bg-[#fcfaf2]/70 shadow-sm transition-transform duration-300 group-hover:scale-110 pointer-events-none select-none overflow-hidden"> <div className="w-14 h-14 rounded-full border border-[#d8c396] flex items-center justify-center mb-3 bg-[#fcfaf2]/70 shadow-sm transition-transform duration-300 group-hover:scale-110 pointer-events-none select-none overflow-hidden">
<Image src="/images/home/our-story/icons/authentic.webp" alt="Authentic Recipes" width={32} height={32} className="object-contain" /> <Image src="/images/home/our-story/icons/authentic.webp" alt="Authentic Recipes Story Icon" width={32} height={32} className="object-contain" />
</div> </div>
<h4 className="font-serif text-[11px] sm:text-xs font-bold text-[#0a4a2b] tracking-wider uppercase mb-1.5"> <h4 className="font-serif text-[11px] sm:text-xs font-bold text-[#0a4a2b] tracking-wider uppercase mb-1.5">
@ -147,7 +147,7 @@ export default function RestaurantStory() {
className="flex flex-col items-center text-center group select-text" className="flex flex-col items-center text-center group select-text"
> >
<div className="w-14 h-14 rounded-full border border-[#d8c396] flex items-center justify-center mb-3 bg-[#fcfaf2]/70 shadow-sm transition-transform duration-300 group-hover:scale-110 pointer-events-none select-none overflow-hidden"> <div className="w-14 h-14 rounded-full border border-[#d8c396] flex items-center justify-center mb-3 bg-[#fcfaf2]/70 shadow-sm transition-transform duration-300 group-hover:scale-110 pointer-events-none select-none overflow-hidden">
<Image src="/images/home/our-story/icons/love.webp" alt="Made With Love" width={32} height={32} className="object-contain" /> <Image src="/images/home/our-story/icons/love.webp" alt="Made With Love Story Icon" width={32} height={32} className="object-contain" />
</div> </div>
<h4 className="font-serif text-[11px] sm:text-xs font-bold text-[#0a4a2b] tracking-wider uppercase mb-1.5"> <h4 className="font-serif text-[11px] sm:text-xs font-bold text-[#0a4a2b] tracking-wider uppercase mb-1.5">

View File

@ -97,7 +97,7 @@ export default function SpatialSanctuary() {
{/* Parchment background image for Slide 1 */} {/* Parchment background image for Slide 1 */}
<Image <Image
src="/images/home/culinary-heritage/our-culinary-heritage-bg-1.webp" src="/images/home/culinary-heritage/our-culinary-heritage-bg-1.webp"
alt="Traditional South Indian Sanctuary Background" alt="Traditional South Indian Sanctuary Desktop Background"
fill fill
priority priority
className="object-fill pointer-events-none select-none z-0" className="object-fill pointer-events-none select-none z-0"
@ -226,7 +226,7 @@ export default function SpatialSanctuary() {
{/* Parchment background image for Bento Grid */} {/* Parchment background image for Bento Grid */}
<Image <Image
src="/images/home/culinary-heritage/our-culinary-heritage-bg-2.webp" src="/images/home/culinary-heritage/our-culinary-heritage-bg-2.webp"
alt="Traditional South Indian Sanctuary Background" alt="Traditional South Indian Sanctuary Mobile Background"
fill fill
className="object-fill pointer-events-none select-none z-0" className="object-fill pointer-events-none select-none z-0"
/> />

View File

@ -4,6 +4,8 @@ export const BLOG_POSTS = [
"slug": "dosa-in-waterloo-guide", "slug": "dosa-in-waterloo-guide",
"title": "Dosa in Waterloo: A Complete Guide to Our Full Menu of South Indian Crepes", "title": "Dosa in Waterloo: A Complete Guide to Our Full Menu of South Indian Crepes",
"desc": "Craving dosa in Waterloo? Explore My Dosa Place's full menu of South Indian crepes, from classic masala dosa to specialty varieties, all 100% vegetarian.", "desc": "Craving dosa in Waterloo? Explore My Dosa Place's full menu of South Indian crepes, from classic masala dosa to specialty varieties, all 100% vegetarian.",
"metaTitle": "Dosa in Waterloo: Full Menu Guide | My Dosa Place",
"metaDesc": "Explore every dosa variety in Waterloo — masala, plain, rava, onion & paper dosa — plus idli, vada & uttapam. 100% pure vegetarian, made fresh daily.",
"date": "JULY 15, 2026", "date": "JULY 15, 2026",
"image": "/images/masala_dosa_luxury.png", "image": "/images/masala_dosa_luxury.png",
"cardImage": "/images/blog/blog-card-1.webp", "cardImage": "/images/blog/blog-card-1.webp",
@ -17,6 +19,8 @@ export const BLOG_POSTS = [
"slug": "south-indian-restaurant-waterloo", "slug": "south-indian-restaurant-waterloo",
"title": "South Indian Restaurant in Waterloo: What to Expect at My Dosa Place", "title": "South Indian Restaurant in Waterloo: What to Expect at My Dosa Place",
"desc": "Search for a South Indian restaurant in Waterloo and you'll notice most listings point one direction... My Dosa Place exists to serve that other half of Indian food properly.", "desc": "Search for a South Indian restaurant in Waterloo and you'll notice most listings point one direction... My Dosa Place exists to serve that other half of Indian food properly.",
"metaTitle": "South Indian Restaurant in Waterloo: What to Expect | My Dosa Place",
"metaDesc": "Discover what to expect at My Dosa Place, Waterloo's dedicated South Indian restaurant — masala dosa, thali, idli & vada, 100% pure vegetarian dining.",
"date": "JUNE 28, 2026", "date": "JUNE 28, 2026",
"image": "/images/category_curries.jpg", "image": "/images/category_curries.jpg",
"cardImage": "/images/blog/blog-card-2.webp", "cardImage": "/images/blog/blog-card-2.webp",
@ -30,6 +34,8 @@ export const BLOG_POSTS = [
"slug": "vegan-friendly-south-indian-food", "slug": "vegan-friendly-south-indian-food",
"title": "Vegan-Friendly South Indian Food in Waterloo: What to Order at My Dosa Place", "title": "Vegan-Friendly South Indian Food in Waterloo: What to Order at My Dosa Place",
"desc": "If you're searching for vegan food in Waterloo, South Indian cuisine deserves a spot on your shortlist — and My Dosa Place is one of the easiest places in the city to eat plant-based.", "desc": "If you're searching for vegan food in Waterloo, South Indian cuisine deserves a spot on your shortlist — and My Dosa Place is one of the easiest places in the city to eat plant-based.",
"metaTitle": "Vegan-Friendly South Indian Food in Waterloo | My Dosa Place",
"metaDesc": "Discover our best vegan options in Waterloo — idli, vada, uttapam & more. My Dosa Place is a 100% pure vegetarian South Indian restaurant, vegan-friendly by design.",
"date": "MAY 12, 2026", "date": "MAY 12, 2026",
"image": "/images/south_indian_thali_luxury.png", "image": "/images/south_indian_thali_luxury.png",
"cardImage": "/images/blog/blog-card-3.webp", "cardImage": "/images/blog/blog-card-3.webp",