All inner pages, seo selenium, sitemap, dynamic blog , contatc api integration are updated

This commit is contained in:
akash 2025-11-25 23:17:48 +07:00
parent 7eee6d62d0
commit 345cef2e37
49 changed files with 6084 additions and 238 deletions

455
package-lock.json generated
View File

@ -8,14 +8,21 @@
"name": "antalya-web",
"version": "0.1.0",
"dependencies": {
"axios": "^1.13.2",
"framer-motion": "^12.23.24",
"next": "16.0.3",
"react": "19.2.0",
"react-dom": "19.2.0"
"react-dom": "19.2.0",
"react-google-recaptcha": "^3.1.0",
"selenium-webdriver": "^4.38.0",
"sitemap": "^9.0.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-google-recaptcha": "^2.1.9",
"eslint": "^9",
"eslint-config-next": "16.0.3",
"typescript": "^5"
@ -261,6 +268,12 @@
"node": ">=6.9.0"
}
},
"node_modules/@bazel/runfiles": {
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/@bazel/runfiles/-/runfiles-6.5.0.tgz",
"integrity": "sha512-RzahvqTkfpY2jsDxo8YItPX+/iZ6hbiikw1YhE0bA9EKBR5Og8Pa6FHn9PO9M0zaXRVsr0GFQLKbB/0rzy9SzA==",
"license": "Apache-2.0"
},
"node_modules/@emnapi/core": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
@ -1263,7 +1276,6 @@
"version": "20.19.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.25.tgz",
"integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"undici-types": "~6.21.0"
@ -1289,6 +1301,25 @@
"@types/react": "^19.2.0"
}
},
"node_modules/@types/react-google-recaptcha": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/@types/react-google-recaptcha/-/react-google-recaptcha-2.1.9.tgz",
"integrity": "sha512-nT31LrBDuoSZJN4QuwtQSF3O89FVHC4jLhM+NtKEmVF5R1e8OY0Jo4//x2Yapn2aNHguwgX5doAq8Zo+Ehd0ug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*"
}
},
"node_modules/@types/sax": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.47.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.47.0.tgz",
@ -1915,6 +1946,12 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
@ -2109,6 +2146,12 @@
"node": ">= 0.4"
}
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@ -2135,6 +2178,17 @@
"node": ">=4"
}
},
"node_modules/axios": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
"integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.4",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@ -2243,7 +2297,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -2343,6 +2396,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -2357,6 +2422,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@ -2501,6 +2572,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@ -2528,7 +2608,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@ -2626,7 +2705,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -2636,7 +2714,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -2674,7 +2751,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@ -2687,7 +2763,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
"dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@ -3302,6 +3377,26 @@
"dev": true,
"license": "ISC"
},
"node_modules/follow-redirects": {
"version": "1.15.11",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
"integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@ -3318,11 +3413,53 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/form-data": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
"integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"es-set-tostringtag": "^2.1.0",
"hasown": "^2.0.2",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/framer-motion": {
"version": "12.23.24",
"resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz",
"integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.23.23",
"motion-utils": "^12.23.6",
"tslib": "^2.4.0"
},
"peerDependencies": {
"@emotion/is-prop-valid": "*",
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"peerDependenciesMeta": {
"@emotion/is-prop-valid": {
"optional": true
},
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -3383,7 +3520,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@ -3408,7 +3544,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@ -3496,7 +3631,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3568,7 +3702,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -3581,7 +3714,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@ -3597,7 +3729,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@ -3623,6 +3754,15 @@
"hermes-estree": "0.25.1"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@ -3633,6 +3773,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -3660,6 +3806,12 @@
"node": ">=0.8.19"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/internal-slot": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
@ -4126,7 +4278,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@ -4205,6 +4356,18 @@
"node": ">=4.0"
}
},
"node_modules/jszip": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
"integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
"license": "(MIT OR GPL-3.0-or-later)",
"dependencies": {
"lie": "~3.3.0",
"pako": "~1.0.2",
"readable-stream": "~2.3.6",
"setimmediate": "^1.0.5"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -4249,6 +4412,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
"integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -4276,7 +4448,6 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@ -4299,7 +4470,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -4329,6 +4499,27 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -4352,6 +4543,21 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/motion-dom": {
"version": "12.23.23",
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz",
"integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.23.6"
}
},
"node_modules/motion-utils": {
"version": "12.23.6",
"resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz",
"integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==",
"license": "MIT"
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -4463,7 +4669,6 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -4650,6 +4855,12 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
"license": "(MIT AND Zlib)"
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@ -4757,11 +4968,16 @@
"node": ">= 0.8.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
"license": "MIT"
},
"node_modules/prop-types": {
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
"dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@ -4769,6 +4985,12 @@
"react-is": "^16.13.1"
}
},
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@ -4809,6 +5031,19 @@
"node": ">=0.10.0"
}
},
"node_modules/react-async-script": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/react-async-script/-/react-async-script-1.2.0.tgz",
"integrity": "sha512-bCpkbm9JiAuMGhkqoAiC0lLkb40DJ0HOEJIku+9JDjxX3Rcs+ztEOG13wbrOskt3n2DTrjshhaQ/iay+SnGg5Q==",
"license": "MIT",
"dependencies": {
"hoist-non-react-statics": "^3.3.0",
"prop-types": "^15.5.0"
},
"peerDependencies": {
"react": ">=16.4.1"
}
},
"node_modules/react-dom": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
@ -4821,11 +5056,44 @@
"react": "^19.2.0"
}
},
"node_modules/react-google-recaptcha": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/react-google-recaptcha/-/react-google-recaptcha-3.1.0.tgz",
"integrity": "sha512-cYW2/DWas8nEKZGD7SCu9BSuVz8iOcOLHChHyi7upUuVhkpkhYG/6N3KDiTQ3XAiZ2UAZkfvYKMfAHOzBOcGEg==",
"license": "MIT",
"dependencies": {
"prop-types": "^15.5.0",
"react-async-script": "^1.2.0"
},
"peerDependencies": {
"react": ">=16.4.1"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true,
"license": "MIT"
},
"node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"node_modules/readable-stream/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"license": "MIT"
},
"node_modules/reflect.getprototypeof": {
@ -4968,6 +5236,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
"license": "MIT"
},
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@ -5003,12 +5277,43 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sax": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.3.tgz",
"integrity": "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==",
"license": "BlueOak-1.0.0"
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/selenium-webdriver": {
"version": "4.38.0",
"resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.38.0.tgz",
"integrity": "sha512-5/UXXFSQmn7FGQkbcpAqvfhzflUdMWtT7QqpEgkFD6Q6rDucxB5EUfzgjmr6JbUj30QodcW3mDXehzoeS/Vy5w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/SeleniumHQ"
},
{
"type": "opencollective",
"url": "https://opencollective.com/selenium"
}
],
"license": "Apache-2.0",
"dependencies": {
"@bazel/runfiles": "^6.3.1",
"jszip": "^3.10.1",
"tmp": "^0.2.5",
"ws": "^8.18.3"
},
"engines": {
"node": ">= 20.0.0"
}
},
"node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
@ -5068,6 +5373,12 @@
"node": ">= 0.4"
}
},
"node_modules/setimmediate": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
"integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
"license": "MIT"
},
"node_modules/sharp": {
"version": "0.34.5",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
@ -5225,6 +5536,40 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sitemap": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/sitemap/-/sitemap-9.0.0.tgz",
"integrity": "sha512-J/SU27FJ+I52TcDLKZzPRRVQUMj0Pp1i/HLb2lrkU+hrMLM+qdeRjdacrNxnSW48Waa3UcEOGOdX1+0Lob7TgA==",
"license": "MIT",
"dependencies": {
"@types/node": "^24.9.2",
"@types/sax": "^1.2.1",
"arg": "^5.0.0",
"sax": "^1.4.1"
},
"bin": {
"sitemap": "dist/esm/cli.js"
},
"engines": {
"node": ">=20.19.5",
"npm": ">=10.8.2"
}
},
"node_modules/sitemap/node_modules/@types/node": {
"version": "24.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz",
"integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.16.0"
}
},
"node_modules/sitemap/node_modules/undici-types": {
"version": "7.16.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -5255,6 +5600,15 @@
"node": ">= 0.4"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
},
"node_modules/string.prototype.includes": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@ -5488,6 +5842,15 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/tmp": {
"version": "0.2.5",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
"license": "MIT",
"engines": {
"node": ">=14.14"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@ -5698,7 +6061,6 @@
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
"node_modules/unrs-resolver": {
@ -5777,6 +6139,12 @@
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -5892,6 +6260,49 @@
"node": ">=0.10.0"
}
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xml2js": {
"version": "0.6.2",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.2.tgz",
"integrity": "sha512-T4rieHaC1EXcES0Kxxj4JWgaUQHDk+qwHcYOCFHfiwKz7tOVPLq7Hjq9dM1WCMhylqMEfP7hMcOIChvotiZegA==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@ -6,17 +6,25 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
"lint": "eslint",
"sitemap": "node scripts/generate-sitemap.cjs"
},
"dependencies": {
"axios": "^1.13.2",
"framer-motion": "^12.23.24",
"next": "16.0.3",
"react": "19.2.0",
"react-dom": "19.2.0"
"react-dom": "19.2.0",
"react-google-recaptcha": "^3.1.0",
"selenium-webdriver": "^4.38.0",
"sitemap": "^9.0.0",
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/react-google-recaptcha": "^2.1.9",
"eslint": "^9",
"eslint-config-next": "16.0.3",
"typescript": "^5"

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

BIN
public/images/footer-bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 776 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 356 KiB

6
public/robots.txt Normal file
View File

@ -0,0 +1,6 @@
# Allow all search engines to crawl all pages
User-agent: *
Disallow:
# Sitemap
Sitemap: http://localhost:3000/sitemap.xml

1
public/sitemap.xml Normal file
View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:news="http://www.google.com/schemas/sitemap-news/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:image="http://www.google.com/schemas/sitemap-image/1.1" xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"><url><loc>http://localhost:3000/</loc><changefreq>daily</changefreq><priority>1.0</priority></url><url><loc>http://localhost:3000/about/</loc><changefreq>monthly</changefreq><priority>0.5</priority></url><url><loc>http://localhost:3000/menu/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/gallery/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/contact/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/blog/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/blog/the-art-of-turkish-tea/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/blog/secrets-of-charcoal-grilling/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url><url><loc>http://localhost:3000/blog/a-taste-of-sweet-legacy/</loc><changefreq>weekly</changefreq><priority>0.6</priority></url></urlset>

View File

@ -0,0 +1,66 @@
const fs = require("fs");
const path = require("path");
const { SitemapStream, streamToPromise } = require("sitemap");
const { pathToFileURL } = require("url");
const hostname = "http://localhost:3000"; // localhost for development
const addTrailingSlash = true; // ✅ Set this true if your Next.js uses trailingSlash: true
// Utility to format URLs based on config
const shouldAddSlash = (url) => {
if (url === "/") return false;
if (/\.[a-z0-9]{2,6}(\?.*)?$/i.test(url)) return false;
return true;
};
const formatUrl = (url) => {
if (!url.startsWith("/")) url = "/" + url;
if (addTrailingSlash && shouldAddSlash(url) && !url.endsWith("/")) {
return url + "/";
}
if (!addTrailingSlash && url.endsWith("/") && url !== "/") {
return url.slice(0, -1);
}
return url;
};
// ✅ Static pages
const staticLinks = [
{ url: '/', changefreq: 'daily', priority: 1.0 },
{ url: '/about/', changefreq: 'monthly', priority: 0.5 },
{ url: '/menu/', changefreq: 'weekly', priority: 0.6 },
{ url: '/gallery/', changefreq: 'weekly', priority: 0.6 },
{ url: '/contact/', changefreq: 'weekly', priority: 0.6 },
{ url: '/blog/', changefreq: 'weekly', priority: 0.6 },
];
// ✅ Dynamic blog posts (only URLs, no extra metadata needed beyond static format)
const blogPosts = [
{ slug: '/blog/the-art-of-turkish-tea' },
{ slug: '/blog/secrets-of-charcoal-grilling/' },
{ slug: '/blog/a-taste-of-sweet-legacy/' },
];
const blogLinks = blogPosts.map(p => ({ url: p.slug, changefreq: 'weekly', priority: 0.6 }));
const allLinks = [...staticLinks, ...blogLinks].map(l => ({ ...l, url: formatUrl(l.url) }));
async function generateSitemap() {
try {
const sitemap = new SitemapStream({ hostname });
const writeStream = fs.createWriteStream(path.resolve(__dirname, "../public/sitemap.xml"));
sitemap.pipe(writeStream);
console.log('📦 Writing URLs to sitemap:');
allLinks.forEach(link => {
console.log(' -', hostname + link.url);
sitemap.write(link);
});
sitemap.end();
await streamToPromise(sitemap);
console.log('✅ sitemap.xml created successfully!');
} catch (error) {
console.error('❌ Error creating sitemap.xml:', error);
}
}
generateSitemap();

View File

@ -0,0 +1,10 @@
Page URL,Image Src,Alt Text,Issue Type
"http://localhost:3000/","","Antalya Restaurant","Duplicate Alt (2 times)"
"http://localhost:3000/about/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/menu/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/gallery/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/contact/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/blog/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/blog/the-art-of-turkish-tea/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/blog/secrets-of-charcoal-grilling/","","Antalya Restaurant","Duplicate Alt (3 times)"
"http://localhost:3000/blog/a-taste-of-sweet-legacy/","","Antalya Restaurant","Duplicate Alt (3 times)"
1 Page URL Image Src Alt Text Issue Type
2 http://localhost:3000/ Antalya Restaurant Duplicate Alt (2 times)
3 http://localhost:3000/about/ Antalya Restaurant Duplicate Alt (3 times)
4 http://localhost:3000/menu/ Antalya Restaurant Duplicate Alt (3 times)
5 http://localhost:3000/gallery/ Antalya Restaurant Duplicate Alt (3 times)
6 http://localhost:3000/contact/ Antalya Restaurant Duplicate Alt (3 times)
7 http://localhost:3000/blog/ Antalya Restaurant Duplicate Alt (3 times)
8 http://localhost:3000/blog/the-art-of-turkish-tea/ Antalya Restaurant Duplicate Alt (3 times)
9 http://localhost:3000/blog/secrets-of-charcoal-grilling/ Antalya Restaurant Duplicate Alt (3 times)
10 http://localhost:3000/blog/a-taste-of-sweet-legacy/ Antalya Restaurant Duplicate Alt (3 times)

View File

@ -0,0 +1,244 @@
const { Builder, By } = require("selenium-webdriver");
const chrome = require("selenium-webdriver/chrome");
const axios = require("axios");
const xml2js = require("xml2js");
const fs = require("fs");
const path = require("path");
// CSV file for Image Alt issues
const csvPath = path.join(__dirname, "image_alt_issues.csv");
fs.writeFileSync(csvPath, "Page URL,Image Src,Alt Text,Issue Type\n", "utf8");
// ==========================
// 1⃣ Fetch URLs from sitemap.xml
// ==========================
async function getUrlsFromSitemap(sitemapUrl) {
try {
const res = await axios.get(sitemapUrl);
const parsed = await xml2js.parseStringPromise(res.data);
return parsed.urlset.url.map((u) => u.loc[0]);
} catch (err) {
console.error("❌ Failed to load sitemap:", err.message);
return [];
}
}
// ==========================
// 2⃣ Check HTTP Status
// ==========================
async function checkLinkStatus(url) {
try {
const res = await axios.get(url, {
timeout: 10000,
validateStatus: () => true,
});
if (res.status === 200 && res.data.toLowerCase().includes("page not found")) {
return "Soft 404";
}
return res.status;
} catch (err) {
return err.response ? err.response.status : "❌ No Response";
}
}
// ==========================
// 3⃣ Main SEO + Accessibility + Image Alt Audit
// ==========================
async function checkSEO(url, siteDomain) {
const options = new chrome.Options();
options.addArguments("--headless", "--no-sandbox", "--disable-gpu");
const driver = await new Builder()
.forBrowser("chrome")
.setChromeOptions(options)
.build();
try {
const pageStatus = await checkLinkStatus(url);
if (pageStatus === 404 || pageStatus === "Soft 404") {
console.log(`\n🚫 ${url} → ❌ Page not found (${pageStatus})`);
return;
}
await driver.get(url);
const pageSource = await driver.getPageSource();
// Basic SEO Elements
const title = await driver.getTitle();
const descElem = await driver.findElements(By.css('meta[name="description"]'));
const canonicalElem = await driver.findElements(By.css('link[rel="canonical"]'));
const robotsElem = await driver.findElements(By.css('meta[name="robots"]'));
const viewportElem = await driver.findElements(By.css('meta[name="viewport"]'));
const charset = await driver.findElements(By.css('meta[charset]'));
const htmlTag = await driver.findElement(By.css("html"));
const langAttr = await htmlTag.getAttribute("lang").catch(() => "");
const h1Tags = await driver.findElements(By.css("h1"));
const h2Tags = await driver.findElements(By.css("h2"));
// Meta Description
let descContent = descElem.length > 0 ? await descElem[0].getAttribute("content") : "";
const descLength = descContent.length;
const descStatus =
descLength === 0
? "❌ Missing"
: descLength < 50
? `⚠️ Too short (${descLength})`
: descLength > 160
? `⚠️ Too long (${descLength})`
: "✅ Perfect";
// Title length check
const titleLength = title.length;
const titleStatus =
titleLength === 0
? "❌ Missing"
: titleLength < 30
? `⚠️ Too short (${titleLength})`
: titleLength > 65
? `⚠️ Too long (${titleLength})`
: "✅ Perfect";
// Canonical
const canonicalURL = canonicalElem.length > 0 ? await canonicalElem[0].getAttribute("href") : "❌ Missing";
// 🖼️ Image Accessibility Audit
const imgs = await driver.findElements(By.css("img"));
let missingAlt = 0;
let emptyAlt = 0;
let duplicateAlt = [];
const altTextMap = new Map();
for (const img of imgs) {
const src = await img.getAttribute("src");
const alt = (await img.getAttribute("alt"))?.trim() ?? null;
if (alt === null) {
missingAlt++;
fs.appendFileSync(csvPath, `"${url}","${src}","","Missing Alt"\n`, "utf8");
continue;
}
if (alt === "") {
emptyAlt++;
fs.appendFileSync(csvPath, `"${url}","${src}","(empty)","Empty Alt"\n`, "utf8");
}
if (altTextMap.has(alt)) {
altTextMap.set(alt, altTextMap.get(alt) + 1);
} else {
altTextMap.set(alt, 1);
}
}
for (const [altText, count] of altTextMap.entries()) {
if (altText && count > 1) {
duplicateAlt.push({ altText, count });
fs.appendFileSync(
csvPath,
`"${url}","","${altText}","Duplicate Alt (${count} times)"\n`,
"utf8"
);
}
}
// Detect tracking & schema tags
const hasGTM = pageSource.includes("googletagmanager.com/gtm.js");
const hasClarity = pageSource.includes("clarity.ms/tag");
const hasFBPixel = pageSource.includes("fbevents.js") || pageSource.includes("fbq(");
const hasAnalytics = pageSource.includes("www.googletagmanager.com/gtag/js");
const ogTags = await driver.findElements(By.css("meta[property^='og:']"));
const twitterTags = await driver.findElements(By.css("meta[name^='twitter:']"));
const schemaScripts = await driver.findElements(By.css('script[type="application/ld+json"]'));
// Links check
const anchorTags = await driver.findElements(By.css("a[href]"));
const brokenLinks = [];
for (const a of anchorTags) {
const href = await a.getAttribute("href");
if (!href || href.startsWith("#") || href.startsWith("mailto:")) continue;
const fullUrl = href.startsWith("http")
? href
: `${siteDomain}${href.startsWith("/") ? href : `/${href}`}`;
if (fullUrl.includes(siteDomain)) {
const status = await checkLinkStatus(fullUrl);
if (status === 404 || status === "Soft 404" || status === "❌ No Response") {
brokenLinks.push({ link: fullUrl, status });
}
}
}
// Lazy loading check
const mediaElems = await driver.findElements(By.css("img, video, iframe"));
const lazyLoadCount = await Promise.all(
mediaElems.map(async (el) => {
const loading = await el.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}`);
})();

View File

@ -0,0 +1,575 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/images/hero-1.png');
background-size: cover;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.heroContent {
max-width: 800px;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: var(--color-gold);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.section {
padding: 80px 20px;
background-color: #f5e6d3;
}
.featuresSection {
background-color: #3a0c08;
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
}
.testimonialsSection {
background-color: #3a0c08;
background-image: url('/images/about-testimonial-bg.jpg');
background-size: cover;
background-position: center;
background-repeat: repeat;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 4rem;
flex-wrap: wrap;
}
.textBlock {
flex: 1;
min-width: 300px;
}
.imageBlock {
flex: 1;
min-width: 300px;
display: flex;
justify-content: center;
}
.sectionTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #5d4037;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.sectionTitleCenter {
font-family: var(--font-playfair);
font-size: 3.5rem;
color: #C5A059;
margin-bottom: 3rem;
text-align: center;
text-transform: uppercase;
letter-spacing: 2px;
}
.text {
font-size: 1.1rem;
line-height: 1.8;
color: #5d4037;
margin-bottom: 1.5rem;
opacity: 0.9;
font-family: var(--font-lato);
}
.image {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
max-width: 100%;
height: auto;
border: 4px solid #C5A059;
}
.menuButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
margin-top: 1rem;
}
.menuButton:hover {
background-color: #C5A059;
color: #000;
}
/* Features Section */
.featuresGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3rem;
max-width: 1200px;
margin: 0 auto;
}
.featureCard {
background-color: #F5E6D3;
padding: 2.5rem 2rem;
text-align: center;
border: 4px solid #C5A059;
transition: transform 0.3s ease;
}
.featureCard:hover {
transform: translateY(-10px);
}
.featureImageWrapper {
position: relative;
width: 100%;
height: 200px;
margin-bottom: 1.5rem;
border-radius: 8px;
overflow: hidden;
border: 2px solid #C5A059;
}
.featureImage {
object-fit: cover;
}
.featureTitle {
font-family: var(--font-playfair);
font-size: 1.8rem;
color: #3e2723;
margin-bottom: 1rem;
font-weight: 600;
}
.featureDesc {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
}
/* Testimonials Slider */
.testimonialSlider {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
max-width: 900px;
margin: 0 auto;
position: relative;
}
.sliderBtn {
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
font-size: 2.5rem;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
flex-shrink: 0;
}
.sliderBtn:hover {
background-color: #C5A059;
color: #000;
}
.testimonialCard {
background-color: #F5E6D3;
padding: 3rem 2.5rem;
border: 4px solid #C5A059;
display: flex;
flex-direction: column;
gap: 2rem;
flex: 1;
min-height: 300px;
}
.testimonialText {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #5d4037;
line-height: 1.8;
font-style: italic;
text-align: center;
}
.testimonialAuthor {
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
}
.authorImageWrapper {
position: relative;
width: 70px;
height: 70px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #C5A059;
flex-shrink: 0;
}
.authorImage {
object-fit: cover;
}
.authorInfo {
display: flex;
flex-direction: column;
text-align: left;
}
.authorName {
font-family: var(--font-playfair);
font-size: 1.2rem;
color: #3e2723;
font-weight: 600;
}
.authorRole {
font-family: var(--font-lato);
font-size: 0.95rem;
color: #836839;
}
.sliderDots {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 2rem;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: transparent;
border: 2px solid #C5A059;
cursor: pointer;
transition: all 0.3s;
padding: 0;
}
.dot:hover,
.activeDot {
background-color: #C5A059;
}
/* Call to Action Section */
.ctaSection {
padding: 5rem 2rem;
background-size: cover;
background-position: center;
position: relative;
text-align: center;
}
.ctaOverlay {
position: relative;
z-index: 2;
background: rgba(58, 12, 8, 0.9);
padding: 4rem 2rem;
max-width: 800px;
margin: 0 auto;
border: 4px solid #C5A059;
}
.ctaTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.ctaSubtitle {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #F5E6D3;
margin-bottom: 2rem;
}
.ctaButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
}
.ctaButton:hover {
background-color: #C5A059;
color: #000;
}
@media (max-width: 1024px) {
.featuresGrid {
grid-template-columns: 1fr;
}
.testimonialSlider {
flex-direction: column;
}
.sliderBtn {
order: 2;
}
.testimonialCard {
order: 1;
}
.faqContainer {
grid-template-columns: 1fr;
gap: 3rem;
}
.faqImageBlock {
min-height: 400px;
}
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.container {
flex-direction: column;
gap: 2rem;
}
.section {
padding: 60px 20px;
}
.sectionTitleCenter {
font-size: 2rem;
}
.ctaTitle {
font-size: 2rem;
}
.sliderBtn {
width: 40px;
height: 40px;
font-size: 2rem;
}
.faqSection {
padding: 60px 20px;
}
.faqTitle {
font-size: 2rem;
}
.faqSubtitle {
font-size: 1rem;
}
.faqQuestion {
padding: 1.2rem 1.5rem;
font-size: 1rem;
}
.faqAnswer {
padding: 1.2rem 1.5rem;
}
.faqImageBlock {
min-height: 300px;
}
}
/* FAQ Section Styles */
.faqSection {
padding: 80px 20px;
background-color: #F5E6D3;
}
.faqContainer {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: start;
}
.faqImageBlock {
position: relative;
width: 100%;
height: 100%;
min-height: 600px;
}
.faqImage {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.faqContentBlock {
display: flex;
flex-direction: column;
gap: 2rem;
}
.faqTitle {
font-family: var(--font-playfair);
font-size: 2.5rem;
color: #5d4037;
margin-bottom: 0.5rem;
}
.faqSubtitle {
font-family: var(--font-lato);
font-size: 1.1rem;
color: #836839;
line-height: 1.6;
margin-bottom: 1rem;
}
.faqAccordion {
display: flex;
flex-direction: column;
gap: 1rem;
}
.faqItem {
background: #fff;
border: 2px solid #C5A059;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.faqItem:hover {
box-shadow: 0 4px 12px rgba(197, 160, 89, 0.2);
}
.faqQuestion {
width: 100%;
padding: 1.5rem 2rem;
background: transparent;
border: none;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-family: var(--font-lato);
font-size: 1.1rem;
font-weight: 600;
color: #5d4037;
text-align: left;
transition: all 0.3s ease;
}
.faqQuestion:hover {
background-color: #faf6f0;
}
.faqQuestionActive {
background-color: #C5A059;
color: #fff;
}
.faqIcon {
font-size: 1.5rem;
font-weight: bold;
color: #C5A059;
transition: transform 0.3s ease;
flex-shrink: 0;
margin-left: 1rem;
}
.faqQuestionActive .faqIcon {
color: #fff;
transform: rotate(180deg);
}
.faqAnswer {
padding: 1.5rem 2rem;
border-top: 1px solid #e0d5c7;
animation: slideDown 0.3s ease;
}
.faqAnswer p {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.8;
margin: 0;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

View File

@ -0,0 +1,770 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/images/hero-1.png');
background-size: cover;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.heroContent {
max-width: 800px;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: var(--color-gold);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.section {
padding: 80px 20px;
background-color: #0a0a0a;
}
.featuresSection {
background-color: #3a0c08;
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
}
.testimonialsSection {
background-color: #0a0a0a;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 4rem;
flex-wrap: wrap;
}
.textBlock {
flex: 1;
min-width: 300px;
}
.imageBlock {
flex: 1;
min-width: 300px;
display: flex;
justify-content: center;
}
.sectionTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.sectionTitleCenter {
font-family: var(--font-playfair);
font-size: 3.5rem;
color: #C5A059;
margin-bottom: 3rem;
text-align: center;
text-transform: uppercase;
letter-spacing: 2px;
}
.text {
font-size: 1.1rem;
line-height: 1.8;
color: #C5A059;
margin-bottom: 1.5rem;
opacity: 0.9;
font-family: var(--font-lato);
}
.image {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
max-width: 100%;
height: auto;
border: 4px solid #C5A059;
}
.menuButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
margin-top: 1rem;
}
.menuButton:hover {
background-color: #C5A059;
color: #000;
}
/* Features Section */
.featuresGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3rem;
max-width: 1200px;
margin: 0 auto;
}
.featureCard {
background-color: #F5E6D3;
padding: 2.5rem 2rem;
text-align: center;
border: 4px solid #C5A059;
transition: transform 0.3s ease;
}
.featureCard:hover {
transform: translateY(-10px);
}
.featureImageWrapper {
position: relative;
width: 100%;
height: 200px;
margin-bottom: 1.5rem;
border-radius: 8px;
overflow: hidden;
border: 2px solid #C5A059;
}
.featureImage {
object-fit: cover;
}
.featureTitle {
font-family: var(--font-playfair);
font-size: 1.8rem;
color: #3e2723;
margin-bottom: 1rem;
font-weight: 600;
}
.featureDesc {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
}
/* Testimonials Slider */
.testimonialSlider {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
max-width: 900px;
margin: 0 auto;
position: relative;
}
.sliderBtn {
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
font-size: 2.5rem;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
flex-shrink: 0;
}
.sliderBtn:hover {
background-color: #C5A059;
color: #000;
}
.testimonialCard {
background-color: #F5E6D3;
padding: 3rem 2.5rem;
border: 4px solid #C5A059;
display: flex;
flex-direction: column;
gap: 2rem;
flex: 1;
min-height: 300px;
}
.testimonialText {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #5d4037;
line-height: 1.8;
font-style: italic;
text-align: center;
}
.testimonialAuthor {
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
}
.authorImageWrapper {
position: relative;
width: 70px;
height: 70px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #C5A059;
flex-shrink: 0;
}
.authorImage {
object-fit: cover;
}
.authorInfo {
display: flex;
flex-direction: column;
text-align: left;
}
.authorName {
font-family: var(--font-playfair);
font-size: 1.2rem;
color: #3e2723;
font-weight: 600;
}
.authorRole {
font-family: var(--font-lato);
font-size: 0.95rem;
color: #836839;
}
.sliderDots {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 2rem;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: transparent;
border: 2px solid #C5A059;
cursor: pointer;
transition: all 0.3s;
padding: 0;
}
.dot:hover,
.activeDot {
background-color: #C5A059;
}
/* Call to Action Section */
.ctaSection {
padding: 5rem 2rem;
background-size: cover;
background-position: center;
position: relative;
text-align: center;
}
.ctaOverlay {
position: relative;
z-index: 2;
background: rgba(58, 12, 8, 0.9);
padding: 4rem 2rem;
max-width: 800px;
margin: 0 auto;
border: 4px solid #C5A059;
}
.ctaTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.ctaSubtitle {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #F5E6D3;
margin-bottom: 2rem;
}
.ctaButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
}
.ctaButton:hover {
background-color: #C5A059;
color: #000;
}
@media (max-width: 1024px) {
.featuresGrid {
grid-template-columns: 1fr;
}
.testimonialSlider {
flex-direction: column;
}
.sliderBtn {
order: 2;
}
font-family: var(--font-playfair);
font-size: 1.8rem;
color: #3e2723;
margin-bottom: 1rem;
font-weight: 600;
}
.featureDesc {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
}
/* Testimonials Slider */
.testimonialSlider {
display: flex;
align-items: center;
justify-content: center;
gap: 2rem;
max-width: 900px;
margin: 0 auto;
position: relative;
}
.sliderBtn {
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
font-size: 2.5rem;
width: 50px;
height: 50px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
flex-shrink: 0;
}
.sliderBtn:hover {
background-color: #C5A059;
color: #000;
}
.testimonialCard {
background-color: #F5E6D3;
padding: 3rem 2.5rem;
border: 4px solid #C5A059;
display: flex;
flex-direction: column;
gap: 2rem;
flex: 1;
min-height: 300px;
}
.testimonialText {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #5d4037;
line-height: 1.8;
font-style: italic;
text-align: center;
}
.testimonialAuthor {
display: flex;
align-items: center;
justify-content: center;
gap: 1.5rem;
}
.authorImageWrapper {
position: relative;
width: 70px;
height: 70px;
border-radius: 50%;
overflow: hidden;
border: 3px solid #C5A059;
flex-shrink: 0;
}
.authorImage {
object-fit: cover;
}
.authorInfo {
display: flex;
flex-direction: column;
text-align: left;
}
.authorName {
font-family: var(--font-playfair);
font-size: 1.2rem;
color: #3e2723;
font-weight: 600;
}
.authorRole {
font-family: var(--font-lato);
font-size: 0.95rem;
color: #836839;
}
.sliderDots {
display: flex;
justify-content: center;
gap: 1rem;
margin-top: 2rem;
}
.dot {
width: 12px;
height: 12px;
border-radius: 50%;
background: transparent;
border: 2px solid #C5A059;
cursor: pointer;
transition: all 0.3s;
padding: 0;
}
.dot:hover,
.activeDot {
background-color: #C5A059;
}
/* Call to Action Section */
.ctaSection {
padding: 5rem 2rem;
background-size: cover;
background-position: center;
position: relative;
text-align: center;
}
.ctaOverlay {
position: relative;
z-index: 2;
background: rgba(58, 12, 8, 0.9);
padding: 4rem 2rem;
max-width: 800px;
margin: 0 auto;
border: 4px solid #C5A059;
}
.ctaTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.ctaSubtitle {
font-family: var(--font-lato);
font-size: 1.3rem;
color: #F5E6D3;
margin-bottom: 2rem;
}
.ctaButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
}
.ctaButton:hover {
background-color: #C5A059;
color: #000;
}
@media (max-width: 1024px) {
.featuresGrid {
grid-template-columns: 1fr;
}
.testimonialSlider {
flex-direction: column;
}
.sliderBtn {
order: 2;
}
.testimonialCard {
order: 1;
}
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.container {
flex-direction: column;
gap: 2rem;
}
.section {
padding: 60px 20px;
}
.sectionTitleCenter {
font-size: 2rem;
}
.ctaTitle {
font-size: 2rem;
}
.sliderBtn {
width: 40px;
height: 40px;
font-size: 2rem;
}
}
/* FAQ Section Styles */
.faqSection {
padding: 80px 20px;
background-color: #F5E6D3;
}
.faqContainer {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
align-items: start;
}
.faqImageBlock {
position: relative;
width: 100%;
height: 100%;
min-height: 600px;
}
.faqImage {
width: 100%;
height: 100%;
object-fit: cover;
border-radius: 12px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}
.faqContentBlock {
display: flex;
flex-direction: column;
gap: 2rem;
}
.faqTitle {
font-family: var(--font-playfair);
font-size: 2.5rem;
color: #5d4037;
margin-bottom: 0.5rem;
}
.faqSubtitle {
font-family: var(--font-lato);
font-size: 1.1rem;
color: #836839;
line-height: 1.6;
margin-bottom: 1rem;
}
.faqAccordion {
display: flex;
flex-direction: column;
gap: 1rem;
}
.faqItem {
background: #fff;
border: 2px solid #C5A059;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.faqItem:hover {
box-shadow: 0 4px 12px rgba(197, 160, 89, 0.2);
}
.faqQuestion {
width: 100%;
padding: 1.5rem 2rem;
background: transparent;
border: none;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-family: var(--font-lato);
font-size: 1.1rem;
font-weight: 600;
color: #5d4037;
text-align: left;
transition: all 0.3s ease;
}
.faqQuestion:hover {
background-color: #faf6f0;
}
.faqQuestionActive {
background-color: #C5A059;
color: #fff;
}
.faqIcon {
font-size: 1.5rem;
font-weight: bold;
color: #C5A059;
transition: transform 0.3s ease;
flex-shrink: 0;
margin-left: 1rem;
}
.faqQuestionActive .faqIcon {
color: #fff;
transform: rotate(180deg);
}
.faqAnswer {
padding: 1.5rem 2rem;
border-top: 1px solid #e0d5c7;
animation: slideDown 0.3s ease;
}
.faqAnswer p {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.8;
margin: 0;
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@media (max-width: 1024px) {
.faqContainer {
grid-template-columns: 1fr;
gap: 3rem;
}
.faqImageBlock {
min-height: 400px;
}
}
@media (max-width: 768px) {
.faqSection {
padding: 60px 20px;
}
.faqTitle {
font-size: 2rem;
}
.faqSubtitle {
font-size: 1rem;
}
.faqQuestion {
padding: 1.2rem 1.5rem;
font-size: 1rem;
}
.faqAnswer {
padding: 1.2rem 1.5rem;
}
.faqImageBlock {
min-height: 300px;
}
}

View File

@ -0,0 +1,168 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/images/hero-1.png');
background-size: cover;
background-position: center;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
.heroContent {
max-width: 800px;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: var(--color-gold);
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.section {
padding: 80px 20px;
background-color: #f5e6d3;
}
.featuresSection {
background-color: #3a0c08;
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
}
.testimonialsSection {
background-color: #0a0a0a;
}
.container {
max-width: 1200px;
margin: 0 auto;
display: flex;
align-items: center;
gap: 4rem;
flex-wrap: wrap;
}
.textBlock {
flex: 1;
min-width: 300px;
}
.imageBlock {
flex: 1;
min-width: 300px;
display: flex;
justify-content: center;
}
.sectionTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 1.5rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.sectionTitleCenter {
font-family: var(--font-playfair);
font-size: 3.5rem;
color: #C5A059;
margin-bottom: 3rem;
text-align: center;
text-transform: uppercase;
letter-spacing: 2px;
}
.text {
font-size: 1.1rem;
line-height: 1.8;
color: #C5A059;
margin-bottom: 1.5rem;
opacity: 0.9;
font-family: var(--font-lato);
}
.image {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
max-width: 100%;
height: auto;
border: 4px solid #C5A059;
}
.menuButton {
display: inline-block;
padding: 15px 40px;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1.1rem;
text-transform: uppercase;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
letter-spacing: 1px;
margin-top: 1rem;
}
.menuButton:hover {
background-color: #C5A059;
color: #000;
}
/* Features Section */
.featuresGrid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 3rem;
max-width: 1200px;
margin: 0 auto;
}
.featureCard {
background-color: #F5E6D3;
padding: 2.5rem 2rem;
text-align: center;
border: 4px solid #C5A059;
transition: transform 0.3s ease;
}
.featureCard:hover {
transform: translateY(-10px);
}
.featureImageWrapper {
position: relative;
width: 100%;
height: 200px;
margin-bottom: 1.5rem;
border-radius: 8px;
overflow: hidden;
border: 2px solid #C5A059;
}

352
src/app/about/page.tsx Normal file
View File

@ -0,0 +1,352 @@
'use client'
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import FAQ from "@/components/FAQ/FAQ";
import Image from "next/image";
import Link from "next/link";
import styles from "./about.module.css";
import { featuresData, testimonialData, ctaData, aboutFaqData } from "@/utils/constant";
import { useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
export default function AboutPage() {
const [currentTestimonial, setCurrentTestimonial] = useState(0);
// Animation variants
const fadeInUp = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6
}
}
};
const fadeIn = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: 0.8 }
}
};
const slideInLeft = {
hidden: { opacity: 0, x: -50 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.7
}
}
};
const slideInRight = {
hidden: { opacity: 0, x: 50 },
visible: {
opacity: 1,
x: 0,
transition: {
duration: 0.7
}
}
};
const staggerContainer = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.1
}
}
};
// Auto-slide testimonials
useEffect(() => {
const interval = setInterval(() => {
setCurrentTestimonial((prev) => (prev + 1) % testimonialData.length);
}, 5000); // Change every 5 seconds
return () => clearInterval(interval);
}, []);
const nextTestimonial = () => {
setCurrentTestimonial((prev) => (prev + 1) % testimonialData.length);
};
const prevTestimonial = () => {
setCurrentTestimonial((prev) => (prev - 1 + testimonialData.length) % testimonialData.length);
};
return (
<main className={styles.main}>
<Navbar />
{/* Hero Banner */}
<motion.section
className={styles.hero}
initial="hidden"
animate="visible"
variants={fadeIn}
>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>About Us</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / About
</p>
</div>
</motion.section>
{/* About Section - No boxed structure */}
<section className={styles.section}>
<motion.div
className={styles.container}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
>
<motion.div className={styles.textBlock} variants={slideInLeft}>
<h2 className={styles.sectionTitle}>Our Story</h2>
<p className={styles.text}>
Founded in 2010, Antalya Restaurant began with a simple mission: to bring the authentic flavors of Turkey to our community.
What started as a small family-run kitchen has grown into a beloved dining destination, known for its warm hospitality and traditional recipes passed down through generations.
</p>
<p className={styles.text}>
Every dish we serve tells a story of tradition, passion, and dedication to the culinary arts. Our chefs use time-honored techniques combined with the freshest ingredients to create an unforgettable dining experience.
</p>
<Link href="/menu" className={styles.menuButton}>
View Our Menu
</Link>
</motion.div>
<motion.div className={styles.imageBlock} variants={slideInRight}>
<Image src="/images/restaurant-interior.png" alt="Our Story" width={500} height={350} className={styles.image} />
</motion.div>
</motion.div>
</section>
{/* Features Section - With real images */}
<section className={`${styles.section} ${styles.featuresSection}`}>
<motion.h2
className={styles.sectionTitleCenter}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
>
What Makes Us Special
</motion.h2>
<motion.div
className={styles.featuresGrid}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
>
{featuresData.map((feature) => (
<motion.div
key={feature.id}
className={styles.featureCard}
variants={fadeInUp}
whileHover={{ y: -5, transition: { duration: 0.3 } }}
>
<div className={styles.featureImageWrapper}>
<Image src={feature.image} alt={feature.title} fill className={styles.featureImage} />
</div>
<h3 className={styles.featureTitle}>{feature.title}</h3>
<p className={styles.featureDesc}>{feature.description}</p>
</motion.div>
))}
</motion.div>
</section>
{/* Why Choose Us Section */}
<section className={styles.section}>
<motion.div
className={styles.container}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
>
<motion.div className={styles.imageBlock} variants={slideInLeft}>
<Image src="/images/hero-2.png" alt="Why Choose Us" width={500} height={350} className={styles.image} />
</motion.div>
<motion.div className={styles.textBlock} variants={slideInRight}>
<h2 className={styles.sectionTitle}>Why Choose Us</h2>
<p className={styles.text}>
At Antalya Restaurant, we don't just serve food we create experiences. Our commitment to authenticity means every spice, every ingredient, and every cooking method stays true to Turkish culinary traditions.
</p>
<p className={styles.text}>
From our charcoal-grilled kebabs to our handmade baklava, we take pride in delivering dishes that transport you straight to the streets of Istanbul and the coasts of Antalya.
</p>
</motion.div>
</motion.div>
</section>
{/* Testimonials Section - Auto Slider */}
<section className={`${styles.section} ${styles.testimonialsSection}`}>
<motion.h2
className={styles.sectionTitleCenter}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeInUp}
>
What Our Guests Say
</motion.h2>
<motion.div
className={styles.testimonialSlider}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeIn}
>
<button className={styles.sliderBtn} onClick={prevTestimonial}></button>
<AnimatePresence mode="wait">
<motion.div
key={currentTestimonial}
className={styles.testimonialCard}
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.5 }}
>
<p className={styles.testimonialText}>"{testimonialData[currentTestimonial].text}"</p>
<div className={styles.testimonialAuthor}>
<div className={styles.authorImageWrapper}>
<Image
src={testimonialData[currentTestimonial].image}
alt={testimonialData[currentTestimonial].name}
fill
className={styles.authorImage}
/>
</div>
<div className={styles.authorInfo}>
<p className={styles.authorName}>{testimonialData[currentTestimonial].name}</p>
<p className={styles.authorRole}>{testimonialData[currentTestimonial].role}</p>
</div>
</div>
</motion.div>
</AnimatePresence>
<button className={styles.sliderBtn} onClick={nextTestimonial}></button>
</motion.div>
{/* Slider dots */}
<div className={styles.sliderDots}>
{testimonialData.map((_, index) => (
<button
key={index}
className={`${styles.dot} ${index === currentTestimonial ? styles.activeDot : ''}`}
onClick={() => setCurrentTestimonial(index)}
/>
))}
</div>
</section>
{/* FAQ Section - Image Left, FAQ Right */}
<section className={styles.faqSection}>
<motion.div
className={styles.faqContainer}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={staggerContainer}
>
<motion.div className={styles.faqImageBlock} variants={slideInLeft}>
<Image
src="/images/restaurant-interior.png"
alt="Frequently Asked Questions"
width={600}
height={700}
className={styles.faqImage}
/>
</motion.div>
<motion.div className={styles.faqContentBlock} variants={slideInRight}>
<h2 className={styles.faqTitle}>Frequently Asked Questions</h2>
<p className={styles.faqSubtitle}>
Have questions about Antalya Restaurant? Find answers to our most commonly asked questions below.
</p>
<div className={styles.faqAccordion}>
{aboutFaqData.map((faq, index) => (
<FaqItem key={index} question={faq.q} answer={faq.a} />
))}
</div>
</motion.div>
</motion.div>
</section>
{/* Call to Action */}
<motion.section
className={styles.ctaSection}
style={{ backgroundImage: `url(${ctaData.backgroundImage})` }}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={fadeIn}
>
<div className={styles.ctaOverlay}>
<motion.h2
className={styles.ctaTitle}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.2 }}
>
{ctaData.title}
</motion.h2>
<motion.p
className={styles.ctaSubtitle}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
>
{ctaData.subtitle}
</motion.p>
<motion.div
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.6 }}
>
<Link href={ctaData.buttonLink} className={styles.ctaButton}>
{ctaData.buttonText}
</Link>
</motion.div>
</div>
</motion.section>
<Footer />
</main>
);
}
// FAQ Item Component
function FaqItem({ question, answer }: { question: string; answer: string }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div className={styles.faqItem}>
<button
className={`${styles.faqQuestion} ${isOpen ? styles.faqQuestionActive : ''}`}
onClick={() => setIsOpen(!isOpen)}
>
<span>{question}</span>
<span className={styles.faqIcon}>{isOpen ? '' : '+'}</span>
</button>
{isOpen && (
<div className={styles.faqAnswer}>
<p>{answer}</p>
</div>
)}
</div>
);
}

View File

@ -0,0 +1,310 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
}
.hero {
padding: 10rem 2rem;
background-size: cover;
background-position: center;
text-align: center;
}
.heroContent {
max-width: 800px;
margin: 0 auto;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.article {
background-color: #F5E6D3;
padding: 60px 20px;
}
.contentContainer {
max-width: 1200px;
margin: 0 auto;
background: #fff;
padding: 3rem;
border: 4px solid #C5A059;
}
.meta {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 2rem;
flex-wrap: wrap;
font-family: var(--font-lato);
}
.category {
background: #C5A059;
color: #fff;
padding: 0.5rem 1rem;
border-radius: 4px;
font-size: 0.9rem;
font-weight: 600;
text-transform: uppercase;
}
.date,
.comments {
color: #836839;
font-size: 0.95rem;
}
.separator {
color: #d4c5b0;
}
.featuredImage {
width: 100%;
height: 500px;
position: relative;
margin-bottom: 2.5rem;
border-radius: 8px;
overflow: hidden;
border: 3px solid #C5A059;
}
.featuredImage img {
width: 100%;
height: 100%;
object-fit: cover;
}
.content {
font-family: var(--font-lato);
font-size: 1.1rem;
line-height: 1.8;
color: #5d4037;
}
.content h2 {
font-family: var(--font-playfair);
font-size: 2rem;
color: #5d4037;
margin: 2rem 0 1rem;
padding-bottom: 0.5rem;
border-bottom: 2px solid #C5A059;
}
.content h3 {
font-family: var(--font-playfair);
font-size: 1.6rem;
color: #5d4037;
margin: 1.5rem 0 1rem;
}
.content h4 {
font-family: var(--font-playfair);
font-size: 1.3rem;
color: #5d4037;
margin: 1.2rem 0 0.8rem;
}
.content p {
margin-bottom: 1.2rem;
}
.content ul {
margin: 1.5rem 0;
padding-left: 2rem;
}
.content li {
margin-bottom: 0.8rem;
color: #5d4037;
}
.content strong {
color: #3e2723;
font-weight: 600;
}
.content a {
color: #C5A059;
text-decoration: underline;
transition: color 0.3s;
}
.content a:hover {
color: #b08f4a;
}
.content .sec-title {
margin-top: 2rem;
}
.content .mt-4 {
margin-top: 1.5rem;
}
.content .list-style-two {
list-style: none;
padding-left: 0;
}
.content .list-style-two li {
position: relative;
padding-left: 2rem;
}
.content .list-style-two li:before {
content: "✓";
position: absolute;
left: 0;
color: #C5A059;
font-weight: bold;
font-size: 1.2rem;
}
/* Impressive Blockquote Styling */
.content blockquote {
text-align: center;
background: linear-gradient(135deg, #faf6f0 0%, #f5ede0 100%);
border-left: 6px solid #c5a059;
/* border-right: 6px solid #c5a059; */
/* max-width: 800px; */
margin: 3rem 0px;
padding: 10px 20px 10px 20px;
position: relative;
box-shadow: 0 8px 24px #c5a05926;
}
.content blockquote p {
font-family: var(--font-playfair);
font-size: 1.4rem;
font-style: italic;
color: #5d4037;
line-height: 1.8;
margin: 0;
position: relative;
z-index: 1;
}
.content blockquote cite {
display: block;
margin-top: 1.5rem;
font-family: var(--font-lato);
font-size: 1rem;
font-style: normal;
color: #C5A059;
font-weight: 600;
}
.content blockquote cite:before {
content: "— ";
}
.navigation {
margin-top: 3rem;
padding-top: 2rem;
border-top: 2px solid #e0d5c7;
}
.backButton {
display: inline-block;
padding: 12px 30px;
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
font-family: var(--font-lato);
font-size: 1rem;
font-weight: 600;
text-decoration: none;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 1px;
}
.backButton:hover {
background: #C5A059;
color: #fff;
}
.errorContainer {
min-height: 60vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 4rem 2rem;
}
.errorTitle {
font-family: var(--font-playfair);
font-size: 3rem;
color: #C5A059;
margin-bottom: 2rem;
}
.backLink {
color: #C5A059;
font-size: 1.2rem;
text-decoration: underline;
}
@media (max-width: 768px) {
.heroTitle {
font-size: 2.5rem;
}
.contentContainer {
padding: 2rem 1.5rem;
}
.featuredImage {
height: 300px;
}
.content {
font-size: 1rem;
}
.content h2 {
font-size: 1.6rem;
}
.content h3 {
font-size: 1.3rem;
}
.content blockquote {
padding: 2rem 2rem;
margin: 2rem auto;
}
.content blockquote p {
font-size: 1.2rem;
}
.meta {
font-size: 0.85rem;
}
}

View File

@ -0,0 +1,94 @@
'use client'
import { useParams } from 'next/navigation';
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import FAQ from "@/components/FAQ/FAQ";
import Image from "next/image";
import Link from "next/link";
import styles from "./blogDetail.module.css";
import { blogData } from "@/utils/constant";
export default function BlogDetailPage() {
const params = useParams();
const slug = params.id as string;
const blog = blogData.find((b) => b.slug === slug);
if (!blog) {
return (
<main className={styles.main}>
<Navbar />
<div className={styles.errorContainer}>
<h1 className={styles.errorTitle}>Blog Post Not Found</h1>
<Link href="/blog" className={styles.backLink}>Back to Blog</Link>
</div>
<Footer />
</main>
);
}
return (
<main className={styles.main}>
<Navbar />
{/* Banner Section */}
<section className={styles.hero} style={{ backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url(${blog.banner})` }}>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>{blog.title}</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / <Link href="/blog">Blog</Link> / {blog.title}
</p>
</div>
</section>
{/* Blog Content */}
<article className={styles.article}>
<div className={styles.contentContainer}>
{/* Meta Information */}
{/* Featured Image */}
{blog.detailImage && (
<div className={styles.featuredImage}>
<Image
src={blog.detailImage}
alt={blog.title}
width={1200}
height={600}
style={{ objectFit: 'cover' }}
/>
</div>
)}
<div className={styles.meta}>
<span className={styles.category}>{blog.category}</span>
<span className={styles.separator}>|</span>
<span className={styles.date}>{blog.date}</span>
<span className={styles.separator}>|</span>
<span className={styles.comments}>{blog.comments}</span>
</div>
{/* Blog Content */}
<div
className={styles.content}
dangerouslySetInnerHTML={{ __html: blog.content }}
/>
{/* FAQ Section */}
{blog.faqs && blog.faqs.length > 0 && (
<FAQ faqs={blog.faqs} />
)}
{/* Navigation */}
<div className={styles.navigation}>
<Link href="/blog" className={styles.backButton}> Back to All Blogs</Link>
</div>
</div>
</article>
<Footer />
</main>
);
}

View File

@ -0,0 +1,143 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
background-color: #f5e6d3;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/images/dish-2.png');
background-size: cover;
background-position: center;
text-align: center;
}
.heroContent {
max-width: 800px;
margin: 0 auto;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.blogSection {
padding: 80px 20px;
max-width: 1200px;
margin: 0 auto;
/* background-color: #0a0a0a; */
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
.card {
background-color: #F5E6D3;
border: 4px solid #C5A059;
overflow: hidden;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 10px 30px rgba(197, 160, 89, 0.5);
}
.imageContainer {
position: relative;
width: 100%;
height: 250px;
border-bottom: 1px solid #C5A059;
}
.cardContent {
padding: 1.5rem;
display: flex;
flex-direction: column;
height: calc(100% - 250px);
}
.blogTitle {
font-family: var(--font-playfair);
font-size: 1.6rem;
margin-bottom: 0.5rem;
}
.blogTitle a {
color: #3e2723;
transition: color 0.3s;
}
.blogTitle a:hover {
color: #C5A059;
}
.date {
font-size: 0.9rem;
color: #836839;
margin-bottom: 1rem;
font-family: var(--font-lato);
font-weight: 600;
}
.excerpt {
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
margin-bottom: 1.5rem;
flex-grow: 1;
font-family: var(--font-lato);
}
.button {
display: inline-block;
padding: 10px 25px;
border: 2px solid #C5A059;
color: #5d4037;
text-align: center;
transition: all 0.3s ease;
align-self: flex-start;
font-family: var(--font-lato);
text-transform: uppercase;
font-size: 0.85rem;
font-weight: 600;
}
.button:hover {
background-color: #C5A059;
color: #fff;
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.blogSection {
padding: 60px 20px;
}
}

110
src/app/blog/page.tsx Normal file
View File

@ -0,0 +1,110 @@
'use client'
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import Image from "next/image";
import Link from "next/link";
import styles from "./blog.module.css";
import { blogData } from "@/utils/constant";
import { motion } from "framer-motion";
export default function BlogPage() {
// Animation variants
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.15,
delayChildren: 0.2
}
}
};
const cardVariants = {
hidden: {
opacity: 0,
y: 30
},
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6
}
}
};
const heroVariants = {
hidden: { opacity: 0, y: -20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.8
}
}
};
return (
<main className={styles.main}>
<Navbar />
<motion.section
className={styles.hero}
initial="hidden"
animate="visible"
variants={heroVariants}
>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>Our Blog</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / Blog
</p>
</div>
</motion.section>
<section className={styles.blogSection}>
<motion.div
className={styles.grid}
initial="hidden"
animate="visible"
variants={containerVariants}
>
{blogData.map((blog) => (
<motion.div
key={blog.id}
className={styles.card}
variants={cardVariants}
whileHover={{
y: -8,
transition: { duration: 0.3 }
}}
>
<div className={styles.imageContainer}>
<Image
src={blog.image}
alt={blog.title}
fill
style={{ objectFit: 'cover' }}
/>
</div>
<div className={styles.cardContent}>
<h3 className={styles.blogTitle}>
<Link href={`/blog/${blog.slug}`}>{blog.title}</Link>
</h3>
<p className={styles.date}>{blog.date}</p>
<p className={styles.excerpt}>{blog.excerpt}</p>
<Link href={`/blog/${blog.slug}`} className={styles.button}>
Read More
</Link>
</div>
</motion.div>
))}
</motion.div>
</section>
<Footer />
</main>
);
}

