meta tags updated
382
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
10
package.json
@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB |
|
Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 47 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 16 KiB |
1
public/sitemap.xml
Normal 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>
|
||||||
91
scripts/generate-sitemap.cjs
Normal 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();
|
||||||
1
scripts/image_alt_issues.csv
Normal file
@ -0,0 +1 @@
|
|||||||
|
Page URL,Image Src,Alt Text,Issue Type
|
||||||
|
266
scripts/seo-test-selenium.cjs
Normal 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
@ -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}</>;
|
||||||
|
}
|
||||||
@ -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 */}
|
||||||
|
|||||||
@ -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
@ -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}</>;
|
||||||
|
}
|
||||||
@ -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">
|
||||||
|
|||||||
17
src/app/contact/layout.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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
@ -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}</>;
|
||||||
|
}
|
||||||
18
src/app/reservation/layout.tsx
Normal 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}</>;
|
||||||
|
}
|
||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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() {
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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">
|
||||||
|
|
||||||
|
|||||||
@ -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"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||