View File

@ -0,0 +1,315 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('/images/hero-2.png');
background-size: cover;
background-position: center;
text-align: center;
}
.heroContent {
max-width: 800px;
margin: 0 auto;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.contactSection {
padding: 80px 20px;
background-color: #F5E6D3;
/* background-image: url('/images/section-bg.jpg'); */
background-size: cover;
background-position: center;
}
.container {
max-width: 1400px;
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4rem;
}
/* Left Side - Form Block */
.formBlock {
background-color: #F5E6D3;
padding: 2.5rem;
border: 4px solid #C5A059;
}
.title {
font-family: var(--font-cinzel), serif;
font-size: 2.5rem;
color: #5d4037;
margin-bottom: 1rem;
font-weight: 600;
border-bottom: 3px solid #C5A059;
padding-bottom: 0.5rem;
display: inline-block;
}
.subtitle {
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
margin-bottom: 2rem;
font-family: var(--font-lato);
}
.form {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.formRow {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
.formGroup {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.formLabel {
font-family: var(--font-lato);
color: #C5A059;
font-size: 0.95rem;
font-weight: 600;
}
.input,
.textarea {
background: #fff;
border: 1px solid #d4c5b0;
padding: 0.9rem;
color: #C5A059;
font-family: var(--font-lato);
font-size: 0.95rem;
transition: border-color 0.3s;
border-radius: 4px;
}
.input::placeholder,
.textarea::placeholder {
color: #a89b87;
}
.input:focus,
.textarea:focus {
outline: none;
border-color: #C5A059;
}
.textarea {
resize: vertical;
min-height: 100px;
}
.charCount {
font-size: 0.85rem;
color: #836839;
text-align: right;
font-family: var(--font-lato);
}
.submitButton {
background: #C5A059;
border: none;
color: #fff;
padding: 15px 40px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s;
text-transform: uppercase;
letter-spacing: 1px;
font-family: var(--font-lato);
border-radius: 50px;
margin-top: 1rem;
}
.submitButton:hover {
background-color: #b08f4a;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(197, 160, 89, 0.3);
}
/* Alert Messages */
.alert {
padding: 1rem 1.5rem;
border-radius: 8px;
margin-bottom: 1.5rem;
font-family: var(--font-lato);
font-size: 0.95rem;
}
.alertSuccess {
background-color: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alertDanger {
background-color: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.errorText {
color: #dc3545;
font-size: 0.85rem;
margin-top: 0.25rem;
display: block;
font-family: var(--font-lato);
}
.recaptchaWrapper {
margin: 1rem 0;
}
/* Right Side - Info Block */
.infoBlock {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.locationCard {
background: #F5E6D3;
padding: 1.5rem;
border: 4px solid #C5A059;
display: flex;
align-items: flex-start;
gap: 1.5rem;
transition: transform 0.3s;
}
.locationCard:hover {
transform: translateY(-5px);
}
.iconWrapper {
background: #C5A059;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.icon {
width: 24px;
height: 24px;
color: #fff;
}
.locationInfo {
flex: 1;
}
.locationTitle {
font-family: var(--font-cinzel), serif;
font-size: 1.3rem;
color: #5d4037;
margin-bottom: 0.3rem;
font-weight: 600;
}
.locationSubtitle {
font-size: 0.9rem;
color: #836839;
margin-bottom: 0.5rem;
font-family: var(--font-lato);
}
.locationAddress,
.locationPhone {
font-size: 1rem;
color: #5d4037;
line-height: 1.6;
font-family: var(--font-lato);
}
.locationPhone {
font-size: 1.2rem;
font-weight: 600;
color: #C5A059;
}
.mapContainer {
border: 4px solid #C5A059;
overflow: hidden;
margin-top: 0.5rem;
}
.mapContainer iframe {
display: block;
width: 100%;
height: 300px;
}
@media (max-width: 1024px) {
.container {
grid-template-columns: 1fr;
gap: 3rem;
}
.formRow {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.contactSection {
padding: 60px 20px;
}
.title {
font-size: 2rem;
}
.formRow {
grid-template-columns: 1fr;
}
.formBlock {
padding: 2rem 1.5rem;
}
}

384
src/app/contact/page.tsx Normal file
View File

@ -0,0 +1,384 @@
'use client'
import { useState, useEffect } from "react";
import ReCAPTCHA from "react-google-recaptcha";
import axios from "axios";
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import Link from "next/link";
import styles from "./contact.module.css";
import { motion } from "framer-motion";
interface FormErrors {
name?: string;
email?: string;
phone?: string;
date?: string;
time?: string;
captcha?: string;
}
export default function ContactPage() {
const [email, setEmail] = useState("");
useEffect(() => {
const user = "info";
const domain = "antalya-restaurant.com";
setEmail(`${user}@${domain}`);
}, []);
const [formData, setFormData] = useState({
name: "",
email: "",
phone: "",
guests: "2 Guests",
date: "",
time: "",
requests: "",
});
const [formErrors, setFormErrors] = useState<FormErrors>({});
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
const [charCount, setCharCount] = useState(0);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
if (name === "requests") {
setCharCount(value.length);
}
};
const handleCaptchaChange = (token: string | null) => {
setCaptchaToken(token);
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const errors: FormErrors = {};
if (!formData.name.trim()) errors.name = "Name is required.";
if (!formData.email.trim()) errors.email = "Email is required.";
if (!formData.phone.trim()) errors.phone = "Phone is required.";
if (!formData.date.trim()) errors.date = "Date is required.";
if (!formData.time.trim()) errors.time = "Time is required.";
if (!captchaToken) errors.captcha = "Please verify the CAPTCHA.";
setFormErrors(errors);
if (Object.keys(errors).length > 0) return;
const emailData = {
name: formData.name,
phone: formData.phone,
email: formData.email,
subject: `Table Reservation - ${formData.guests} on ${formData.date}`,
message: `
<strong>Reservation Details:</strong><br/>
Name: ${formData.name}<br/>
Email: ${formData.email}<br/>
Phone: ${formData.phone}<br/>
Guests: ${formData.guests}<br/>
Date: ${formData.date}<br/>
Time: ${formData.time}<br/><br/>
<strong>Special Requests:</strong><br/>
${formData.requests || "None"}
`,
to: email,
senderName: "Antalya Restaurant - Table Reservation",
recaptchaToken: captchaToken,
};
try {
const res = await axios.post(
"https://mailserver.metatronnest.com/send",
emailData,
{ headers: { "Content-Type": "application/json" } }
);
setAlert({
show: true,
type: "success",
message: res?.data?.message || "Reservation request sent successfully! We'll contact you soon.",
});
setFormData({
name: "",
email: "",
phone: "",
guests: "2 Guests",
date: "",
time: "",
requests: "",
});
setCaptchaToken(null);
setFormErrors({});
setCharCount(0);
} catch (error) {
setAlert({
show: true,
type: "danger",
message: "Failed to send reservation request. Please try again later.",
});
}
};
useEffect(() => {
if (alert.show) {
const timer = setTimeout(() => {
setAlert((prev) => ({ ...prev, show: false }));
}, 5000);
return () => clearTimeout(timer);
}
}, [alert.show]);
return (
<main className={styles.main}>
<Navbar />
<motion.section
className={styles.hero}
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8 }}
>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>Contact Us</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / Contact
</p>
</div>
</motion.section>
<section className={styles.contactSection}>
<motion.div
className={styles.container}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
transition={{ staggerChildren: 0.3 }}
>
{/* Left Side - Book a Table Form */}
<motion.div
className={styles.formBlock}
initial={{ opacity: 0, x: -60 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<h2 className={styles.title}>Book a Table</h2>
<p className={styles.subtitle}>
Reserve your table for an unforgettable Turkish dining experience. We look forward to serving you our authentic cuisine.
</p>
{alert.show && (
<div className={`${styles.alert} ${styles[`alert${alert.type.charAt(0).toUpperCase() + alert.type.slice(1)}`]}`}>
{alert.message}
</div>
)}
<form className={styles.form} onSubmit={handleSubmit}>
<div className={styles.formRow}>
<div className={styles.formGroup}>
<label htmlFor="name" className={styles.formLabel}>Name *</label>
<input
type="text"
id="name"
name="name"
className={styles.input}
placeholder="Your Full Name"
value={formData.name}
onChange={handleChange}
required
/>
{formErrors.name && <small className={styles.errorText}>{formErrors.name}</small>}
</div>
<div className={styles.formGroup}>
<label htmlFor="email" className={styles.formLabel}>Email *</label>
<input
type="email"
id="email"
name="email"
className={styles.input}
placeholder="your@email.com"
value={formData.email}
onChange={handleChange}
required
/>
{formErrors.email && <small className={styles.errorText}>{formErrors.email}</small>}
</div>
</div>
<div className={styles.formRow}>
<div className={styles.formGroup}>
<label htmlFor="phone" className={styles.formLabel}>Phone *</label>
<input
type="tel"
id="phone"
name="phone"
className={styles.input}
placeholder="(519) 000-0000"
value={formData.phone}
onChange={handleChange}
required
/>
{formErrors.phone && <small className={styles.errorText}>{formErrors.phone}</small>}
</div>
<div className={styles.formGroup}>
<label htmlFor="guests" className={styles.formLabel}>Guests</label>
<select
id="guests"
name="guests"
className={styles.input}
value={formData.guests}
onChange={handleChange}
>
<option>1 Guest</option>
<option>2 Guests</option>
<option>3 Guests</option>
<option>4 Guests</option>
<option>5 Guests</option>
<option>6+ Guests</option>
</select>
</div>
</div>
<div className={styles.formRow}>
<div className={styles.formGroup}>
<label htmlFor="date" className={styles.formLabel}>Date *</label>
<input
type="date"
id="date"
name="date"
className={styles.input}
value={formData.date}
onChange={handleChange}
required
/>
{formErrors.date && <small className={styles.errorText}>{formErrors.date}</small>}
</div>
<div className={styles.formGroup}>
<label htmlFor="time" className={styles.formLabel}>Time *</label>
<input
type="time"
id="time"
name="time"
className={styles.input}
value={formData.time}
onChange={handleChange}
required
/>
{formErrors.time && <small className={styles.errorText}>{formErrors.time}</small>}
</div>
</div>
<div className={styles.formGroup}>
<label htmlFor="requests" className={styles.formLabel}>Special Requests</label>
<textarea
id="requests"
name="requests"
className={styles.textarea}
placeholder="Any special dietary requirements or requests..."
rows={4}
maxLength={500}
value={formData.requests}
onChange={handleChange}
></textarea>
<span className={styles.charCount}>{charCount}/500 characters</span>
</div>
<div className={styles.recaptchaWrapper}>
<ReCAPTCHA
sitekey="6Lckq9MrAAAAABjBD9rQYm19BMGFFWiwb9mPiw2K"
onChange={handleCaptchaChange}
/>
{formErrors.captcha && <small className={styles.errorText}>{formErrors.captcha}</small>}
</div>
<button type="submit" className={styles.submitButton}>
Reserve Table
</button>
</form>
</motion.div>
{/* Right Side - Location Info & Map */}
<motion.div
className={styles.infoBlock}
initial={{ opacity: 0, x: 60 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
{/* Location Cards */}
<div className={styles.locationCard}>
<div className={styles.iconWrapper}>
<svg className={styles.icon} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div className={styles.locationInfo}>
<h3 className={styles.locationTitle}>Kitchener Location</h3>
<p className={styles.locationSubtitle}>Main Restaurant</p>
<p className={styles.locationAddress}>
1187 Fischer-Hallman Rd #411<br />
Kitchener, ON N2T 4H2
</p>
</div>
</div>
<div className={styles.locationCard}>
<div className={styles.iconWrapper}>
<svg className={styles.icon} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 11a3 3 0 11-6 0 3 3 0 016 0z" />
</svg>
</div>
<div className={styles.locationInfo}>
<h3 className={styles.locationTitle}>Burlington Location</h3>
<p className={styles.locationSubtitle}>Second Location</p>
<p className={styles.locationAddress}>
1860 Appleby Line<br />
Burlington, ON L7L 7H7
</p>
</div>
</div>
<div className={styles.locationCard}>
<div className={styles.iconWrapper}>
<svg className={styles.icon} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 5a2 2 0 012-2h3.28a1 1 0 01.948.684l1.498 4.493a1 1 0 01-.502 1.21l-2.257 1.13a11.042 11.042 0 005.516 5.516l1.13-2.257a1 1 0 011.21-.502l4.493 1.498a1 1 0 01.684.949V19a2 2 0 01-2 2h-1C9.716 21 3 14.284 3 6V5z" />
</svg>
</div>
<div className={styles.locationInfo}>
<h3 className={styles.locationTitle}>Call Us</h3>
<p className={styles.locationSubtitle}>For reservations & inquiries</p>
<p className={styles.locationPhone}>(519) 570-4848</p>
</div>
</div>
{/* Map */}
<div className={styles.mapContainer}>
<iframe
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d2895.8474424447677!2d-80.48089492346795!3d43.44826897111238!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x882b8b1e1e1e1e1e%3A0x1e1e1e1e1e1e1e1e!2s1187%20Fischer-Hallman%20Rd%2C%20Kitchener%2C%20ON!5e0!3m2!1sen!2sca!4v1234567890123!5m2!1sen!2sca"
width="100%"
height="300"
style={{ border: 0 }}
allowFullScreen
loading="lazy"
referrerPolicy="no-referrer-when-downgrade"
title="Restaurant Location Map"
></iframe>
</div>
</motion.div>
</motion.div>
</section>
<Footer />
</main>
);
}

View File

@ -0,0 +1,219 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
background-color: #f5e6d3;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)), url('/images/hero-2.png');
background-size: cover;
background-position: center;
text-align: center;
}
.heroContent {
max-width: 800px;
margin: 0 auto;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.section {
padding: 80px 20px;
max-width: 1200px;
margin: 0 auto;
/* background-color: #0a0a0a; */
}
.tabs {
display: flex;
justify-content: center;
gap: 1.5rem;
margin-bottom: 3rem;
}
.tab {
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
padding: 12px 30px;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
font-family: var(--font-lato);
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
.tab:hover,
.activeTab {
background-color: #C5A059;
color: #F5E6D3;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem;
}
.imageWrapper {
position: relative;
aspect-ratio: 4/3;
cursor: pointer;
overflow: hidden;
border: 4px solid #C5A059;
}
.image {
object-fit: cover;
transition: transform 0.5s ease;
}
.imageWrapper:hover .image {
transform: scale(1.1);
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(58, 12, 8, 0.7);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: opacity 0.3s ease;
}
.imageWrapper:hover .overlay {
opacity: 1;
}
.viewText {
color: #C5A059;
font-size: 1.2rem;
border: 2px solid #C5A059;
padding: 0.5rem 1.5rem;
font-family: var(--font-lato);
text-transform: uppercase;
letter-spacing: 1px;
}
/* Lightbox */
.lightbox {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.95);
z-index: 1000;
display: flex;
align-items: center;
justify-content: center;
}
.lightboxContent {
position: relative;
width: 90%;
height: 80%;
max-width: 1000px;
display: flex;
align-items: center;
justify-content: center;
}
.lightboxImageWrapper {
position: relative;
width: 100%;
height: 100%;
}
.closeBtn {
position: absolute;
top: -40px;
right: 0;
background: none;
border: none;
color: #C5A059;
font-size: 2.5rem;
cursor: pointer;
transition: color 0.3s;
}
.closeBtn:hover {
color: #fff;
}
.navBtn {
position: absolute;
top: 50%;
transform: translateY(-50%);
background: rgba(197, 160, 89, 0.2);
border: 2px solid #C5A059;
color: #C5A059;
font-size: 2rem;
padding: 1rem;
cursor: pointer;
z-index: 10;
transition: all 0.3s;
}
.navBtn:hover {
background: rgba(197, 160, 89, 0.5);
color: #fff;
}
.prevBtn {
left: -60px;
}
.nextBtn {
right: -60px;
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.prevBtn {
left: 10px;
}
.nextBtn {
right: 10px;
}
.tabs {
flex-wrap: wrap;
}
}

209
src/app/gallery/page.tsx Normal file
View File

@ -0,0 +1,209 @@
'use client'
import { useState } from 'react';
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import Image from "next/image";
import Link from "next/link";
import styles from "./gallery.module.css";
import { galleryData } from "@/utils/constant";
import { motion, AnimatePresence } from "framer-motion";
const categories = ['All', 'Food', 'Interior'];
export default function GalleryPage() {
const [activeTab, setActiveTab] = useState('All');
const [lightboxOpen, setLightboxOpen] = useState(false);
const [currentIndex, setCurrentIndex] = useState(0);
// Animation variants
const heroVariants = {
hidden: { opacity: 0, y: -20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8 }
}
};
const tabsVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: 0.6,
staggerChildren: 0.1
}
}
};
const tabVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4 }
}
};
const gridVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.08,
delayChildren: 0.1
}
}
};
const imageVariants = {
hidden: { opacity: 0, scale: 0.9 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: 0.5 }
}
};
const filteredImages = activeTab === 'All'
? galleryData
: galleryData.filter(img => img.category === activeTab);
const openLightbox = (index: number) => {
setCurrentIndex(index);
setLightboxOpen(true);
};
const closeLightbox = () => {
setLightboxOpen(false);
};
const nextImage = (e: React.MouseEvent) => {
e.stopPropagation();
setCurrentIndex((prev) => (prev + 1) % filteredImages.length);
};
const prevImage = (e: React.MouseEvent) => {
e.stopPropagation();
setCurrentIndex((prev) => (prev - 1 + filteredImages.length) % filteredImages.length);
};
return (
<main className={styles.main}>
<Navbar />
<motion.section
className={styles.hero}
initial="hidden"
animate="visible"
variants={heroVariants}
>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>Our Gallery</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / Gallery
</p>
</div>
</motion.section>
<section className={styles.section}>
<motion.div
className={styles.tabs}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={tabsVariants}
>
{categories.map(category => (
<motion.button
key={category}
className={`${styles.tab} ${activeTab === category ? styles.activeTab : ''}`}
onClick={() => setActiveTab(category)}
variants={tabVariants}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{category}
</motion.button>
))}
</motion.div>
<motion.div
className={styles.grid}
key={activeTab}
initial="hidden"
animate="visible"
variants={gridVariants}
>
{filteredImages.map((img, index) => (
<motion.div
key={img.id}
className={styles.imageWrapper}
onClick={() => openLightbox(index)}
variants={imageVariants}
whileHover={{
scale: 1.05,
transition: { duration: 0.3 }
}}
>
<Image
src={img.src}
alt={img.alt}
fill
className={styles.image}
/>
<div className={styles.overlay}>
<span className={styles.viewText}>View</span>
</div>
</motion.div>
))}
</motion.div>
</section>
<AnimatePresence>
{lightboxOpen && (
<motion.div
className={styles.lightbox}
onClick={closeLightbox}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.3 }}
>
<motion.div
className={styles.lightboxContent}
onClick={(e) => e.stopPropagation()}
initial={{ scale: 0.8, opacity: 0 }}
animate={{ scale: 1, opacity: 1 }}
exit={{ scale: 0.8, opacity: 0 }}
transition={{ duration: 0.3 }}
>
<button className={styles.closeBtn} onClick={closeLightbox}>×</button>
<button className={`${styles.navBtn} ${styles.prevBtn}`} onClick={prevImage}></button>
<motion.div
className={styles.lightboxImageWrapper}
key={currentIndex}
initial={{ opacity: 0, x: 50 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -50 }}
transition={{ duration: 0.3 }}
>
<Image
src={filteredImages[currentIndex].src}
alt={filteredImages[currentIndex].alt}
fill
style={{ objectFit: 'contain' }}
/>
</motion.div>
<button className={`${styles.navBtn} ${styles.nextBtn}`} onClick={nextImage}></button>
</motion.div>
</motion.div>
)}
</AnimatePresence>
<Footer />
</main>
);
}

View File

@ -1,10 +1,11 @@
:root {
--color-gold: #bf9b30;
--color-gold: #b28839;
--color-dark: #0a0a0a;
--color-input-bg: #e8e0d5;
--color-text-light: #f5f5f5;
--font-playfair: 'Playfair Display', serif;
--font-lato: 'Lato', sans-serif;
--font-cormorant: 'Cormorant Garamond', serif;
}
* {
@ -19,7 +20,8 @@ body {
overflow-x: hidden;
background-color: var(--color-dark);
color: var(--color-text-light);
font-family: var(--font-lato);
font-family: var(--font-cormorant);
font-weight: 300;
/* Ensure default font is applied */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

View File

@ -1,5 +1,5 @@
import type { Metadata } from "next";
import { Playfair_Display, Lato, Cinzel, Inter } from "next/font/google";
import { Playfair_Display, Lato, Cinzel, Inter, Cormorant_Garamond } from "next/font/google";
import "./globals.css";
import ScrollToTop from "@/components/ScrollToTop/ScrollToTop";
@ -28,6 +28,13 @@ const inter = Inter({
display: "swap",
});
const cormorantGaramond = Cormorant_Garamond({
weight: ["300", "400"],
subsets: ["latin"],
variable: "--font-cormorant",
display: "swap",
});
export const metadata: Metadata = {
title: "Antalya Restaurant - Book A Table",
description: "Experience luxury dining at Antalya Restaurant.",
@ -41,8 +48,8 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
<html lang="en" className={`${playfair.variable} ${lato.variable} ${cinzel.variable} ${inter.variable}`}>
<body className={lato.className}>
<html lang="en" className={`${playfair.variable} ${lato.variable} ${cinzel.variable} ${inter.variable} ${cormorantGaramond.variable}`}>
<body className={cormorantGaramond.className}>
<Navbar />
{children}
<ScrollToTop />

View File

@ -0,0 +1,289 @@
.main {
background-color: var(--color-dark);
color: var(--color-text-light);
min-height: 100vh;
background-color: #f5e6d3;
}
.hero {
padding: 10rem 2rem;
background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('/images/hero-3.png');
background-size: cover;
background-position: center;
background-attachment: fixed;
text-align: center;
}
.heroContent {
max-width: 800px;
margin: 0 auto;
}
.heroTitle {
font-family: var(--font-playfair);
font-size: 4rem;
color: #C5A059;
margin-bottom: 1rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.breadcrumb {
font-size: 1.1rem;
color: #C5A059;
font-family: var(--font-lato);
}
.breadcrumb a {
color: #fff;
transition: color 0.3s;
}
.breadcrumb a:hover {
color: var(--color-gold);
}
.menuSection {
max-width: 1200px;
margin: 0 auto;
padding: 60px 20px 80px;
/* background-color: #0a0a0a; */
}
/* Category Tabs */
.tabs {
display: flex;
justify-content: center;
gap: 1.5rem;
margin-bottom: 3rem;
flex-wrap: wrap;
}
.tab {
background: transparent;
border: 2px solid #C5A059;
color: #C5A059;
padding: 12px 30px;
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
font-family: var(--font-playfair);
text-transform: uppercase;
letter-spacing: 1px;
font-weight: 600;
}
.tab:hover,
.activeTab {
background-color: #C5A059;
color: #F5E6D3;
transform: translateY(-2px);
}
.menuGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2rem;
}
.menuCard {
display: flex;
gap: 1.2rem;
background: #c5a059;
padding: 1.2rem;
border: 1px solid rgba(197, 160, 89, 0.2);
transition: all 0.4s ease;
animation: fadeInUp 0.6s ease forwards;
opacity: 0;
}
.menuCard:nth-child(1) {
animation-delay: 0.1s;
}
.menuCard:nth-child(2) {
animation-delay: 0.2s;
}
.menuCard:nth-child(3) {
animation-delay: 0.3s;
}
.menuCard:nth-child(4) {
animation-delay: 0.4s;
}
.menuCard:nth-child(5) {
animation-delay: 0.5s;
}
.menuCard:nth-child(6) {
animation-delay: 0.6s;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.menuCard:hover {
background: #c5a059;
border-color: #F5E6D3;
transform: translateY(-5px);
box-shadow: 0 10px 30px rgba(197, 160, 89, 0.2);
}
.imageFrame {
position: relative;
width: 130px;
height: 130px;
flex-shrink: 0;
padding: 6px;
background: linear-gradient(135deg, rgba(197, 160, 89, 0.1), rgba(197, 160, 89, 0.05));
}
.imageWrapper {
position: relative;
width: 100%;
height: 100%;
border: 2px solid #F5E6D3;
overflow: hidden;
}
.dishImage {
object-fit: cover;
transition: transform 0.5s ease;
}
.menuCard:hover .dishImage {
transform: scale(1.1);
}
/* Corner decorations */
.cornerTL,
.cornerTR,
.cornerBL,
.cornerBR {
position: absolute;
width: 15px;
height: 15px;
border: 2px solid #F5E6D3;
}
.cornerTL {
top: 0;
left: 0;
border-right: none;
border-bottom: none;
}
.cornerTR {
top: 0;
right: 0;
border-left: none;
border-bottom: none;
}
.cornerBL {
bottom: 0;
left: 0;
border-right: none;
border-top: none;
}
.cornerBR {
bottom: 0;
right: 0;
border-left: none;
border-top: none;
}
.cardContent {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
}
.cardHeader {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: 0.6rem;
}
.dishName {
font-family: var(--font-playfair);
font-size: 1.3rem;
color: #F5E6D3;
text-transform: capitalize;
transition: color 0.3s;
}
.menuCard:hover .dishName {
color: #F5E6D3;
}
.price {
font-size: 1.2rem;
color: #F5E6D3;
font-weight: bold;
font-family: var(--font-playfair);
}
.description {
font-size: 0.9rem;
color: #F5E6D3;
line-height: 1.6;
font-family: var(--font-lato);
opacity: 0.85;
}
@media (max-width: 968px) {
.menuGrid {
grid-template-columns: 1fr;
}
}
@media (max-width: 768px) {
.heroTitle {
font-size: 3rem;
}
.menuSection {
padding: 40px 20px 60px;
}
.tabs {
gap: 1rem;
}
.tab {
padding: 10px 20px;
font-size: 0.9rem;
}
.menuCard {
flex-direction: column;
align-items: center;
text-align: center;
}
.imageFrame {
width: 150px;
height: 150px;
}
.cardHeader {
flex-direction: column;
gap: 0.5rem;
align-items: center;
}
}

155
src/app/menu/page.tsx Normal file
View File

@ -0,0 +1,155 @@
'use client'
import { useState } from 'react';
import Navbar from "@/components/Navbar/Navbar";
import Footer from "@/components/Footer/Footer";
import Image from "next/image";
import Link from "next/link";
import styles from "./menu.module.css";
import { menuData } from "@/utils/constant";
import { motion } from "framer-motion";
export default function MenuPage() {
const [activeCategory, setActiveCategory] = useState(0);
// Animation variants
const heroVariants = {
hidden: { opacity: 0, y: -20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.8 }
}
};
const tabsVariants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
};
const tabVariants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.4 }
}
};
const gridVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2
}
}
};
const cardVariants = {
hidden: { opacity: 0, y: 30 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.5 }
}
};
return (
<main className={styles.main}>
<Navbar />
<motion.section
className={styles.hero}
initial="hidden"
animate="visible"
variants={heroVariants}
>
<div className={styles.heroContent}>
<h1 className={styles.heroTitle}>Our Menu</h1>
<p className={styles.breadcrumb}>
<Link href="/">Home</Link> / Menu
</p>
</div>
</motion.section>
<section className={styles.menuSection}>
{/* Category Tabs */}
<motion.div
className={styles.tabs}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
variants={tabsVariants}
>
{menuData.map((section, index) => (
<motion.button
key={index}
className={`${styles.tab} ${activeCategory === index ? styles.activeTab : ''}`}
onClick={() => setActiveCategory(index)}
variants={tabVariants}
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
{section.category}
</motion.button>
))}
</motion.div>
{/* Menu Items Grid */}
<motion.div
className={styles.menuGrid}
key={activeCategory}
initial="hidden"
animate="visible"
variants={gridVariants}
>
{menuData[activeCategory].items.map((item, itemIndex) => (
<motion.div
key={itemIndex}
className={styles.menuCard}
variants={cardVariants}
whileHover={{
y: -8,
transition: { duration: 0.3 }
}}
>
<div className={styles.imageFrame}>
<div className={styles.imageWrapper}>
<Image
src={item.image}
alt={item.name}
fill
className={styles.dishImage}
/>
</div>
<div className={styles.cornerTL}></div>
<div className={styles.cornerTR}></div>
<div className={styles.cornerBL}></div>
<div className={styles.cornerBR}></div>
</div>
<div className={styles.cardContent}>
<div className={styles.cardHeader}>
<h3 className={styles.dishName}>{item.name}</h3>
<span className={styles.price}>{item.price}</span>
</div>
<p className={styles.description}>{item.description}</p>
</div>
</motion.div>
))}
</motion.div>
</section>
<Footer />
</main>
);
}

View File

@ -1,4 +1,3 @@
import Navbar from "@/components/Navbar/Navbar";
import Hero from "@/components/Hero/Hero";
import PopularDishes from "@/components/PopularDishes/PopularDishes";
import About from "@/components/About/About";
@ -11,7 +10,6 @@ import Footer from "@/components/Footer/Footer";
export default function Home() {
return (
<main>
<Navbar />
<Hero />
<PopularDishes />
<About />

View File

@ -1,20 +1,41 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import styles from './About.module.css'
import { motion } from 'framer-motion'
export default function About() {
return (
<section className={styles.section} id="about">
<div className={styles.container}>
<div className={styles.imageWrapper}>
<motion.div
className={styles.container}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
transition={{ staggerChildren: 0.3 }}
>
<motion.div
className={styles.imageWrapper}
initial={{ opacity: 0, x: -60 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
<Image
src="/images/restaurant-interior.png"
alt="About Antalya Interior"
fill
className={styles.image}
/>
</div>
<div className={styles.content}>
</motion.div>
<motion.div
className={styles.content}
initial={{ opacity: 0, x: 60 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.8 }}
>
{/* Decorative Icon SVG */}
<svg className={styles.icon} viewBox="0 0 100 100" fill="currentColor">
<path d="M50 0 C60 20 80 40 100 50 C80 60 60 80 50 100 C40 80 20 60 0 50 C20 40 40 20 50 0 Z" />
@ -31,8 +52,8 @@ export default function About() {
<Link href="#about-more" className={styles.button}>
Learn More
</Link>
</div>
</div>
</motion.div>
</motion.div>
</section>
)
}

View File

@ -1,9 +1,9 @@
.section {
padding: 80px 20px;
background-color: #3a0c08;
/* Dark red/brown background */
background-image: url('/images/pattern-overlay.png');
/* Optional pattern */
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
text-align: center;
position: relative;
overflow: hidden;
@ -21,19 +21,12 @@
.sliderContainer {
display: flex;
justify-content: center;
align-items: center;
gap: 20px;
max-width: 1400px;
margin: 0 auto;
position: relative;
padding: 0 40px;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 30px;
max-width: 1400px;
width: 100%;
position: relative;
padding: 0 60px;
margin: 0 auto;
}
.card {
@ -46,7 +39,9 @@
flex-direction: column;
align-items: center;
transition: transform 0.3s ease;
height: 100%;
flex: 0 0 calc(33.333% - 30px);
max-width: calc(33.333% - 30px);
box-sizing: border-box;
}
.card:hover {
@ -103,20 +98,28 @@
}
.arrow {
background: none;
border: none;
position: absolute;
top: 50%;
transform: translateY(-50%);
color: #C5A059;
font-size: 3rem;
cursor: pointer;
background: none;
border: none;
transition: transform 0.2s;
padding: 0 10px;
display: flex;
align-items: center;
justify-content: center;
z-index: 10;
}
.arrow:hover {
transform: scale(1.1);
transform: translateY(-50%) scale(1.1);
}
.prevArrow {
left: 0;
}
.nextArrow {
right: 0;
}
.viewMoreContainer {
@ -127,15 +130,13 @@
display: inline-block;
padding: 15px 40px;
border: 1px solid #C5A059;
color: #C5A059;
color: #fff;
font-family: var(--font-inter), sans-serif;
text-transform: uppercase;
font-size: 1rem;
text-decoration: none;
transition: all 0.3s ease;
background: transparent;
border-radius: 5px;
/* Slight radius as per some designs, or keep square */
border-radius: 8px;
}
@ -144,12 +145,88 @@
color: #000;
}
@media (max-width: 1024px) {
.grid {
grid-template-columns: 1fr;
/* Responsive adjustments for different viewport sizes */
@media (max-width: 1400px) {
.sliderContainer {
padding: 0 50px;
}
.arrow {
font-size: 2.8rem;
}
}
@media (max-width: 1200px) {
.sliderContainer {
padding: 0 40px;
}
.arrow {
font-size: 2.5rem;
}
}
@media (max-width: 1024px) {
.sliderContainer {
padding: 0 30px;
}
.card {
flex: 0 0 100%;
max-width: 100%;
}
/* Hide 2nd and 3rd cards on mobile, show only 1st */
.card:nth-child(2),
.card:nth-child(3) {
display: none;
}
.arrow {
font-size: 2.2rem;
}
.prevArrow {
left: 5px;
}
.nextArrow {
right: 5px;
}
.title {
font-size: 2.5rem;
}
}
@media (max-width: 768px) {
.sliderContainer {
padding: 0 20px;
}
.arrow {
font-size: 2rem;
}
.prevArrow {
left: 0;
}
.nextArrow {
right: 0;
}
}
@media (max-width: 480px) {
.sliderContainer {
padding: 0 15px;
}
.arrow {
font-size: 1.8rem;
}
.title {
font-size: 2rem;
}
}

View File

@ -1,61 +1,73 @@
'use client'
import { useState, useEffect } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import styles from './Blogs.module.css'
const blogs = [
{
id: 1,
title: 'The Art of Turkish Tea',
image: '/images/dish-1.png', // Placeholder
excerpt: 'Lorem ipsum dolor sit amet, tuem cergat imlpecion diirm, iadioc-ticid est nt eedrama inapat.'
},
{
id: 2,
title: 'Secrets of Charcoal Grilling',
image: '/images/hero-3.png', // Placeholder
excerpt: 'Lorem ipsum dolor sit amet, tuem cergat imlpecion diirm, iadioc-ticid est nt eedrama inapat.'
},
{
id: 3,
title: 'A Taste of Sweet Legacy',
image: '/images/dish-2.png', // Placeholder
excerpt: 'Lorem ipsum dolor sit amet, tuem cergat imlpecion diirm, iadioc-ticid est nt eedrama inapat.'
}
]
import { blogData } from '@/utils/constant'
export default function Blogs() {
const [currentIndex, setCurrentIndex] = useState(0)
const nextSlide = () => {
setCurrentIndex((prev) => (prev + 1) % blogData.length)
}
const prevSlide = () => {
setCurrentIndex((prev) => (prev === 0 ? blogData.length - 1 : prev - 1))
}
// Auto-slide effect
useEffect(() => {
const interval = setInterval(() => {
setCurrentIndex((prev) => (prev + 1) % blogData.length)
}, 5000) // Change slide every 5 seconds
return () => clearInterval(interval)
}, [currentIndex])
// Get the 3 visible blogs
const getVisibleBlogs = () => {
const items = []
for (let i = 0; i < 3; i++) {
const index = (currentIndex + i) % blogData.length
items.push(blogData[index])
}
return items
}
const visibleItems = getVisibleBlogs()
return (
<section className={styles.section} id="blog">
<h2 className={styles.title}>OUR BLOGS</h2>
<div className={styles.sliderContainer}>
<button className={styles.arrow}></button>
<button className={`${styles.arrow} ${styles.prevArrow}`} onClick={prevSlide}></button>
<div className={styles.grid}>
{blogs.map((blog) => (
<div key={blog.id} className={styles.card}>
<div className={styles.imageContainer}>
<Image
src={blog.image}
alt={blog.title}
fill
style={{ objectFit: 'cover' }}
/>
</div>
<h3 className={styles.blogTitle}>{blog.title}</h3>
<p className={styles.excerpt}>{blog.excerpt}</p>
<Link href="#" className={styles.button}>
View More
</Link>
{visibleItems.map((blog) => (
<div key={blog.id} className={styles.card}>
<div className={styles.imageContainer}>
<Image
src={blog.imageDetail}
alt={blog.title}
fill
style={{ objectFit: 'cover' }}
/>
</div>
))}
</div>
<h3 className={styles.blogTitle}>{blog.title}</h3>
<p className={styles.excerpt}>{blog.para}</p>
<Link href={`/blog/${blog.slug}`} className={styles.button}>
View More
</Link>
</div>
))}
<button className={styles.arrow}></button>
<button className={`${styles.arrow} ${styles.nextArrow}`} onClick={nextSlide}></button>
</div>
<div className={styles.viewMoreContainer}>
<Link href="#" className={styles.viewMoreButton}>
<Link href="/blog" className={styles.viewMoreButton}>
Read More Blogs
</Link>
</div>

View File

@ -62,16 +62,14 @@
.inputGroup {
display: flex;
align-items: center;
gap: 1rem;
flex-direction: column;
gap: 0.5rem;
}
.label {
font-family: var(--font-playfair);
color: var(--color-gold);
width: 80px;
font-size: 1.1rem;
text-align: right;
}
.input,
@ -83,7 +81,7 @@
padding: 0.8rem 1rem;
font-family: var(--font-lato);
font-size: 1rem;
color: #333;
color: #757575;
outline: none;
transition: box-shadow 0.3s ease;
}
@ -107,7 +105,7 @@
font-size: 1rem;
cursor: pointer;
transition: all 0.3s ease;
align-self: flex-end;
align-self: center;
margin-top: 1rem;
border-radius: 4px;
}
@ -124,6 +122,55 @@
font-family: var(--font-lato);
}
/* Alert styles */
.alert {
padding: 1rem;
margin-bottom: 1.5rem;
border-radius: 4px;
font-family: var(--font-lato);
text-align: center;
}
.alertSuccess {
background-color: rgba(40, 167, 69, 0.2);
color: #28a745;
border: 1px solid #28a745;
}
.alertDanger {
background-color: rgba(220, 53, 69, 0.2);
color: #dc3545;
border: 1px solid #dc3545;
}
/* Error text */
.errorText {
color: #dc3545;
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
font-family: var(--font-lato);
}
/* Character count */
.charCount {
color: var(--color-gold);
font-size: 0.875rem;
margin-top: 0.25rem;
display: block;
text-align: right;
font-family: var(--font-lato);
}
/* ReCAPTCHA wrapper */
.recaptchaWrapper {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
margin: 1rem 0;
}
@media (min-width: 768px) {
.imageContainer {
display: block;

View File

@ -1,32 +1,121 @@
'use client'
import { useState } from 'react'
import { useState, useEffect } from 'react'
import Image from 'next/image'
import ReCAPTCHA from "react-google-recaptcha"
import axios from "axios"
import styles from './BookTable.module.css'
import { submitReservation } from '@/app/actions'
interface FormErrors {
name?: string;
phone?: string;
date?: string;
captcha?: string;
}
export default function BookTable() {
const [isSubmitting, setIsSubmitting] = useState(false)
const [message, setMessage] = useState('')
const [email, setEmail] = useState("");
async function handleSubmit(formData: FormData) {
setIsSubmitting(true)
setMessage('')
useEffect(() => {
const user = "info";
const domain = "antalya-restaurant.com";
setEmail(`${user}@${domain}`);
}, []);
const [formData, setFormData] = useState({
name: "",
phone: "",
date: "",
message: "",
});
const [formErrors, setFormErrors] = useState<FormErrors>({});
const [captchaToken, setCaptchaToken] = useState<string | null>(null);
const [alert, setAlert] = useState({ show: false, type: "", message: "" });
const [charCount, setCharCount] = useState(0);
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
if (name === "message") {
setCharCount(value.length);
}
};
const handleCaptchaChange = (token: string | null) => {
setCaptchaToken(token);
};
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const errors: FormErrors = {};
if (!formData.name.trim()) errors.name = "Name is required.";
if (!formData.phone.trim()) errors.phone = "Phone is required.";
if (!formData.date.trim()) errors.date = "Date is required.";
if (!captchaToken) errors.captcha = "Please verify the CAPTCHA.";
setFormErrors(errors);
if (Object.keys(errors).length > 0) return;
const emailData = {
name: formData.name,
phone: formData.phone,
email: email,
subject: `Table Reservation - ${formData.name} on ${formData.date}`,
message: `
<strong>Reservation Details:</strong><br/>
Name: ${formData.name}<br/>
Phone: ${formData.phone}<br/>
Date: ${formData.date}<br/><br/>
<strong>Message:</strong><br/>
${formData.message || "None"}
`,
to: email,
senderName: "Antalya Restaurant - Table Reservation",
recaptchaToken: captchaToken,
};
try {
const result = await submitReservation(formData)
if (result.success) {
setMessage(result.message)
const form = document.getElementById('reservation-form') as HTMLFormElement
form?.reset()
}
const res = await axios.post(
"https://mailserver.metatronnest.com/send",
emailData,
{ headers: { "Content-Type": "application/json" } }
);
setAlert({
show: true,
type: "success",
message: res?.data?.message || "Reservation request sent successfully! We'll contact you soon.",
});
setFormData({
name: "",
phone: "",
date: "",
message: "",
});
setCaptchaToken(null);
setFormErrors({});
setCharCount(0);
} catch (error) {
console.error('Error submitting form:', error)
setMessage('Something went wrong. Please try again.')
} finally {
setIsSubmitting(false)
setAlert({
show: true,
type: "danger",
message: "Failed to send reservation request. Please try again later.",
});
}
}
};
useEffect(() => {
if (alert.show) {
const timer = setTimeout(() => {
setAlert((prev) => ({ ...prev, show: false }));
}, 5000);
return () => clearTimeout(timer);
}
}, [alert.show]);
return (
<section className={styles.section} id="book">
@ -44,65 +133,96 @@ export default function BookTable() {
<div className={styles.content}>
<h2 className={styles.title}>Book A Table</h2>
{alert.show && (
<div className={`${styles.alert} ${styles[`alert${alert.type.charAt(0).toUpperCase() + alert.type.slice(1)}`]}`}>
{alert.message}
</div>
)}
<form
id="reservation-form"
action={handleSubmit}
onSubmit={handleSubmit}
className={styles.form}
>
{/* Name Input with Placeholder */}
<div className={styles.inputGroup}>
<label htmlFor="name" className={styles.label}>Name</label>
<label htmlFor="name" className={styles.label}>Name *</label>
<input
type="text"
id="name"
name="name"
required
className={styles.input}
placeholder="Your Full Name"
value={formData.name}
onChange={handleChange}
/>
{formErrors.name && <small className={styles.errorText}>{formErrors.name}</small>}
</div>
{/* Phone Input with Placeholder */}
<div className={styles.inputGroup}>
<label htmlFor="phone" className={styles.label}>Phone</label>
<label htmlFor="phone" className={styles.label}>Phone *</label>
<input
type="tel"
id="phone"
name="phone"
required
className={styles.input}
placeholder="(519) 000-0000"
value={formData.phone}
onChange={handleChange}
/>
{formErrors.phone && <small className={styles.errorText}>{formErrors.phone}</small>}
</div>
<div className={styles.inputGroup}>
<label htmlFor="date" className={styles.label}>Date</label>
<label htmlFor="date" className={styles.label}>Date *</label>
<input
type="date"
id="date"
name="date"
required
className={styles.input}
value={formData.date}
onChange={handleChange}
/>
{formErrors.date && <small className={styles.errorText}>{formErrors.date}</small>}
</div>
{/* Message Textarea with Placeholder */}
<div className={styles.inputGroup}>
<label htmlFor="message" className={styles.label}>Message</label>
<textarea
id="message"
name="message"
className={styles.textarea}
placeholder="Any special requests..."
maxLength={500}
value={formData.message}
onChange={handleChange}
/>
<span className={styles.charCount}>{charCount}/500 characters</span>
</div>
<button
<button
type="submit"
disabled={isSubmitting}
className={styles.submitButton}
>
{isSubmitting ? 'Submitting...' : 'Submit Reservation'}
Submit Reservation
</button>
{message && <p className={styles.successMessage}>{message}</p>}
<div className={styles.recaptchaWrapper}>
<ReCAPTCHA
sitekey="6Lckq9MrAAAAABjBD9rQYm19BMGFFWiwb9mPiw2K"
onChange={handleCaptchaChange}
/>
{formErrors.captcha && <small className={styles.errorText}>{formErrors.captcha}</small>}
</div>
</form>
</div>
</div>
</section>
)
}
}

View File

@ -0,0 +1,123 @@
.faqSection {
padding: 30px 20px 60px 20px;
/* background-color: #F5E6D3; */
max-width: 1200px;
margin: 0 auto;
}
.faqTitle {
font-family: var(--font-playfair);
font-size: 2.5rem;
color: #5d4037;
text-align: start;
margin-bottom: 3rem;
text-transform: uppercase;
letter-spacing: 2px;
}
.faqContainer {
display: flex;
flex-direction: column;
gap: 1rem;
}
.faqItem {
background: #fff;
border: 2px solid #C5A059;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
}
.faqItem:hover {
box-shadow: 0 4px 12px rgba(197, 160, 89, 0.2);
}
.faqQuestion {
width: 100%;
padding: 1.5rem 2rem;
background: transparent;
border: none;
display: flex;
justify-content: space-between;
align-items: center;
cursor: pointer;
font-family: var(--font-lato);
font-size: 1.1rem;
font-weight: 600;
color: #5d4037;
text-align: left;
transition: all 0.3s ease;
}
.faqQuestion:hover {
background-color: #faf6f0;
}
.faqQuestion.active {
background-color: #C5A059;
color: #fff;
}
.icon {
font-size: 1.5rem;
font-weight: bold;
color: #C5A059;
transition: transform 0.3s ease;
flex-shrink: 0;
margin-left: 1rem;
}
.faqQuestion.active .icon {
color: #fff;
transform: rotate(180deg);
}
.faqAnswer {
max-height: 0;
overflow: hidden;
transition: max-height 0.4s ease, padding 0.4s ease;
}
.faqAnswer.open {
max-height: 1000px;
padding: 1.5rem 2rem;
border-top: 1px solid #e0d5c7;
}
.faqContent {
font-family: var(--font-lato);
font-size: 1rem;
color: #5d4037;
line-height: 1.8;
}
.faqContent a {
color: #C5A059;
text-decoration: underline;
transition: color 0.3s;
}
.faqContent a:hover {
color: #b08f4a;
}
@media (max-width: 768px) {
.faqSection {
padding: 40px 15px;
}
.faqTitle {
font-size: 2rem;
margin-bottom: 2rem;
}
.faqQuestion {
padding: 1.2rem 1.5rem;
font-size: 1rem;
}
.faqAnswer.open {
padding: 1.2rem 1.5rem;
}
}

View File

@ -0,0 +1,46 @@
'use client'
import { useState } from 'react';
import styles from './FAQ.module.css';
interface FAQItem {
q: string;
a: string;
}
interface FAQProps {
faqs: FAQItem[];
}
export default function FAQ({ faqs }: FAQProps) {
const [openIndex, setOpenIndex] = useState<number | null>(null);
const toggleFAQ = (index: number) => {
setOpenIndex(openIndex === index ? null : index);
};
return (
<div className={styles.faqSection}>
<h2 className={styles.faqTitle}>FAQs</h2>
<div className={styles.faqContainer}>
{faqs.map((faq, index) => (
<div key={index} className={styles.faqItem}>
<button
className={`${styles.faqQuestion} ${openIndex === index ? styles.active : ''}`}
onClick={() => toggleFAQ(index)}
>
<span>{faq.q}</span>
<span className={styles.icon}>{openIndex === index ? '' : '+'}</span>
</button>
<div className={`${styles.faqAnswer} ${openIndex === index ? styles.open : ''}`}>
<div
className={styles.faqContent}
dangerouslySetInnerHTML={{ __html: faq.a }}
/>
</div>
</div>
))}
</div>
</div>
);
}

16
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,16 @@
import React from 'react';
import styles from '@/app/about/about.module.css'; // reuse styles or define minimal inline
export default function Footer() {
return (
<footer className={styles.footer || ''} style={{
backgroundColor: 'var(--color-dark)',
color: '#fff',
textAlign: 'center',
padding: '2rem 0',
marginTop: '4rem',
}}>
<p>© 2025 Antalya Restaurant. All rights reserved.</p>
</footer>
);
}

View File

@ -1,5 +1,6 @@
.footer {
background-color: #2a0a0a;
background-image: url('/images/section-bg.jpg');
/* Deep red/brown */
color: #d4b060;
/* Gold-ish text */
@ -7,6 +8,21 @@
border-top: 1px solid var(--color-gold);
}
/* Add this (or adjust as needed) to Footer.module.css */
.logoImage {
/* Style the Next.js Image component inside the logo container */
width: auto;
height: 100px;
/* Example height - adjust this to your desired size */
max-width: 100%;
}
.logoa {
/* Ensure the link container doesn't disrupt layout */
display: inline-block;
}
.container {
max-width: 1200px;
margin: 0 auto;
@ -57,7 +73,7 @@
display: flex;
align-items: center;
justify-content: center;
color: #2a0a0a;
color: #ffff;
font-weight: bold;
}

View File

@ -1,15 +1,32 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import styles from './Footer.module.css'
export default function Footer() {
const metatronCubeLink = "https://metatroncubesolutions.com/";
return (
<footer className={styles.footer} id="contact">
<div className={styles.container}>
{/* Brand Column */}
<div className={`${styles.column} ${styles.brandColumn}`}>
{/* Logo updated to use Image and Link */}
<div className={styles.logo}>
ANTALYA<br />
<span style={{ fontSize: '1rem', letterSpacing: '4px' }}>RESTAURANT</span>
<Link href="/" onClick={() => window.scrollTo(0, 0)}>
<Image
src="/images/antalya-logo.png"
alt="Antalya Restaurant"
width={200}
height={200}
className={styles.logoImage} // Must be styled in Footer.module.css
priority
/>
</Link>
</div>
{/* End of Logo update */}
<p className={styles.description}>
Discover the essence of fusion cuisine in the heart of Ontario at Antalya Restaurant.
Our carefully curated menu offers a delicious selection from sizzling kebabs and succulent doners to authentic pides, sandwiches, and wraps.
@ -24,14 +41,17 @@ export default function Footer() {
{/* Quick Links Column */}
<div className={styles.column}>
<h3 className={styles.heading}>Quick Links</h3>
{/* Removed the Bootstrap classes (row, col-lg-6) as this is a CSS Modules file and those classes are not defined. Reverting to a single list structure for stability. */}
<ul className={styles.linkList}>
<li className={styles.linkItem}><a href="#" className={styles.link}>Home</a></li>
<li className={styles.linkItem}><a href="#menu" className={styles.link}>Menu</a></li>
<li className={styles.linkItem}><a href="#gallery" className={styles.link}>Gallery</a></li>
<li className={styles.linkItem}><a href="#about" className={styles.link}>About</a></li>
<li className={styles.linkItem}><a href="#blog" className={styles.link}>Blog</a></li>
<li className={styles.linkItem}><a href="#contact" className={styles.link}>Contact</a></li>
<li className={styles.linkItem}><a href="/" className={styles.link}>Home</a></li>
<li className={styles.linkItem}><a href="/about" className={styles.link}>About</a></li>
<li className={styles.linkItem}><a href="/gallery" className={styles.link}>Gallery</a></li>
<li className={styles.linkItem}><a href="/menu" className={styles.link}>Menu</a></li>
<li className={styles.linkItem}><a href="/blog" className={styles.link}>Blog</a></li>
<li className={styles.linkItem}><a href="/contact" className={styles.link}>Contact</a></li>
</ul>
</div>
{/* Location Column */}
@ -62,8 +82,16 @@ export default function Footer() {
</div>
<div className={styles.copyright}>
© Copyright 2025 Antalya Restaurant | Powered by MetatronCube All Rights Reserved
© Copyright 2025 Antalya Restaurant | Powered by{' '}
<a
href={metatronCubeLink}
target="_blank"
rel="noopener noreferrer"
>
MetatronCube
</a>{' '}
All Rights Reserved
</div>
</footer>
)
}
}

View File

@ -1,7 +1,9 @@
.section {
padding: 6rem 2rem;
background-color: #3a0c08;
/* Very dark red/brown */
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
text-align: center;
}
@ -62,7 +64,7 @@
margin-top: 3rem;
padding: 0.8rem 2rem;
border: 1px solid var(--color-gold);
color: var(--color-gold);
color: #fff;
font-family: var(--font-lato);
text-transform: uppercase;
text-decoration: none;
@ -73,7 +75,7 @@
.button:hover {
background-color: var(--color-gold);
color: #000;
color: #fff;
}
/* Lightbox */

View File

@ -3,6 +3,7 @@
import { useState } from 'react'
import Image from 'next/image'
import styles from './Gallery.module.css'
import Link from 'next/link'
const images = [
'/images/dish-1.png',
@ -37,7 +38,7 @@ export default function Gallery() {
}
return (
<section className={styles.section} id="gallery">
<section className={styles.section}>
<h2 className={styles.title}>Gallery</h2>
<div className={styles.grid}>
{images.map((src, index) => (
@ -55,7 +56,7 @@ export default function Gallery() {
</div>
))}
</div>
<button className={styles.button}>View More</button>
<Link href="/gallery" className={styles.button}>View More</Link>
{lightboxOpen && (
<div className={styles.lightbox} onClick={closeLightbox}>

View File

@ -90,8 +90,9 @@
.btnPrimary {
background-color: var(--color-gold);
color: #000;
color: #fff;
border: 1px solid var(--color-gold);
border-radius: 10px;
}
.btnPrimary:hover {
@ -102,11 +103,12 @@
.btnSecondary {
background-color: transparent;
color: #fff;
border: 1px solid #fff;
border: 3px solid #b28839;
border-radius: 10px;
}
.btnSecondary:hover {
background-color: #fff;
background-color: #b28839;
color: #000;
}

View File

@ -4,6 +4,7 @@ import { useState, useEffect } from 'react'
import Image from 'next/image'
import Link from 'next/link'
import styles from './Hero.module.css'
import { motion, AnimatePresence } from 'framer-motion'
const slides = [
{
@ -38,34 +39,62 @@ export default function Hero() {
return (
<section className={styles.hero}>
{slides.map((slide, index) => (
<div
key={slide.id}
className={`${styles.slide} ${index === currentSlide ? styles.active : ''}`}
<AnimatePresence mode="wait">
<motion.div
key={currentSlide}
className={`${styles.slide} ${styles.active}`}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.7 }}
>
<Image
src={slide.image}
alt={slide.title}
src={slides[currentSlide].image}
alt={slides[currentSlide].title}
fill
style={{ objectFit: 'cover' }}
priority={index === 0}
priority={currentSlide === 0}
/>
<div className={styles.overlay}>
<div className={styles.content}>
<h1 className={styles.title}>{slide.title}</h1>
<p className={styles.subtitle}>{slide.subtitle}</p>
<div className={styles.buttons}>
<Link href="#menu" className={`${styles.btn} ${styles.btnPrimary}`}>
<motion.div
className={styles.content}
initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.3 }}
>
<motion.h1
className={styles.title}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
>
{slides[currentSlide].title}
</motion.h1>
<motion.p
className={styles.subtitle}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.7 }}
>
{slides[currentSlide].subtitle}
</motion.p>
<motion.div
className={styles.buttons}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.9 }}
>
<Link href="/menu" className={`${styles.btn} ${styles.btnPrimary}`}>
View Menu
</Link>
<Link href="#book" className={`${styles.btn} ${styles.btnSecondary}`}>
<Link href="/contact" className={`${styles.btn} ${styles.btnSecondary}`}>
Book A Table
</Link>
</div>
</div>
</motion.div>
</motion.div>
</div>
</div>
))}
</motion.div>
</AnimatePresence>
<div className={styles.dots}>
{slides.map((_, index) => (

26
src/components/Navbar.tsx Normal file
View File

@ -0,0 +1,26 @@
import React from 'react';
import Link from 'next/link';
import styles from '@/app/about/about.module.css'; // reuse existing styles if needed
export default function Navbar() {
return (
<nav className={styles.navbar || ''} style={{
backgroundColor: 'var(--color-dark)',
padding: '1rem 2rem',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
color: '#fff',
}}>
<div style={{ fontFamily: 'var(--font-playfair)', fontSize: '1.5rem' }}>
Antalya Restaurant
</div>
<ul style={{ listStyle: 'none', display: 'flex', gap: '1.5rem', margin: 0 }}>
<li><Link href="/" style={{ color: '#fff', textDecoration: 'none' }}>Home</Link></li>
<li><Link href="/about" style={{ color: '#fff', textDecoration: 'none' }}>About</Link></li>
<li><Link href="/menu" style={{ color: '#fff', textDecoration: 'none' }}>Menu</Link></li>
<li><Link href="/contact" style={{ color: '#fff', textDecoration: 'none' }}>Contact</Link></li>
</ul>
</nav>
);
}

View File

@ -3,7 +3,7 @@
top: 0;
left: 0;
width: 100%;
height: 70px;
height: 120px;
background-color: rgba(26, 26, 26, 0.9);
backdrop-filter: blur(10px);
display: flex;
@ -27,7 +27,7 @@
}
.logoImage {
height: 50px;
height: 110px;
width: auto;
object-fit: contain;
}
@ -41,7 +41,7 @@
.navLink {
color: #C9A865;
text-decoration: none;
font-size: 1rem;
font-size: 28px;
font-weight: 400;
letter-spacing: 0.5px;
transition: all 0.3s ease;
@ -154,4 +154,12 @@
.navbar {
padding: 0 20px;
}
}
@media (max-width: 768px) and (min-width: 200px) {
.logoImage {
object-fit: contain;
width: auto;
height: 100px;
}
}

View File

@ -7,13 +7,14 @@ import styles from './Navbar.module.css'
const navLinks = [
{ name: 'Home', href: '/' },
{ name: 'About', href: '#about' },
{ name: 'Gallery', href: '#gallery' },
{ name: 'Menu', href: '#menu' },
{ name: 'Blog', href: '#blog' },
{ name: 'Contact', href: '#contact' },
{ name: 'About', href: '/about' },
{ name: 'Gallery', href: '/gallery' },
{ name: 'Menu', href: '/menu' },
{ name: 'Blog', href: '/blog' },
{ name: 'Contact', href: '/contact' },
]
export default function Navbar() {
const [isOpen, setIsOpen] = useState(false)
const [scrolled, setScrolled] = useState(false)
@ -81,7 +82,7 @@ export default function Navbar() {
src="/images/antalya-logo.png"
alt="Antalya Restaurant"
width={200}
height={60}
height={200}
className={styles.logoImage}
priority
/>

View File

@ -1,9 +1,9 @@
.section {
padding: 80px 20px;
background-color: #3a0c08;
/* Dark red/brown background */
background-image: url('/images/pattern-overlay.png');
/* Optional pattern */
background-image: url('/images/section-bg.jpg');
background-size: cover;
background-position: center;
text-align: center;
position: relative;
}
@ -98,7 +98,7 @@
display: inline-block;
padding: 15px 40px;
border: 1px solid #C5A059;
color: #C5A059;
color: #fff;
font-family: var(--font-inter), sans-serif;
text-transform: uppercase;
font-size: 1rem;

View File

@ -1,6 +1,9 @@
'use client'
import Image from 'next/image'
import Link from 'next/link'
import styles from './PopularDishes.module.css'
import { motion } from 'framer-motion'
const dishes = [
{
@ -25,12 +28,54 @@ const dishes = [
]
export default function PopularDishes() {
const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2,
delayChildren: 0.2
}
}
};
const cardVariants = {
hidden: { opacity: 0, y: 40 },
visible: {
opacity: 1,
y: 0,
transition: { duration: 0.6 }
}
};
return (
<section className={styles.section} id="menu">
<h2 className={styles.title}>POPULAR DISHES</h2>
<div className={styles.grid}>
<motion.h2
className={styles.title}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6 }}
>
POPULAR DISHES
</motion.h2>
<motion.div
className={styles.grid}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: "-100px" }}
variants={containerVariants}
>
{dishes.map((dish) => (
<div key={dish.id} className={styles.card}>
<motion.div
key={dish.id}
className={styles.card}
variants={cardVariants}
whileHover={{
y: -10,
transition: { duration: 0.3 }
}}
>
<div className={styles.imageContainer}>
<Image
src={dish.image}
@ -44,14 +89,20 @@ export default function PopularDishes() {
<Link href="#menu" className={styles.button}>
View More
</Link>
</div>
</motion.div>
))}
</div>
<div className={styles.viewMoreContainer}>
</motion.div>
<motion.div
className={styles.viewMoreContainer}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, delay: 0.4 }}
>
<Link href="#menu" className={styles.viewMoreButton}>
View More
</Link>
</div>
</motion.div>
</section>
)
}

View File

@ -87,6 +87,19 @@
background-color: #333;
}
.authorImageWrapper {
position: relative;
width: 100%;
height: 100%;
border-radius: 50%;
overflow: hidden;
}
.authorImage {
border-radius: 50%;
object-fit: cover;
}
.name {
font-family: var(--font-inter), sans-serif;
/* Assuming Inter or similar */
@ -129,11 +142,11 @@
}
.prevArrow {
left: -25px;
left: 0;
}
.nextArrow {
right: -25px;
right: 0;
}
/* Lanterns decoration placeholder */
@ -146,20 +159,90 @@
z-index: 5;
}
/* Responsive adjustments for different viewport sizes */
@media (max-width: 1400px) {
.sliderContainer {
padding: 0 50px;
}
.arrow {
font-size: 2.8rem;
}
}
@media (max-width: 1200px) {
.sliderContainer {
padding: 0 40px;
}
.arrow {
font-size: 2.5rem;
}
}
@media (max-width: 1024px) {
.track {
flex-direction: column;
transform: none !important;
/* Disable slide on mobile if stacking */
.sliderContainer {
padding: 0 30px;
}
.card {
flex: 0 0 100%;
max-width: 100%;
margin: 0 0 20px 0;
margin: 0;
}
/* Hide 2nd and 3rd cards on mobile, show only 1st */
.card:nth-child(2),
.card:nth-child(3) {
display: none;
}
.arrow {
display: none;
font-size: 2.2rem;
}
.prevArrow {
left: 5px;
}
.nextArrow {
right: 5px;
}
.title {
font-size: 2.5rem;
margin-bottom: 40px;
}
}
@media (max-width: 768px) {
.sliderContainer {
padding: 0 20px;
}
.arrow {
font-size: 2rem;
}
.prevArrow {
left: 0;
}
.nextArrow {
right: 0;
}
}
@media (max-width: 480px) {
.sliderContainer {
padding: 0 15px;
}
.arrow {
font-size: 1.8rem;
}
.title {
font-size: 2rem;
}
}

View File

@ -4,48 +4,47 @@ import { useState, useEffect } from 'react'
import Image from 'next/image'
import styles from './Testimonials.module.css'
// Placeholder data with 6 items
const testimonials = [
{
id: 1,
name: 'Anya Petrova, [City]',
name: 'Anya Petrova',
image: '/images/avatar-anya.png',
text: '“An unforgettable journey of flavors and warmth. The ambiance is exquisite is Turkish fusion dishes simply divine.”',
text: '"An unforgettable journey of flavors and warmth. The ambiance is exquisite and the Turkish fusion dishes are simply divine."',
rating: 5
},
{
id: 2,
name: 'Tuest Khan, [City]',
image: '/images/avatar-anya.png',
text: '“An unforgettable journey of flavors and warmth. The ambiance is exquisite is Turkish fusion dishes simply divine.”',
name: 'Tuest Khan',
image: '/images/avatar-tuest.png',
text: '"The best Turkish restaurant in town! Every dish is prepared with authentic spices and love. The staff made us feel like family."',
rating: 5
},
{
id: 3,
name: 'Tariq Name, [City]',
image: '/images/avatar-anya.png',
text: '“An unforgettable journey of flavors and warmth. The ambiene exquisite, and Turkish fusion dishes simply divine.”',
name: 'Tariq Ahmed',
image: '/images/avatar-tariq.png',
text: '"Exceptional quality and presentation. The kebabs were perfectly grilled and the baklava for dessert was heavenly!"',
rating: 5
},
{
id: 4,
name: 'Sarah Jenkins, [City]',
image: '/images/avatar-anya.png', // Reusing for now
text: '“Amazing service and beautiful atmosphere. Highly recommend the mixed grill!”',
name: 'Sarah Jenkins',
image: '/images/avatar-sarah.png',
text: '"Amazing service and beautiful atmosphere. The mixed grill platter is a must-try. Highly recommend for special occasions!"',
rating: 5
},
{
id: 5,
name: 'Michael Chen, [City]',
image: '/images/avatar-sarah.png', // Reusing for now
text: '“A hidden gem! The fusion of flavors is incredible. Will definitely come back.”',
name: 'Michael Chen',
image: '/images/avatar-michael.png',
text: '"A hidden gem! The fusion of traditional Turkish flavors with modern techniques is incredible. Will definitely come back soon."',
rating: 5
},
{
id: 6,
name: 'Emily Davis, [City]',
image: '/images/avatar-sarah.png', // Reusing for now
text: '“The staff was so welcoming and the food was absolutely delicious. 10/10 experience.”',
name: 'Emily Davis',
image: '/images/avatar-emily.png',
text: '"The staff was so welcoming and the food was absolutely delicious. From appetizers to dessert, everything was perfect. 10/10 experience!"',
rating: 5
}
]
@ -71,12 +70,6 @@ export default function Testimonials() {
}, [])
// Get the 3 visible testimonials
// Simple sliding window logic for 3 items
// Note: This simple logic assumes we stop at the end or loop.
// For a true infinite loop with 3 items visible, it's more complex.
// Here we will just slide through the window.
// Actually, let's do a simple modulo wrap for the window to always show 3.
const getVisibleTestimonials = () => {
const items = []
for (let i = 0; i < 3; i++) {
@ -90,11 +83,6 @@ export default function Testimonials() {
return (
<section className={styles.section}>
{/* Decorative Lanterns - Placeholder Image */}
{/* <div className={styles.lanterns}>
<Image src="/images/lanterns.png" alt="Lanterns" width={150} height={200} />
</div> */}
<h2 className={styles.title}>TESTIMONIALS</h2>
<div className={styles.sliderContainer}>
@ -103,11 +91,14 @@ export default function Testimonials() {
{visibleItems.map((item) => (
<div key={item.id} className={styles.card}>
<div className={styles.avatarContainer}>
{/* Using a div fallback if image is missing, or standard img tag */}
<div className={styles.avatar} style={{ overflow: 'hidden' }}>
{/* Placeholder for avatar - using different colors to distinguish if needed, or just gray */}
<div style={{ width: '100%', height: '100%', backgroundColor: '#ccc', display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#666' }}>
{item.name.charAt(0)}
<div className={styles.authorImageWrapper}>
<Image
src={item.image}
alt={item.name}
fill
className={styles.authorImage}
/>
</div>
</div>
</div>

300
src/utils/constant.ts Normal file
View File

@ -0,0 +1,300 @@
export const blogData = [
{
id: 1,
slug: 'the-art-of-turkish-tea',
title: 'The Art of Turkish Tea',
metatitle: 'The Art of Turkish Tea - Antalya Restaurant',
metadesc: 'Discover the rich history and tradition behind the perfect cup of Turkish tea at Antalya Restaurant.',
category: 'Culture',
date: 'October 15, 2025',
comments: '12 Comments',
author: 'Emre Yilmaz',
para: 'Discover the rich history and tradition behind the perfect cup of Turkish tea.',
image: '/images/dish-1.png',
imageDetail: '/images/dish-1.png',
banner: '/images/hero-1.png',
detailImage: '/images/dish-1.png',
excerpt: 'Discover the rich history and tradition behind the perfect cup of Turkish tea.',
content: `
<div class="sec-title new mt-4">
<h2>The Rich Tradition of Turkish Tea</h2>
</div>
<p>Turkish tea, known as çay, is more than just a drink; it is a symbol of hospitality and friendship. Brewed in a unique two-stacked kettle called a çaydanlık, the tea is served in small, tulip-shaped glasses to showcase its deep red color.</p>
<div class="sec-title new mt-4">
<h3>The History of Tea in Turkey</h3>
</div>
<p>While Turkey is famous for its coffee, tea has become the most consumed beverage in the country. The tea culture in Turkey began in the early 20th century when the government encouraged tea cultivation in the Black Sea region.</p>
<div class="sec-title new mt-4">
<h3>How to Brew the Perfect Turkish Tea</h3>
</div>
<ul class="list-style-two">
<li>Use a traditional çaydanlık (double teapot)</li>
<li>Boil water in the lower pot</li>
<li>Add loose tea leaves to the upper pot</li>
<li>Pour boiling water over the leaves</li>
<li>Let it steep for 10-15 minutes</li>
<li>Serve in tulip-shaped glasses</li>
</ul>
<p>At Antalya Restaurant, we serve authentic Turkish tea with every meal, continuing this beautiful tradition of hospitality.</p>
<blockquote>
<p>Tea is not just a beverage in Turkey; it's a way of life, a symbol of hospitality, and a bridge between hearts.</p>
<cite>Turkish Proverb</cite>
</blockquote>
`,
faqs: [
{
q: "What makes Turkish tea different from other teas?",
a: "Turkish tea is brewed using a special double teapot called a çaydanlık, which allows for a stronger, more flavorful brew. It's typically served in small tulip-shaped glasses and is known for its deep red color and robust taste."
},
{
q: "Can I order Turkish tea at Antalya Restaurant?",
a: "Yes! We serve authentic Turkish tea with every meal. It's complimentary with your dining experience and brewed fresh throughout the day using traditional methods."
},
{
q: "Is Turkish tea caffeinated?",
a: "Yes, Turkish tea contains caffeine as it's made from black tea leaves. However, the caffeine content can vary depending on how strong you prefer your tea."
}
]
},
{
id: 2,
slug: 'secrets-of-charcoal-grilling',
title: 'Secrets of Charcoal Grilling',
metatitle: 'Secrets of Charcoal Grilling - Antalya Restaurant',
metadesc: 'Why charcoal grilling is the heart of authentic Turkish cuisine and how we perfect it at Antalya.',
category: 'Cooking',
date: 'November 2, 2025',
comments: '8 Comments',
author: 'Chef Ahmet',
para: 'Why charcoal grilling is the heart of authentic Turkish cuisine.',
image: '/images/hero-3.png',
imageDetail: '/images/hero-3.png',
banner: '/images/hero-2.png',
detailImage: '/images/hero-3.png',
excerpt: 'Why charcoal grilling is the heart of authentic Turkish cuisine.',
content: `
<div class="sec-title new mt-4">
<h2>The Art of Charcoal Grilling</h2>
</div>
<p>The smoky flavor of Turkish kebabs comes from the traditional method of charcoal grilling. Unlike gas grills, charcoal provides an intense heat that sears the meat while locking in the juices.</p>
<div class="sec-title new mt-4">
<h3>Why Charcoal Makes a Difference</h3>
</div>
<p>Charcoal grilling isn't just about heat—it's about flavor. The smoke from natural charcoal infuses the meat with a distinctive taste that cannot be replicated with gas or electric grills.</p>
<ul class="list-style-two">
<li>Higher cooking temperatures for perfect searing</li>
<li>Natural smoke flavor enhancement</li>
<li>Traditional cooking method passed down through generations</li>
<li>Better moisture retention in the meat</li>
</ul>
<p>At Antalya, our chefs master the art of the grill to bring you the most authentic flavors of Turkey.</p>
`,
faqs: [
{
q: "What type of charcoal do you use at Antalya?",
a: "We use natural hardwood charcoal that burns hotter and cleaner than briquettes, providing the authentic smoky flavor that Turkish cuisine is known for."
},
{
q: "How long does it take to grill kebabs over charcoal?",
a: "Depending on the type of kebab, it typically takes 10-15 minutes over high heat. Our chefs monitor each skewer carefully to ensure perfect doneness."
}
]
},
{
id: 3,
slug: 'a-taste-of-sweet-legacy',
title: 'A Taste of Sweet Legacy',
metatitle: 'Turkish Desserts: A Taste of Sweet Legacy',
metadesc: 'Exploring the delicate layers of Baklava and other Turkish delights at Antalya Restaurant.',
category: 'Desserts',
date: 'November 20, 2025',
comments: '15 Comments',
author: 'Leyla Demir',
para: 'Exploring the delicate layers of Baklava and other Turkish delights.',
image: '/images/dish-2.png',
imageDetail: '/images/dish-2.png',
banner: '/images/hero-1.png',
detailImage: '/images/dish-2.png',
excerpt: 'Exploring the delicate layers of Baklava and other Turkish delights.',
content: `
<div class="sec-title new mt-4">
<h2>The Sweet World of Turkish Desserts</h2>
</div>
<p>Baklava is perhaps the most famous Turkish dessert, but there is so much more to explore. From the creamy texture of Sütlaç (rice pudding) to the syrupy goodness of Künefe, Turkish desserts are a celebration of sweetness.</p>
<div class="sec-title new mt-4">
<h3>Popular Turkish Desserts</h3>
</div>
<p><strong>Baklava</strong><br>
Layers of thin filo pastry filled with chopped nuts and sweetened with syrup or honey.</p>
<p><strong>Künefe</strong><br>
Shredded pastry soaked in sweet syrup, layered with cheese, and served hot.</p>
<p><strong>Turkish Delight (Lokum)</strong><br>
Soft, chewy confection dusted with powdered sugar, available in various flavors.</p>
<p>In this post, we dive into the history of these treats and share some secrets on how we achieve the perfect balance of flaky pastry and rich filling.</p>
`,
faqs: [
{
q: "Is baklava made fresh at Antalya?",
a: "Yes! Our baklava is made fresh daily using traditional recipes and the finest ingredients, including premium pistachios and honey."
},
{
q: "Are Turkish desserts very sweet?",
a: "Turkish desserts are known for their sweetness, but we balance the flavors carefully. Many of our desserts are served with unsweetened Turkish tea to complement the sweetness."
},
{
q: "Can I order desserts for takeout?",
a: "Absolutely! All our desserts are available for takeout. We package them carefully to maintain their freshness and quality."
}
]
}
];
export const galleryData = [
{ id: 1, src: '/images/dish-1.png', category: 'Food', alt: 'Delicious Kebab' },
{ id: 2, src: '/images/dish-2.png', category: 'Food', alt: 'Sweet Baklava' },
{ id: 3, src: '/images/hero-1.png', category: 'Interior', alt: 'Restaurant Ambience' },
{ id: 4, src: '/images/hero-2.png', category: 'Interior', alt: 'Dining Area' },
{ id: 5, src: '/images/hero-3.png', category: 'Food', alt: 'Grilled Meat' },
{ id: 6, src: '/images/restaurant-interior.png', category: 'Interior', alt: 'Cozy Seating' },
];
export const menuData = [
{
category: 'Starters',
image: '/images/dish-1.png',
items: [
{ name: 'Hummus', price: '$8', description: 'Creamy chickpea dip with tahini, lemon, and garlic.', image: '/images/dish-1.png' },
{ name: 'Ezme', price: '$7', description: 'Spicy tomato and pepper salad with walnuts.', image: '/images/dish-2.png' },
{ name: 'Cacik', price: '$6', description: 'Yogurt with cucumber, garlic, and mint.', image: '/images/hero-1.png' }
]
},
{
category: 'Main Courses',
image: '/images/hero-3.png',
items: [
{ name: 'Adana Kebab', price: '$18', description: 'Spicy minced lamb grilled on a skewer.', image: '/images/hero-3.png' },
{ name: 'Lamb Chops', price: '$24', description: 'Tender lamb chops grilled to perfection.', image: '/images/dish-1.png' },
{ name: 'Chicken Shish', price: '$16', description: 'Marinated chicken cubes grilled on skewers.', image: '/images/hero-2.png' }
]
},
{
category: 'Desserts',
image: '/images/dish-2.png',
items: [
{ name: 'Baklava', price: '$9', description: 'Layers of filo pastry filled with pistachios and syrup.', image: '/images/dish-2.png' },
{ name: 'Künefe', price: '$10', description: 'Shredded pastry with cheese, soaked in syrup.', image: '/images/dish-1.png' },
{ name: 'Turkish Delight', price: '$5', description: 'Assorted traditional gelatin sweets.', image: '/images/hero-1.png' }
]
}
];
export const testimonialData = [
{
id: 1,
name: 'Sarah Johnson',
role: 'Food Critic',
image: '/images/avatar-anya.png',
text: 'The most authentic Turkish cuisine I have experienced outside of Turkey. Every dish is a masterpiece!'
},
{
id: 2,
name: 'Michael Chen',
role: 'Regular Customer',
image: '/images/avatar-emily.png',
text: 'Antalya has become my favorite restaurant. The atmosphere is warm and the food is consistently excellent.'
},
{
id: 3,
name: 'Emma Williams',
role: 'Travel Blogger',
image: '/images/avatar-emily.png',
text: 'A hidden gem! The charcoal-grilled kebabs are to die for, and the service is impeccable.'
},
{
id: 4,
name: 'Emma Williams',
role: 'Travel Blogger',
image: '/images/avatar-emily.png',
text: 'A hidden gem! The charcoal-grilled kebabs are to die for, and the service is impeccable.'
},
{
id: 5,
name: 'Emma Williams',
role: 'Travel Blogger',
image: '/images/avatar-emily.png',
text: 'A hidden gem! The charcoal-grilled kebabs are to die for, and the service is impeccable.'
},
{
id: 6,
name: 'Emma Williams',
role: 'Travel Blogger',
image: '/images/avatar-emily.png',
text: 'A hidden gem! The charcoal-grilled kebabs are to die for, and the service is impeccable.'
}
];
export const featuresData = [
{
id: 1,
title: 'Fresh Ingredients',
description: 'We source only the finest, freshest ingredients from local suppliers daily.',
image: '/images/dish-1.png'
},
{
id: 2,
title: 'Expert Chefs',
description: 'Our chefs have decades of experience in traditional Turkish cuisine.',
image: '/images/hero-3.png'
},
{
id: 3,
title: 'Authentic Recipes',
description: 'Traditional family recipes passed down through generations.',
image: '/images/dish-2.png'
}
];
export const ctaData = {
title: 'Experience the Flavors of Turkey',
subtitle: 'Book your table today and embark on a culinary journey',
buttonText: 'Reserve Now',
buttonLink: '/contact',
backgroundImage: '/images/hero-1.png'
};
export const aboutFaqData = [
{
q: "What makes Antalya Restaurant authentic?",
a: "We use traditional Turkish recipes passed down through generations, authentic cooking methods like charcoal grilling, and import specialty ingredients directly from Turkey to ensure the most authentic dining experience."
},
{
q: "Do you offer vegetarian and vegan options?",
a: "Yes! Turkish cuisine has a rich variety of vegetarian dishes. We offer numerous vegetarian options including mezze platters, stuffed vegetables, lentil soups, and fresh salads. Please ask our staff about vegan modifications."
},
{
q: "Can you accommodate large groups or private events?",
a: "Absolutely! We can accommodate groups of various sizes and offer private dining options for special occasions. Please contact us in advance to discuss your requirements and make arrangements."
},
{
q: "What are your most popular dishes?",
a: "Our signature dishes include Adana Kebab, Lamb Chops, handmade Baklava, and our traditional Turkish tea. The charcoal-grilled meats and fresh mezze platters are customer favorites."
},
{
q: "Do you take reservations?",
a: "Yes, we highly recommend making reservations, especially for weekends and special occasions. You can book a table through our website, by phone, or by visiting us in person."
}
];