commit 95733d99d43122e8c076c9e72c64f6c699d4f852 Author: Alireza Hassani Date: Thu Sep 3 23:40:37 2026 +0330 Initial commit of the Mado Motor public website. diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..e10db26 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://api.madomotor.com:3001/api/v1 +VITE_DASHBOARD_URL=http://manage.madomotor.com:5175 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f57fea2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,28 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +.env +.env.* +!.env.example diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..b0a666b --- /dev/null +++ b/README.md @@ -0,0 +1,16 @@ +# React + TypeScript + Vite + +## Local development + +```bash +npm install +npm run dev +``` + +Open: + +- http://madomotor.com:5174 +- http://www.madomotor.com:5174 +- http://localhost:5174 + +Local domains are in `/etc/hosts` (`../scripts/setup-hosts.sh`). diff --git a/index.html b/index.html new file mode 100644 index 0000000..54b1c90 --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + + + خرید موتور سیکلت برقی از کارخانه | سایت رسمی مادو موتور + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8746ebb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1353 @@ +{ + "name": "website", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "website", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-router-dom": "^7.18.3" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.1.0", + "oxlint": "^1.79.0", + "typescript": "~6.0.2", + "vite": "^8.2.2" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.148.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", + "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.81.0.tgz", + "integrity": "sha512-IcCRsXiedJoJopY6mpZUBEeVFsUrutmrG7dZ87zMuKJlhg70Ora9bBl1WcCxZQtyI10YpnVdEso5oCg7YcfSHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.81.0.tgz", + "integrity": "sha512-GRrIPyTGVhx3L3h+0T5xT2A0jFAcdPv4+IfuXpGDLIdl6XeYhgg/zw72A5ILZoUgRqZuM8F1y+V/gfDriXSxzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.81.0.tgz", + "integrity": "sha512-qNQ9tXRgLuKbqSV1S2h9h4KPHjbovO7RRR2/enUOtHzTkFZ7B9X5zqqHJua8dRyc7dBy7Aoyq5pqTSLFVcAzGQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.81.0.tgz", + "integrity": "sha512-q0QTm32jWga2Gv4j7IaVZN0jYMi9UV73sWVgFtDA4iIfqwMCLLZ3ve+9KwfYtsaKZSgQhmPaogeZWqDZpcY1Pw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.81.0.tgz", + "integrity": "sha512-/+8wVWDXEC7wHVAhOc59Fw/SkMc1arLkFD8iQCaSsmzenK1X4doFqquL9H1wrtGUzaiycVqkf/sSpcILK6W1UA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.81.0.tgz", + "integrity": "sha512-4xt422FEgioRq9hAL4Tq7fujGUWnc8z1BJ+Oi8RN8vB8axaP+sdK6a2xdlcQCCYnJg9QMuMFS0AucuIFx/EacA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.81.0.tgz", + "integrity": "sha512-u3vna8KdGplH4DRCW9K54D68fcMo7IxVrkCJWwXnIhwtBdnDnYrmzOUA/XjmBlPpcLsgw9Z5BNdY4za9+Dj+MQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.81.0.tgz", + "integrity": "sha512-3j9k+gsYsE7nv71GWotXsqsa2l9/aJenD7dVHNt/CBvsb0SgRjSMnHFeP59IXUAl1wvVFhqGl2wJNMwWU3UBlA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.81.0.tgz", + "integrity": "sha512-k5iAp3dNxW0/uDCBY+WSm8jKB2szu7SkEQZdgRRpDXvuDd69vvDcqhB3A/pWCfCwXyenjNjFn9Td1fVoyAc+Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.81.0.tgz", + "integrity": "sha512-TFqLja3uYmVSte6nof9GWrex9Z8WgdZrNiLC6Te5rXGDqXB2y4j/26iFhwosXiAFqDhE9JJVuuCkDKLwptTn1g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.81.0.tgz", + "integrity": "sha512-UEcySvGS0NOVo7h7n7CYyJL9+6gFAh7Zc/ToDXVScFvzHSTIxtzkMVU30rmQ6+nQ1LF+UdiRDdJajpDu+OylLg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.81.0.tgz", + "integrity": "sha512-H+diDbhD00+wI1IRP8Kz88x/lat+DgtoBJzoTthS16xkTJGNaEkfb8gzmd1rzc/2uDQQMl7GNl+JFUacVeWxIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.81.0.tgz", + "integrity": "sha512-8znJ/5TekjOKg1j1Acho4PJMdiAHLtlcXuWEiipOhAMV6rQcXdmDdXCbheyDczN6TjBwiNfjcP81k4AthrKRzw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.81.0.tgz", + "integrity": "sha512-Q2Wj70yFsvn5QjlmifFzbj4H+kJy53bwqc41o1fzoM7MpLV1NIbhg/LpWXRfC6KOkSAdUx1Wd8VJsdPmhp/HRA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.81.0.tgz", + "integrity": "sha512-cPInHp/ddEe5qkyK2IiyQ8Q3Mp2oLLEhhsGgTK2oZx4L6+llGam1H1yBvJZ7qHfOXj8N3hxBS8sj4tO+gtFlIg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.81.0.tgz", + "integrity": "sha512-0CQxSX4ajqm07AHBf5U33qQzXKdd7wtq/oTL/7vpY6RNNuxrRi8W4bqUV1Jyu/vj+9KmxQyDhxfeVX1nQL6kfg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.81.0.tgz", + "integrity": "sha512-l0hbeISm9673hVrrQU8j/p2M7YH9Ouoj7p7E/QM55NTrKVLP+P3PF8hLu+OY+x0VtGRW+ggiQKZqmdYps9H+TA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.81.0.tgz", + "integrity": "sha512-ksqPP5jbFXcYreEQ7zdJh06rJQBymCTyGRCdaXjfcf2aG4f8KxUWY5wcgYHmaTK+FJ4bPG5sUAdOX+6trnH1JA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.81.0.tgz", + "integrity": "sha512-IZuUCwGw9emG5JtCp+fYGB+Z4OWEoeEcM8R5BA1pYw63/ieYFVdcU2ylxTpHbVHSenZnsYE+ZZ20uHAJszQ4cA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", + "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", + "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", + "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", + "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", + "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", + "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", + "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", + "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", + "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", + "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", + "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", + "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", + "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", + "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", + "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.18", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz", + "integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-bzczc+lpPxs7hFBkaOht7j/zVcSZVBGZZajtQoMhTDTrwVCcNa+bd0+LfhDXhnEnT4OHHFBE2jLTenVL+UzF7Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.1.1.tgz", + "integrity": "sha512-yxLaQV9gkhS8ezJqCM6+ndU7mDY6gqAg75NQ+0IjwEI8IYOmQCgkRwHKVSfWXW076DsqMo0Dk+0FK1U+M5RgFw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "oxc-transform-react": "^0.145.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "oxc-transform-react": { + "optional": true + } + } + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/oxlint": { + "version": "1.81.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.81.0.tgz", + "integrity": "sha512-HyrJYqeoOCL0iqaLEzGewGT48ZX99P3hxYh8udAF9RGGIghSamkXE4ClUyBpEDNqasamThgmlPbuMOe7SAZmHg==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.81.0", + "@oxlint/binding-android-arm64": "1.81.0", + "@oxlint/binding-darwin-arm64": "1.81.0", + "@oxlint/binding-darwin-x64": "1.81.0", + "@oxlint/binding-freebsd-x64": "1.81.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.81.0", + "@oxlint/binding-linux-arm-musleabihf": "1.81.0", + "@oxlint/binding-linux-arm64-gnu": "1.81.0", + "@oxlint/binding-linux-arm64-musl": "1.81.0", + "@oxlint/binding-linux-ppc64-gnu": "1.81.0", + "@oxlint/binding-linux-riscv64-gnu": "1.81.0", + "@oxlint/binding-linux-riscv64-musl": "1.81.0", + "@oxlint/binding-linux-s390x-gnu": "1.81.0", + "@oxlint/binding-linux-x64-gnu": "1.81.0", + "@oxlint/binding-linux-x64-musl": "1.81.0", + "@oxlint/binding-openharmony-arm64": "1.81.0", + "@oxlint/binding-win32-arm64-msvc": "1.81.0", + "@oxlint/binding-win32-ia32-msvc": "1.81.0", + "@oxlint/binding-win32-x64-msvc": "1.81.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.27", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.27.tgz", + "integrity": "sha512-79Iho8QeYyooJ8e9lCRyTVlyTAkS/kXBYKff6TMzS3kEWGQ8Ds5UEtXpGrSUDLUWok6QTvxeYy0GO8fopHnaSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/react": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.8.tgz", + "integrity": "sha512-PWaYA1L/q9u2u7xYQi+Y3L3Yfnie7XyLeaJICV1MGD6LprsBxcAqGjYyr0eY3p+QdsA+x/Irkt4Qif8D63+Sbw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.8", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz", + "integrity": "sha512-rVprimfGBG3DR+Tq0IQG2DT5PxKth1WIGDmj5yPmlzr4YBe7uyE+Du4oVqTDXZSHGGGXRtTJEGSSePyQCMBglQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.8" + } + }, + "node_modules/react-router": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.3.tgz", + "integrity": "sha512-gyXgtdr5uACJ5b1Q4udzjVV+tb/rlHIMJKuJ0e89R4Kzgz47z/rgP0dIKxktqIEUhDHluGTPJJH/wRha7CyqsA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.3.tgz", + "integrity": "sha512-ytVbyBBM7vMfRCam25r0WMhSVSom909A8p+8m0/f1w853dz/xfFu6etAT2SEbVoSnI+ZoPRDqIsQXVT89gp7kg==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.3" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/rolldown": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", + "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.148.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.7", + "@rolldown/binding-android-arm64": "1.2.7", + "@rolldown/binding-darwin-arm64": "1.2.7", + "@rolldown/binding-darwin-x64": "1.2.7", + "@rolldown/binding-freebsd-x64": "1.2.7", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", + "@rolldown/binding-linux-arm64-gnu": "1.2.7", + "@rolldown/binding-linux-arm64-musl": "1.2.7", + "@rolldown/binding-linux-ppc64-gnu": "1.2.7", + "@rolldown/binding-linux-s390x-gnu": "1.2.7", + "@rolldown/binding-linux-x64-gnu": "1.2.7", + "@rolldown/binding-linux-x64-musl": "1.2.7", + "@rolldown/binding-openharmony-arm64": "1.2.7", + "@rolldown/binding-win32-arm64-msvc": "1.2.7", + "@rolldown/binding-win32-x64-msvc": "1.2.7" + } + }, + "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/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "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", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", + "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.26", + "rolldown": "~1.2.4", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6c1d16e --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "madomotor-website", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-router-dom": "^7.18.3" + }, + "devDependencies": { + "@types/node": "^24.13.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", + "@vitejs/plugin-react": "^6.1.0", + "oxlint": "^1.79.0", + "typescript": "~6.0.2", + "vite": "^8.2.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..fe94b76 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons.svg b/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/logo.png b/public/logo.png new file mode 100644 index 0000000..7985a19 Binary files /dev/null and b/public/logo.png differ diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..d07c10b --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,4 @@ +User-agent: * +Allow: / + +Sitemap: https://madomotor.ir/sitemap.xml diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..404ba1a --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,49 @@ +import { Navigate, Route, Routes } from 'react-router-dom' +import Header from './components/Header' +import Footer from './components/Footer' +import HomePage from './pages/HomePage' +import ProductsPage from './pages/ProductsPage' +import ProductPage from './pages/ProductPage' +import BlogListPage from './pages/BlogListPage' +import BlogPostPage from './pages/BlogPostPage' +import DealersPage from './pages/DealersPage' +import RepresentationRequestPage from './pages/RepresentationRequestPage' +import LandingModelPage from './pages/LandingModelPage' +import AboutPage from './pages/AboutPage' +import ContactPage from './pages/ContactPage' +import NotFoundPage from './pages/NotFoundPage' +import { paths } from './lib/paths' + +export default function App() { + return ( +
+
+
+ + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + + } /> + } /> + } /> + } /> + + } /> + } /> + +
+
+
+ ) +} diff --git a/src/assets/fonts/iranyekan/eot/iranyekanwebbold.eot b/src/assets/fonts/iranyekan/eot/iranyekanwebbold.eot new file mode 100644 index 0000000..16e8fbc Binary files /dev/null and b/src/assets/fonts/iranyekan/eot/iranyekanwebbold.eot differ diff --git a/src/assets/fonts/iranyekan/eot/iranyekanweblight.eot b/src/assets/fonts/iranyekan/eot/iranyekanweblight.eot new file mode 100644 index 0000000..512a46a Binary files /dev/null and b/src/assets/fonts/iranyekan/eot/iranyekanweblight.eot differ diff --git a/src/assets/fonts/iranyekan/eot/iranyekanwebmedium.eot b/src/assets/fonts/iranyekan/eot/iranyekanwebmedium.eot new file mode 100644 index 0000000..e2d5f34 Binary files /dev/null and b/src/assets/fonts/iranyekan/eot/iranyekanwebmedium.eot differ diff --git a/src/assets/fonts/iranyekan/ttf/iranyekanwebbold.ttf b/src/assets/fonts/iranyekan/ttf/iranyekanwebbold.ttf new file mode 100644 index 0000000..1c6bbd4 Binary files /dev/null and b/src/assets/fonts/iranyekan/ttf/iranyekanwebbold.ttf differ diff --git a/src/assets/fonts/iranyekan/ttf/iranyekanweblight.ttf b/src/assets/fonts/iranyekan/ttf/iranyekanweblight.ttf new file mode 100644 index 0000000..9e81193 Binary files /dev/null and b/src/assets/fonts/iranyekan/ttf/iranyekanweblight.ttf differ diff --git a/src/assets/fonts/iranyekan/ttf/iranyekanwebmedium.ttf b/src/assets/fonts/iranyekan/ttf/iranyekanwebmedium.ttf new file mode 100644 index 0000000..4dc2ad7 Binary files /dev/null and b/src/assets/fonts/iranyekan/ttf/iranyekanwebmedium.ttf differ diff --git a/src/assets/fonts/iranyekan/woff/iranyekanwebbold.woff b/src/assets/fonts/iranyekan/woff/iranyekanwebbold.woff new file mode 100644 index 0000000..7707c6f Binary files /dev/null and b/src/assets/fonts/iranyekan/woff/iranyekanwebbold.woff differ diff --git a/src/assets/fonts/iranyekan/woff/iranyekanweblight.woff b/src/assets/fonts/iranyekan/woff/iranyekanweblight.woff new file mode 100644 index 0000000..9e6d42c Binary files /dev/null and b/src/assets/fonts/iranyekan/woff/iranyekanweblight.woff differ diff --git a/src/assets/fonts/iranyekan/woff/iranyekanwebmedium.woff b/src/assets/fonts/iranyekan/woff/iranyekanwebmedium.woff new file mode 100644 index 0000000..8fe8e40 Binary files /dev/null and b/src/assets/fonts/iranyekan/woff/iranyekanwebmedium.woff differ diff --git a/src/assets/fonts/montserrat/Montserrat-Light.woff b/src/assets/fonts/montserrat/Montserrat-Light.woff new file mode 100644 index 0000000..ff7cd8f Binary files /dev/null and b/src/assets/fonts/montserrat/Montserrat-Light.woff differ diff --git a/src/assets/fonts/montserrat/Montserrat-Regular.woff b/src/assets/fonts/montserrat/Montserrat-Regular.woff new file mode 100644 index 0000000..e77f1bc Binary files /dev/null and b/src/assets/fonts/montserrat/Montserrat-Regular.woff differ diff --git a/src/assets/fonts/montserrat/montserrat-bold.woff b/src/assets/fonts/montserrat/montserrat-bold.woff new file mode 100644 index 0000000..f2f7a8f Binary files /dev/null and b/src/assets/fonts/montserrat/montserrat-bold.woff differ diff --git a/src/assets/hero.png b/src/assets/hero.png new file mode 100644 index 0000000..02251f4 Binary files /dev/null and b/src/assets/hero.png differ diff --git a/src/assets/images/150.png b/src/assets/images/150.png new file mode 100644 index 0000000..a1bc604 Binary files /dev/null and b/src/assets/images/150.png differ diff --git a/src/assets/images/36month.png b/src/assets/images/36month.png new file mode 100644 index 0000000..922ef03 Binary files /dev/null and b/src/assets/images/36month.png differ diff --git a/src/assets/images/Funnel.png b/src/assets/images/Funnel.png new file mode 100644 index 0000000..d6975da Binary files /dev/null and b/src/assets/images/Funnel.png differ diff --git a/src/assets/images/about-pattern.png b/src/assets/images/about-pattern.png new file mode 100644 index 0000000..3e32268 Binary files /dev/null and b/src/assets/images/about-pattern.png differ diff --git a/src/assets/images/aboutus-motor.png b/src/assets/images/aboutus-motor.png new file mode 100644 index 0000000..976b1c7 Binary files /dev/null and b/src/assets/images/aboutus-motor.png differ diff --git a/src/assets/images/back.png b/src/assets/images/back.png new file mode 100644 index 0000000..99ec8fb Binary files /dev/null and b/src/assets/images/back.png differ diff --git a/src/assets/images/bg.jpg b/src/assets/images/bg.jpg new file mode 100644 index 0000000..1b35423 Binary files /dev/null and b/src/assets/images/bg.jpg differ diff --git a/src/assets/images/bog-logo.png b/src/assets/images/bog-logo.png new file mode 100644 index 0000000..2f547b0 Binary files /dev/null and b/src/assets/images/bog-logo.png differ diff --git a/src/assets/images/casket-hat.svg b/src/assets/images/casket-hat.svg new file mode 100644 index 0000000..0d7616d --- /dev/null +++ b/src/assets/images/casket-hat.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/chat.svg b/src/assets/images/chat.svg new file mode 100644 index 0000000..69f6224 --- /dev/null +++ b/src/assets/images/chat.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/cities/bandare-abbas.jpg b/src/assets/images/cities/bandare-abbas.jpg new file mode 100644 index 0000000..7635916 Binary files /dev/null and b/src/assets/images/cities/bandare-abbas.jpg differ diff --git a/src/assets/images/cities/booshehr.jpg b/src/assets/images/cities/booshehr.jpg new file mode 100644 index 0000000..faa3933 Binary files /dev/null and b/src/assets/images/cities/booshehr.jpg differ diff --git a/src/assets/images/cities/gorgan.jpg b/src/assets/images/cities/gorgan.jpg new file mode 100644 index 0000000..3e6e8d3 Binary files /dev/null and b/src/assets/images/cities/gorgan.jpg differ diff --git a/src/assets/images/cities/isfahan.png b/src/assets/images/cities/isfahan.png new file mode 100644 index 0000000..764e97e Binary files /dev/null and b/src/assets/images/cities/isfahan.png differ diff --git a/src/assets/images/cities/kerman.jpg b/src/assets/images/cities/kerman.jpg new file mode 100644 index 0000000..81ca7f0 Binary files /dev/null and b/src/assets/images/cities/kerman.jpg differ diff --git a/src/assets/images/cities/kish.jpg b/src/assets/images/cities/kish.jpg new file mode 100644 index 0000000..417a9e9 Binary files /dev/null and b/src/assets/images/cities/kish.jpg differ diff --git a/src/assets/images/cities/mashhad.jpg b/src/assets/images/cities/mashhad.jpg new file mode 100644 index 0000000..92b54b9 Binary files /dev/null and b/src/assets/images/cities/mashhad.jpg differ diff --git a/src/assets/images/cities/mazandaran.jpg b/src/assets/images/cities/mazandaran.jpg new file mode 100644 index 0000000..772d23b Binary files /dev/null and b/src/assets/images/cities/mazandaran.jpg differ diff --git a/src/assets/images/cities/qeshm.jpg b/src/assets/images/cities/qeshm.jpg new file mode 100644 index 0000000..f28478f Binary files /dev/null and b/src/assets/images/cities/qeshm.jpg differ diff --git a/src/assets/images/cities/qom.jpg b/src/assets/images/cities/qom.jpg new file mode 100644 index 0000000..0be25a4 Binary files /dev/null and b/src/assets/images/cities/qom.jpg differ diff --git a/src/assets/images/cities/rasht.png b/src/assets/images/cities/rasht.png new file mode 100644 index 0000000..ea93f91 Binary files /dev/null and b/src/assets/images/cities/rasht.png differ diff --git a/src/assets/images/cities/shiraz.jpg b/src/assets/images/cities/shiraz.jpg new file mode 100644 index 0000000..ebaf5f4 Binary files /dev/null and b/src/assets/images/cities/shiraz.jpg differ diff --git a/src/assets/images/cities/tabriz.jpg b/src/assets/images/cities/tabriz.jpg new file mode 100644 index 0000000..b5bcc33 Binary files /dev/null and b/src/assets/images/cities/tabriz.jpg differ diff --git a/src/assets/images/cities/tehran.jpg b/src/assets/images/cities/tehran.jpg new file mode 100644 index 0000000..2403b3b Binary files /dev/null and b/src/assets/images/cities/tehran.jpg differ diff --git a/src/assets/images/cities/urmia.jpg b/src/assets/images/cities/urmia.jpg new file mode 100644 index 0000000..3ad50f1 Binary files /dev/null and b/src/assets/images/cities/urmia.jpg differ diff --git a/src/assets/images/cities/yazd.jpg b/src/assets/images/cities/yazd.jpg new file mode 100644 index 0000000..9370d1e Binary files /dev/null and b/src/assets/images/cities/yazd.jpg differ diff --git a/src/assets/images/comments.png b/src/assets/images/comments.png new file mode 100644 index 0000000..c8ac8f6 Binary files /dev/null and b/src/assets/images/comments.png differ diff --git a/src/assets/images/confirmation.png b/src/assets/images/confirmation.png new file mode 100644 index 0000000..5a27c25 Binary files /dev/null and b/src/assets/images/confirmation.png differ diff --git a/src/assets/images/gray-rectangle.png b/src/assets/images/gray-rectangle.png new file mode 100644 index 0000000..c3cb48c Binary files /dev/null and b/src/assets/images/gray-rectangle.png differ diff --git a/src/assets/images/guarantee-typo.png b/src/assets/images/guarantee-typo.png new file mode 100644 index 0000000..64806ea Binary files /dev/null and b/src/assets/images/guarantee-typo.png differ diff --git a/src/assets/images/icons/1.png b/src/assets/images/icons/1.png new file mode 100644 index 0000000..6168d01 Binary files /dev/null and b/src/assets/images/icons/1.png differ diff --git a/src/assets/images/icons/2.png b/src/assets/images/icons/2.png new file mode 100644 index 0000000..71e5fda Binary files /dev/null and b/src/assets/images/icons/2.png differ diff --git a/src/assets/images/icons/3.png b/src/assets/images/icons/3.png new file mode 100644 index 0000000..2a1d481 Binary files /dev/null and b/src/assets/images/icons/3.png differ diff --git a/src/assets/images/icons/4.png b/src/assets/images/icons/4.png new file mode 100644 index 0000000..11d5b91 Binary files /dev/null and b/src/assets/images/icons/4.png differ diff --git a/src/assets/images/icons/angle-down.svg b/src/assets/images/icons/angle-down.svg new file mode 100644 index 0000000..b2ec534 --- /dev/null +++ b/src/assets/images/icons/angle-down.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/assets/images/icons/angle-left-2.svg b/src/assets/images/icons/angle-left-2.svg new file mode 100644 index 0000000..8ce5173 --- /dev/null +++ b/src/assets/images/icons/angle-left-2.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/angle-left.svg b/src/assets/images/icons/angle-left.svg new file mode 100644 index 0000000..48bdd4d --- /dev/null +++ b/src/assets/images/icons/angle-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/arrow-down.svg b/src/assets/images/icons/arrow-down.svg new file mode 100644 index 0000000..cb41cf4 --- /dev/null +++ b/src/assets/images/icons/arrow-down.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/arrow-left.svg b/src/assets/images/icons/arrow-left.svg new file mode 100644 index 0000000..1e283e4 --- /dev/null +++ b/src/assets/images/icons/arrow-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/cart.svg b/src/assets/images/icons/cart.svg new file mode 100644 index 0000000..798baf7 --- /dev/null +++ b/src/assets/images/icons/cart.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/src/assets/images/icons/eitaa.png b/src/assets/images/icons/eitaa.png new file mode 100644 index 0000000..ec30a75 Binary files /dev/null and b/src/assets/images/icons/eitaa.png differ diff --git a/src/assets/images/icons/fast-charge.svg b/src/assets/images/icons/fast-charge.svg new file mode 100644 index 0000000..9b7e73f --- /dev/null +++ b/src/assets/images/icons/fast-charge.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/images/icons/home.svg b/src/assets/images/icons/home.svg new file mode 100644 index 0000000..423d6f2 --- /dev/null +++ b/src/assets/images/icons/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/icons/instagram.png b/src/assets/images/icons/instagram.png new file mode 100644 index 0000000..f2ae6d4 Binary files /dev/null and b/src/assets/images/icons/instagram.png differ diff --git a/src/assets/images/icons/left-white.png b/src/assets/images/icons/left-white.png new file mode 100644 index 0000000..37545e8 Binary files /dev/null and b/src/assets/images/icons/left-white.png differ diff --git a/src/assets/images/icons/motor-icon.png b/src/assets/images/icons/motor-icon.png new file mode 100644 index 0000000..70937ff Binary files /dev/null and b/src/assets/images/icons/motor-icon.png differ diff --git a/src/assets/images/icons/telegram.png b/src/assets/images/icons/telegram.png new file mode 100644 index 0000000..284482d Binary files /dev/null and b/src/assets/images/icons/telegram.png differ diff --git a/src/assets/images/icons/user.svg b/src/assets/images/icons/user.svg new file mode 100644 index 0000000..7af593f --- /dev/null +++ b/src/assets/images/icons/user.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/assets/images/icons/whatsapp.png b/src/assets/images/icons/whatsapp.png new file mode 100644 index 0000000..63cad5f Binary files /dev/null and b/src/assets/images/icons/whatsapp.png differ diff --git a/src/assets/images/image-51.png b/src/assets/images/image-51.png new file mode 100644 index 0000000..fbd2d01 Binary files /dev/null and b/src/assets/images/image-51.png differ diff --git a/src/assets/images/info.png b/src/assets/images/info.png new file mode 100644 index 0000000..78baec2 Binary files /dev/null and b/src/assets/images/info.png differ diff --git a/src/assets/images/isfahan.png b/src/assets/images/isfahan.png new file mode 100644 index 0000000..2d17807 Binary files /dev/null and b/src/assets/images/isfahan.png differ diff --git a/src/assets/images/login.png b/src/assets/images/login.png new file mode 100644 index 0000000..7aecb00 Binary files /dev/null and b/src/assets/images/login.png differ diff --git a/src/assets/images/logo-1.png b/src/assets/images/logo-1.png new file mode 100644 index 0000000..fece14d Binary files /dev/null and b/src/assets/images/logo-1.png differ diff --git a/src/assets/images/logo.png b/src/assets/images/logo.png new file mode 100644 index 0000000..7985a19 Binary files /dev/null and b/src/assets/images/logo.png differ diff --git a/src/assets/images/mado-130.png b/src/assets/images/mado-130.png new file mode 100644 index 0000000..1798d89 Binary files /dev/null and b/src/assets/images/mado-130.png differ diff --git a/src/assets/images/mado-130/battery-130.png b/src/assets/images/mado-130/battery-130.png new file mode 100644 index 0000000..350d541 Binary files /dev/null and b/src/assets/images/mado-130/battery-130.png differ diff --git a/src/assets/images/mado-130/battery-icon.png b/src/assets/images/mado-130/battery-icon.png new file mode 100644 index 0000000..ec8cc2c Binary files /dev/null and b/src/assets/images/mado-130/battery-icon.png differ diff --git a/src/assets/images/mado-130/black-white.png b/src/assets/images/mado-130/black-white.png new file mode 100644 index 0000000..11ab43d Binary files /dev/null and b/src/assets/images/mado-130/black-white.png differ diff --git a/src/assets/images/mado-130/blue-130.png b/src/assets/images/mado-130/blue-130.png new file mode 100644 index 0000000..ca52ce1 Binary files /dev/null and b/src/assets/images/mado-130/blue-130.png differ diff --git a/src/assets/images/mado-130/colors/black.png b/src/assets/images/mado-130/colors/black.png new file mode 100644 index 0000000..0a86329 Binary files /dev/null and b/src/assets/images/mado-130/colors/black.png differ diff --git a/src/assets/images/mado-130/colors/cyan-white.png b/src/assets/images/mado-130/colors/cyan-white.png new file mode 100644 index 0000000..16357a2 Binary files /dev/null and b/src/assets/images/mado-130/colors/cyan-white.png differ diff --git a/src/assets/images/mado-130/colors/cyan.png b/src/assets/images/mado-130/colors/cyan.png new file mode 100644 index 0000000..743358b Binary files /dev/null and b/src/assets/images/mado-130/colors/cyan.png differ diff --git a/src/assets/images/mado-130/colors/pink.png b/src/assets/images/mado-130/colors/pink.png new file mode 100644 index 0000000..c8da658 Binary files /dev/null and b/src/assets/images/mado-130/colors/pink.png differ diff --git a/src/assets/images/mado-130/colors/red.png b/src/assets/images/mado-130/colors/red.png new file mode 100644 index 0000000..afafb5c Binary files /dev/null and b/src/assets/images/mado-130/colors/red.png differ diff --git a/src/assets/images/mado-130/environment.png b/src/assets/images/mado-130/environment.png new file mode 100644 index 0000000..2d59b6e Binary files /dev/null and b/src/assets/images/mado-130/environment.png differ diff --git a/src/assets/images/mado-130/litiomi.png b/src/assets/images/mado-130/litiomi.png new file mode 100644 index 0000000..99bd057 Binary files /dev/null and b/src/assets/images/mado-130/litiomi.png differ diff --git a/src/assets/images/mado-130/price-pic.png b/src/assets/images/mado-130/price-pic.png new file mode 100644 index 0000000..9bf8842 Binary files /dev/null and b/src/assets/images/mado-130/price-pic.png differ diff --git a/src/assets/images/mado-130/slider.png b/src/assets/images/mado-130/slider.png new file mode 100644 index 0000000..8bbd1fd Binary files /dev/null and b/src/assets/images/mado-130/slider.png differ diff --git a/src/assets/images/mado-130/weight-icon.svg b/src/assets/images/mado-130/weight-icon.svg new file mode 100644 index 0000000..450ec06 --- /dev/null +++ b/src/assets/images/mado-130/weight-icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/assets/images/mado-230-bg-1.png b/src/assets/images/mado-230-bg-1.png new file mode 100644 index 0000000..1c785da Binary files /dev/null and b/src/assets/images/mado-230-bg-1.png differ diff --git a/src/assets/images/mado-230-bg-2.png b/src/assets/images/mado-230-bg-2.png new file mode 100644 index 0000000..8ad565a Binary files /dev/null and b/src/assets/images/mado-230-bg-2.png differ diff --git a/src/assets/images/mado-230-bg.png b/src/assets/images/mado-230-bg.png new file mode 100644 index 0000000..157610d Binary files /dev/null and b/src/assets/images/mado-230-bg.png differ diff --git a/src/assets/images/mado-230.png b/src/assets/images/mado-230.png new file mode 100644 index 0000000..720a2ec Binary files /dev/null and b/src/assets/images/mado-230.png differ diff --git a/src/assets/images/mado-230/black.png b/src/assets/images/mado-230/black.png new file mode 100644 index 0000000..f93683a Binary files /dev/null and b/src/assets/images/mado-230/black.png differ diff --git a/src/assets/images/mado-230/cyan.png b/src/assets/images/mado-230/cyan.png new file mode 100644 index 0000000..93f2df7 Binary files /dev/null and b/src/assets/images/mado-230/cyan.png differ diff --git a/src/assets/images/mado-230/gray.png b/src/assets/images/mado-230/gray.png new file mode 100644 index 0000000..2c74564 Binary files /dev/null and b/src/assets/images/mado-230/gray.png differ diff --git a/src/assets/images/mado-230/litiomi.png b/src/assets/images/mado-230/litiomi.png new file mode 100644 index 0000000..08a6135 Binary files /dev/null and b/src/assets/images/mado-230/litiomi.png differ diff --git a/src/assets/images/mado-230/mado230-1.png b/src/assets/images/mado-230/mado230-1.png new file mode 100644 index 0000000..b0af333 Binary files /dev/null and b/src/assets/images/mado-230/mado230-1.png differ diff --git a/src/assets/images/mado-230/mado230-3.png b/src/assets/images/mado-230/mado230-3.png new file mode 100644 index 0000000..e1927c1 Binary files /dev/null and b/src/assets/images/mado-230/mado230-3.png differ diff --git a/src/assets/images/mado-230/mado230-5.png b/src/assets/images/mado-230/mado230-5.png new file mode 100644 index 0000000..79d3c9a Binary files /dev/null and b/src/assets/images/mado-230/mado230-5.png differ diff --git a/src/assets/images/mado-230/mado230-6.png b/src/assets/images/mado-230/mado230-6.png new file mode 100644 index 0000000..400c112 Binary files /dev/null and b/src/assets/images/mado-230/mado230-6.png differ diff --git a/src/assets/images/mado-230/mado230.png b/src/assets/images/mado-230/mado230.png new file mode 100644 index 0000000..2084eae Binary files /dev/null and b/src/assets/images/mado-230/mado230.png differ diff --git a/src/assets/images/mado-230/red.png b/src/assets/images/mado-230/red.png new file mode 100644 index 0000000..46295c6 Binary files /dev/null and b/src/assets/images/mado-230/red.png differ diff --git a/src/assets/images/mado-230/yellow.png b/src/assets/images/mado-230/yellow.png new file mode 100644 index 0000000..2371f19 Binary files /dev/null and b/src/assets/images/mado-230/yellow.png differ diff --git a/src/assets/images/mado-330/1.png b/src/assets/images/mado-330/1.png new file mode 100644 index 0000000..2513f45 Binary files /dev/null and b/src/assets/images/mado-330/1.png differ diff --git a/src/assets/images/mado-330/2.png b/src/assets/images/mado-330/2.png new file mode 100644 index 0000000..830b167 Binary files /dev/null and b/src/assets/images/mado-330/2.png differ diff --git a/src/assets/images/mado-330/3.png b/src/assets/images/mado-330/3.png new file mode 100644 index 0000000..3c020c3 Binary files /dev/null and b/src/assets/images/mado-330/3.png differ diff --git a/src/assets/images/mado-330/4.png b/src/assets/images/mado-330/4.png new file mode 100644 index 0000000..6060b2b Binary files /dev/null and b/src/assets/images/mado-330/4.png differ diff --git a/src/assets/images/mado-330/5.png b/src/assets/images/mado-330/5.png new file mode 100644 index 0000000..286884d Binary files /dev/null and b/src/assets/images/mado-330/5.png differ diff --git a/src/assets/images/mado-330/icon-1.png b/src/assets/images/mado-330/icon-1.png new file mode 100644 index 0000000..c3f924b Binary files /dev/null and b/src/assets/images/mado-330/icon-1.png differ diff --git a/src/assets/images/mado-330/icon-2.png b/src/assets/images/mado-330/icon-2.png new file mode 100644 index 0000000..93dd417 Binary files /dev/null and b/src/assets/images/mado-330/icon-2.png differ diff --git a/src/assets/images/mado230-2.png b/src/assets/images/mado230-2.png new file mode 100644 index 0000000..ceeb970 Binary files /dev/null and b/src/assets/images/mado230-2.png differ diff --git a/src/assets/images/mado230-4.png b/src/assets/images/mado230-4.png new file mode 100644 index 0000000..902739b Binary files /dev/null and b/src/assets/images/mado230-4.png differ diff --git a/src/assets/images/mashhad.png b/src/assets/images/mashhad.png new file mode 100644 index 0000000..b45c09c Binary files /dev/null and b/src/assets/images/mashhad.png differ diff --git a/src/assets/images/orders.png b/src/assets/images/orders.png new file mode 100644 index 0000000..4bdd714 Binary files /dev/null and b/src/assets/images/orders.png differ diff --git a/src/assets/images/plus.svg b/src/assets/images/plus.svg new file mode 100644 index 0000000..020d7ae --- /dev/null +++ b/src/assets/images/plus.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/products.png b/src/assets/images/products.png new file mode 100644 index 0000000..15bcc51 Binary files /dev/null and b/src/assets/images/products.png differ diff --git a/src/assets/images/representation-request.png b/src/assets/images/representation-request.png new file mode 100644 index 0000000..c89d76a Binary files /dev/null and b/src/assets/images/representation-request.png differ diff --git a/src/assets/images/sales-services.png b/src/assets/images/sales-services.png new file mode 100644 index 0000000..d5d3465 Binary files /dev/null and b/src/assets/images/sales-services.png differ diff --git a/src/assets/images/shiraz-pattern.png b/src/assets/images/shiraz-pattern.png new file mode 100644 index 0000000..acf66b8 Binary files /dev/null and b/src/assets/images/shiraz-pattern.png differ diff --git a/src/assets/images/shiraz.png b/src/assets/images/shiraz.png new file mode 100644 index 0000000..a52252d Binary files /dev/null and b/src/assets/images/shiraz.png differ diff --git a/src/assets/images/shopping-bag.svg b/src/assets/images/shopping-bag.svg new file mode 100644 index 0000000..206667b --- /dev/null +++ b/src/assets/images/shopping-bag.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/slider.png b/src/assets/images/slider.png new file mode 100644 index 0000000..82468dd Binary files /dev/null and b/src/assets/images/slider.png differ diff --git a/src/assets/images/special-sale-bg.png b/src/assets/images/special-sale-bg.png new file mode 100644 index 0000000..cae5dcb Binary files /dev/null and b/src/assets/images/special-sale-bg.png differ diff --git a/src/assets/images/special-sale/battery.svg b/src/assets/images/special-sale/battery.svg new file mode 100644 index 0000000..cefd2f3 --- /dev/null +++ b/src/assets/images/special-sale/battery.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/special-sale/charge.svg b/src/assets/images/special-sale/charge.svg new file mode 100644 index 0000000..5316e66 --- /dev/null +++ b/src/assets/images/special-sale/charge.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/special-sale/color.svg b/src/assets/images/special-sale/color.svg new file mode 100644 index 0000000..0639df1 --- /dev/null +++ b/src/assets/images/special-sale/color.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/special-sale/ellipse.png b/src/assets/images/special-sale/ellipse.png new file mode 100644 index 0000000..cd5da4e Binary files /dev/null and b/src/assets/images/special-sale/ellipse.png differ diff --git a/src/assets/images/special-sale/gradient-bg.jpg b/src/assets/images/special-sale/gradient-bg.jpg new file mode 100644 index 0000000..6d4f719 Binary files /dev/null and b/src/assets/images/special-sale/gradient-bg.jpg differ diff --git a/src/assets/images/special-sale/heart.svg b/src/assets/images/special-sale/heart.svg new file mode 100644 index 0000000..69e62c6 --- /dev/null +++ b/src/assets/images/special-sale/heart.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/special-sale/installment-bg.png b/src/assets/images/special-sale/installment-bg.png new file mode 100644 index 0000000..8a0a8fc Binary files /dev/null and b/src/assets/images/special-sale/installment-bg.png differ diff --git a/src/assets/images/special-sale/motor-1.png b/src/assets/images/special-sale/motor-1.png new file mode 100644 index 0000000..2431757 Binary files /dev/null and b/src/assets/images/special-sale/motor-1.png differ diff --git a/src/assets/images/special-sale/motor-2.png b/src/assets/images/special-sale/motor-2.png new file mode 100644 index 0000000..c8b1b9e Binary files /dev/null and b/src/assets/images/special-sale/motor-2.png differ diff --git a/src/assets/images/special-sale/motor-3.png b/src/assets/images/special-sale/motor-3.png new file mode 100644 index 0000000..0595f95 Binary files /dev/null and b/src/assets/images/special-sale/motor-3.png differ diff --git a/src/assets/images/special-sale/motor.svg b/src/assets/images/special-sale/motor.svg new file mode 100644 index 0000000..16ef6d8 --- /dev/null +++ b/src/assets/images/special-sale/motor.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/special-sale/pattern.png b/src/assets/images/special-sale/pattern.png new file mode 100644 index 0000000..e4bd877 Binary files /dev/null and b/src/assets/images/special-sale/pattern.png differ diff --git a/src/assets/images/special-sale/slider-bg.png b/src/assets/images/special-sale/slider-bg.png new file mode 100644 index 0000000..6492053 Binary files /dev/null and b/src/assets/images/special-sale/slider-bg.png differ diff --git a/src/assets/images/special-sale/slider-motor.png b/src/assets/images/special-sale/slider-motor.png new file mode 100644 index 0000000..a1f0289 Binary files /dev/null and b/src/assets/images/special-sale/slider-motor.png differ diff --git a/src/assets/images/special-sale/speed.svg b/src/assets/images/special-sale/speed.svg new file mode 100644 index 0000000..b32fefc --- /dev/null +++ b/src/assets/images/special-sale/speed.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/assets/images/special-sale/thirty-six.png b/src/assets/images/special-sale/thirty-six.png new file mode 100644 index 0000000..702993b Binary files /dev/null and b/src/assets/images/special-sale/thirty-six.png differ diff --git a/src/assets/images/special-sale/wallet.png b/src/assets/images/special-sale/wallet.png new file mode 100644 index 0000000..b6b26bf Binary files /dev/null and b/src/assets/images/special-sale/wallet.png differ diff --git a/src/assets/images/upload-image.svg b/src/assets/images/upload-image.svg new file mode 100644 index 0000000..dcac8ed --- /dev/null +++ b/src/assets/images/upload-image.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/user-review.png b/src/assets/images/user-review.png new file mode 100644 index 0000000..4c91391 Binary files /dev/null and b/src/assets/images/user-review.png differ diff --git a/src/assets/images/user.svg b/src/assets/images/user.svg new file mode 100644 index 0000000..d9b4050 --- /dev/null +++ b/src/assets/images/user.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/BlogCarousel.css b/src/components/BlogCarousel.css new file mode 100644 index 0000000..e9f2909 --- /dev/null +++ b/src/components/BlogCarousel.css @@ -0,0 +1,94 @@ +.blog-carousel { + margin-top: 0.75rem; +} + +.blog-carousel__viewport { + overflow: hidden; + direction: ltr; +} + +.blog-carousel__track { + display: flex; + transition: transform 0.45s ease; +} + +.blog-carousel__slide { + flex: 0 0 50%; + min-width: 0; + padding-inline: 7.5px; + box-sizing: border-box; +} + +.blog-carousel__card { + display: flex; + flex-direction: column; + gap: 8px; + text-decoration: none; + color: inherit; + direction: rtl; +} + +.blog-carousel__meta { + display: flex; + justify-content: flex-end; + color: #9c9c9c; + font-size: 12px; +} + +.blog-carousel__card h3 { + margin: 0; + font-size: 15px; + font-weight: 500; + color: #333; + line-height: 1.6; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; + min-height: 2.9em; +} + +.blog-carousel__card p { + margin: 0; + color: #808080; + font-size: 13px; + line-height: 1.7; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.blog-carousel__dots { + display: flex; + justify-content: center; + gap: 8px; + margin-top: 18px; +} + +.blog-carousel__dots button { + width: 10px; + height: 10px; + border: 0; + border-radius: 999px; + background: #cfd8e6; + cursor: pointer; +} + +.blog-carousel__dots button.is-active { + background: #2c86ed; +} + +@media (min-width: 960px) { + .blog-carousel__slide { + flex-basis: 25%; + } + + .blog-carousel__card h3 { + font-size: 18px; + } + + .blog-carousel__card p { + font-size: 14px; + } +} diff --git a/src/components/BlogCarousel.tsx b/src/components/BlogCarousel.tsx new file mode 100644 index 0000000..9fd2ee1 --- /dev/null +++ b/src/components/BlogCarousel.tsx @@ -0,0 +1,85 @@ +import { useEffect, useMemo, useState } from 'react' +import { Link } from 'react-router-dom' +import type { PublicBlog } from '../lib/api' +import { formatFaDate } from '../lib/api' +import { blogPostPath } from '../lib/paths' +import BlogCover from './BlogCover' +import './BlogCarousel.css' + +type Props = { + blogs: PublicBlog[] +} + +function usePerView() { + const [perView, setPerView] = useState(2) + + useEffect(() => { + const mq = window.matchMedia('(min-width: 960px)') + const sync = () => setPerView(mq.matches ? 4 : 2) + sync() + mq.addEventListener('change', sync) + return () => mq.removeEventListener('change', sync) + }, []) + + return perView +} + +export default function BlogCarousel({ blogs }: Props) { + const perView = usePerView() + const pages = Math.max(1, Math.ceil(blogs.length / perView)) + const [page, setPage] = useState(0) + + useEffect(() => { + setPage((current) => Math.min(current, pages - 1)) + }, [pages]) + + useEffect(() => { + if (pages <= 1) return + const timer = window.setInterval(() => { + setPage((current) => (current + 1) % pages) + }, 3500) + return () => window.clearInterval(timer) + }, [pages]) + + const offset = useMemo(() => `-${page * 100}%`, [page]) + + if (blogs.length === 0) return null + + return ( +
+
+
+ {blogs.map((blog) => ( +
+ + +
+ {formatFaDate(blog.createdAt)} +
+

{blog.title}

+

{blog.summary}

+ +
+ ))} +
+
+ + {pages > 1 ? ( +
+ {Array.from({ length: pages }, (_, index) => ( +
+ ) : null} +
+ ) +} diff --git a/src/components/BlogCover.css b/src/components/BlogCover.css new file mode 100644 index 0000000..414d9f7 --- /dev/null +++ b/src/components/BlogCover.css @@ -0,0 +1,28 @@ +.blog-cover { + aspect-ratio: 4 / 3; + background: #eef2f8; + border-radius: 8px; + overflow: hidden; +} + +.blog-cover img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.blog-cover__placeholder { + width: 100%; + height: 100%; + display: grid; + place-items: center; + background: linear-gradient(180deg, #f4f7fb 0%, #e7edf6 100%); +} + +.blog-cover__placeholder img { + width: min(46%, 120px); + height: auto; + object-fit: contain; + opacity: 0.55; +} diff --git a/src/components/BlogCover.tsx b/src/components/BlogCover.tsx new file mode 100644 index 0000000..13ef383 --- /dev/null +++ b/src/components/BlogCover.tsx @@ -0,0 +1,33 @@ +import { useState } from 'react' +import logo from '../assets/images/logo.png' +import './BlogCover.css' + +export function isUsableImageUrl(url: string | null | undefined): boolean { + const value = (url ?? '').trim() + if (!value) return false + if (value === '\\N' || value === 'null' || value === 'undefined') return false + return /^https?:\/\//i.test(value) || value.startsWith('/') +} + +type Props = { + src: string | null | undefined + alt: string + className?: string +} + +export default function BlogCover({ src, alt, className = '' }: Props) { + const [failed, setFailed] = useState(false) + const showImage = isUsableImageUrl(src) && !failed + + return ( +
+ {showImage ? ( + {alt} setFailed(true)} /> + ) : ( + + )} +
+ ) +} diff --git a/src/components/Footer.css b/src/components/Footer.css new file mode 100644 index 0000000..1aedd3f --- /dev/null +++ b/src/components/Footer.css @@ -0,0 +1,91 @@ +.site-footer { + background: #fff; + border-top: 1px solid #e5e5e5; + padding-bottom: 1.5rem; +} + +.site-footer__social { + border-block: 1px solid #c5c5c5; + padding-block: 1rem; +} + +.site-footer__social-inner { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.site-footer__social-inner > p { + color: #333; +} + +.site-footer__social-links { + display: flex; + flex-wrap: wrap; + gap: 1.25rem 2rem; + align-items: center; +} + +.site-footer__social-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.site-footer__grid { + display: grid; + gap: 1.75rem; + padding-block: 2.75rem 1.5rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.site-footer__grid h3 { + margin-bottom: 0.85rem; + font-size: 1rem; +} + +.site-footer__grid ul { + list-style: disc; + padding-inline-start: 1.1rem; + opacity: 0.55; + line-height: 1.9; + font-size: 0.92rem; +} + +.site-footer__brand { + display: flex; + justify-content: center; + grid-column: 1 / -1; +} + +.site-footer__brand img { + width: 140px; + max-width: 100%; + height: auto; + aspect-ratio: 209 / 76; + object-fit: contain; +} + +.site-footer__copy { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + gap: 0.75rem; + padding-top: 1.25rem; + border-top: 1px solid #ececec; + font-size: 0.9rem; + color: #444; +} + +@media (min-width: 990px) { + .site-footer__grid { + grid-template-columns: 1.1fr 1fr 1.4fr 1fr 0.9fr; + } + + .site-footer__brand { + grid-column: auto; + justify-content: flex-end; + } +} diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx new file mode 100644 index 0000000..45faae8 --- /dev/null +++ b/src/components/Footer.tsx @@ -0,0 +1,139 @@ +import { Link } from 'react-router-dom' +import bogLogo from '../assets/images/bog-logo.png' +import telegram from '../assets/images/icons/telegram.png' +import whatsapp from '../assets/images/icons/whatsapp.png' +import eitaa from '../assets/images/icons/eitaa.png' +import instagram from '../assets/images/icons/instagram.png' +import { toFaDigits } from '../lib/faDigits' +import { paths } from '../lib/paths' +import './Footer.css' + +export default function Footer() { + return ( + + ) +} diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 0000000..3bb1ca9 --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,230 @@ +.site-header { + position: relative; + z-index: 50; + background: #f7f7f7; +} + +.site-header__bar { + display: grid; + grid-template-columns: auto 1fr auto; + align-items: center; + gap: 0.75rem; + min-height: 64px; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); +} + +.site-header__burger { + display: grid; + gap: 5px; + width: 28px; + padding: 0; + grid-column: 1; +} + +.site-header__burger span { + display: block; + width: 22px; + height: 2px; + background: var(--color-navy); +} + +.site-header__logo { + display: block; + grid-column: 1; + justify-self: start; +} + +.site-header__logo img { + width: 88px; + height: auto; +} + +.site-header__nav { + display: none; + align-items: center; + justify-content: center; + gap: 0.15rem; + grid-column: 2; + justify-self: center; +} + +.site-header__home { + display: grid; + place-items: center; + padding: 0.75rem 0.85rem; +} + +.site-header__link { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.75rem 0.85rem; + font-size: 0.92rem; + font-weight: 300; + color: #222; +} + +.site-header__link:hover, +.site-header__link.active { + color: var(--color-navy); +} + +.site-header__dropdown { + position: relative; +} + +.site-header__submenu { + display: none; + position: absolute; + top: 100%; + inset-inline-end: 0; + min-width: 150px; + padding: 0.5rem 0; + background: #fff; + border: 1px solid rgba(0, 0, 0, 0.08); + border-radius: var(--radius); + box-shadow: 0 8px 24px rgba(20, 36, 76, 0.08); +} + +.site-header__submenu a { + display: block; + padding: 0.55rem 1.1rem; + font-size: 0.9rem; +} + +.site-header__submenu a:hover { + background: #f5f8ff; +} + +.site-header__dropdown:hover .site-header__submenu { + display: block; +} + +.site-header__service { + display: inline-flex; + align-items: center; + gap: 0.45rem; + margin-inline-start: 0.35rem; + padding: 0.35rem 0.75rem; + border: 1px solid #2073d2; + border-radius: var(--radius); + color: #2073d2; + font-size: 0.88rem; + font-weight: 500; +} + +.site-header__actions { + display: flex; + align-items: center; + gap: 0.15rem; + grid-column: 3; + justify-self: end; +} + +.site-header__icon, +.site-header__cart { + display: grid; + place-items: center; + padding: 0.65rem; +} + +.site-header__cart { + position: relative; +} + +.site-header__cart span { + position: absolute; + top: 2px; + inset-inline-start: 2px; + min-width: 16px; + height: 16px; + padding: 0 4px; + border-radius: 999px; + background: #e53935; + color: #fff; + font-size: 0.65rem; + line-height: 16px; + text-align: center; +} + +.site-header__phone { + padding: 0.35rem 0.5rem; + font-size: 1.05rem; + font-weight: 300; + color: #333; + letter-spacing: 0.02em; +} + +@media (max-width: 989px) { + .site-header__bar { + grid-template-columns: auto 1fr auto; + } + + .site-header__burger { + display: grid; + } + + .site-header__logo { + grid-column: 2; + justify-self: center; + } + + .site-header__nav.is-open { + display: flex; + flex-direction: column; + align-items: stretch; + position: absolute; + top: 100%; + inset-inline: 0; + z-index: 60; + padding: 1rem; + background: #fff; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + box-shadow: 0 12px 30px rgba(0, 0, 0, 0.08); + grid-column: auto; + justify-self: stretch; + } + + .site-header__dropdown:hover .site-header__submenu, + .site-header__submenu { + display: block; + position: static; + box-shadow: none; + border: 0; + padding-inline-start: 1rem; + } + + .site-header__phone { + display: none; + } +} + +@media (min-width: 990px) { + .site-header__burger { + display: none; + } + + .site-header__bar { + grid-template-columns: 1fr auto 1fr; + } + + .site-header__logo { + grid-column: 1; + justify-self: start; + } + + .site-header__nav { + display: flex; + grid-column: 2; + justify-self: center; + } + + .site-header__actions { + grid-column: 3; + justify-self: end; + } + + .site-header__logo img { + width: 96px; + } +} diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..c8c5993 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,108 @@ +import { useState } from 'react' +import { Link, NavLink } from 'react-router-dom' +import logo from '../assets/images/logo.png' +import homeIcon from '../assets/images/icons/home.svg' +import angleDown from '../assets/images/icons/angle-down.svg' +import angleLeft from '../assets/images/icons/angle-left-2.svg' +import userIcon from '../assets/images/icons/user.svg' +import cartIcon from '../assets/images/icons/cart.svg' +import { LANDING_MODEL_PATHS } from '../data/landingModels' +import { toFaDigits } from '../lib/faDigits' +import { paths } from '../lib/paths' +import './Header.css' + +const DASHBOARD_URL = ( + import.meta.env.VITE_DASHBOARD_URL as string | undefined +)?.replace(/\/$/, '') ?? 'http://manage.madomotor.com:5175' + +const productLinks = [ + ...LANDING_MODEL_PATHS, + { to: paths.products, label: 'قطعات' }, +] + +export default function Header() { + const [open, setOpen] = useState(false) + + return ( +
+
+ + + setOpen(false)}> + مادو موتور + + + + +
+ + + + + + {toFaDigits(0)} + + + {toFaDigits('025-1825')} + +
+
+
+ ) +} diff --git a/src/components/PrimaryButton.css b/src/components/PrimaryButton.css new file mode 100644 index 0000000..b5ff70c --- /dev/null +++ b/src/components/PrimaryButton.css @@ -0,0 +1,67 @@ +.primary-btn { + --btn-fill: var(--color-navy); + position: relative; + display: inline-flex; + min-width: 270px; + align-items: center; + justify-content: space-between; + overflow: hidden; + border: 1px solid var(--color-navy); + border-radius: var(--radius); + background: var(--color-white); + color: var(--color-navy); + transition: color 0.35s ease; +} + +.primary-btn--dark { + --btn-fill: #000; + border-color: #000; + color: #000; +} + +.primary-btn--full { + width: 100%; +} + +.primary-btn__label, +.primary-btn__icon { + position: relative; + z-index: 1; +} + +.primary-btn__label { + padding: 0.7rem 1rem; + font-weight: 500; +} + +.primary-btn__icon { + display: grid; + place-items: center; + width: 52px; + height: 45px; + background: var(--btn-fill); +} + +.primary-btn__icon img { + filter: brightness(0) invert(1); +} + +.primary-btn::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + width: 0; + background: var(--btn-fill); + transition: width 0.35s ease; + z-index: 0; +} + +.primary-btn:hover { + color: #fff; +} + +.primary-btn:hover::after { + width: 100%; +} diff --git a/src/components/PrimaryButton.tsx b/src/components/PrimaryButton.tsx new file mode 100644 index 0000000..93706f1 --- /dev/null +++ b/src/components/PrimaryButton.tsx @@ -0,0 +1,60 @@ +import { Link } from 'react-router-dom' +import arrowLeft from '../assets/images/icons/arrow-left.svg' +import './PrimaryButton.css' + +type Props = { + to: string + title?: string + variant?: 'default' | 'dark' + full?: boolean + download?: boolean + iconSrc?: string + className?: string +} + +export default function PrimaryButton({ + to, + title = 'دوست دارم بیشتر بدونم', + variant = 'default', + full = false, + download = false, + iconSrc = arrowLeft, + className = '', +}: Props) { + const classes = [ + 'primary-btn', + variant === 'dark' ? 'primary-btn--dark' : '', + full ? 'primary-btn--full' : '', + className, + ] + .filter(Boolean) + .join(' ') + + if (download || to.startsWith('http') || to.startsWith('#')) { + return ( + + {title} + + + ) + } + + return ( + + {title} + + + ) +} diff --git a/src/components/SeoHead.tsx b/src/components/SeoHead.tsx new file mode 100644 index 0000000..19dc046 --- /dev/null +++ b/src/components/SeoHead.tsx @@ -0,0 +1,53 @@ +import { useEffect } from 'react' +import { absoluteUrl } from '../lib/paths' + +type Props = { + title: string + description: string + path: string + noIndex?: boolean +} + +function upsertMeta(selector: string, attrs: Record) { + let el = document.head.querySelector(selector) as HTMLMetaElement | null + if (!el) { + el = document.createElement('meta') + document.head.appendChild(el) + } + for (const [key, value] of Object.entries(attrs)) { + el.setAttribute(key, value) + } +} + +function upsertLink(rel: string, href: string) { + let el = document.head.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement | null + if (!el) { + el = document.createElement('link') + el.setAttribute('rel', rel) + document.head.appendChild(el) + } + el.setAttribute('href', href) +} + +export default function SeoHead({ title, description, path, noIndex = false }: Props) { + useEffect(() => { + const url = absoluteUrl(path) + document.title = title + upsertMeta('meta[name="description"]', { name: 'description', content: description }) + upsertMeta('meta[property="og:title"]', { property: 'og:title', content: title }) + upsertMeta('meta[property="og:description"]', { + property: 'og:description', + content: description, + }) + upsertMeta('meta[property="og:url"]', { property: 'og:url', content: url }) + upsertMeta('meta[property="og:type"]', { property: 'og:type', content: 'website' }) + upsertMeta('meta[property="og:locale"]', { property: 'og:locale', content: 'fa_IR' }) + upsertMeta('meta[name="robots"]', { + name: 'robots', + content: noIndex ? 'noindex, nofollow' : 'index, follow', + }) + upsertLink('canonical', url) + }, [title, description, path, noIndex]) + + return null +} diff --git a/src/data/dealers.ts b/src/data/dealers.ts new file mode 100644 index 0000000..8349de7 --- /dev/null +++ b/src/data/dealers.ts @@ -0,0 +1,146 @@ +import tehran from '../assets/images/cities/tehran.jpg' +import yazd from '../assets/images/cities/yazd.jpg' +import shiraz from '../assets/images/cities/shiraz.jpg' +import qom from '../assets/images/cities/qom.jpg' +import mashhad from '../assets/images/cities/mashhad.jpg' +import kish from '../assets/images/cities/kish.jpg' +import tabriz from '../assets/images/cities/tabriz.jpg' +import isfahan from '../assets/images/cities/isfahan.png' + +export type DealerBranch = { + name: string + code: number + ceo?: string + address: string + phones: string[] + postalCode: string + mapURL: string +} + +export type DealerCity = { + name: string + image: string + branches: DealerBranch[] +} + +export const dealerCities: DealerCity[] = [ + { + name: 'تهران', + image: tehran, + branches: [ + { + name: 'نمایندگی تهران', + code: 2107, + ceo: 'آقای حسن کاشی', + address: + 'آدرس: تهران، تقاطع خیابان دماوند و خیابان سبلان. همکف درمانگاه پاستور پلاک 932', + phones: ['09123121761', '02177820419'], + postalCode: '۱۷۳۳۶۴۵۷۴۳', + mapURL: + 'https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d481.5833633569428!2d51.47356296989864!3d35.70915219052479!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3f8e030010794685%3A0x7d9f0db82020577c!2z2YbZhdin24zZhtiv2q_bjCDZhdin2K_ZiCDZhdmI2KrZiNixINi02LHZgiDYqtmH2LHYp9mG!5e0!3m2!1sen!2s!4v1725741467994!5m2!1sen!2s', + }, + ], + }, + { + name: 'یزد', + image: yazd, + branches: [ + { + name: 'شعبه 1: اقای رنجبر', + code: 3505, + address: 'یزد، چهاراه اطلسی روبرو بانک ایران زمین فروشگاه آداک الکترونیک', + phones: ['09133535675', '09131591601'], + postalCode: '8915838686', + mapURL: + 'https://www.google.com/maps/embed?pb=!1m17!1m12!1m3!1d211.84891308407066!2d54.373355849957!3d31.83635785944366!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m2!1m1!2zMzHCsDUwJzExLjEiTiA1NMKwMjInMjQuNiJF!5e0!3m2!1sen!2s!4v1732546028494!5m2!1sen!2s', + }, + ], + }, + { + name: 'شیراز', + image: shiraz, + branches: [ + { + name: 'نمایندگی شیراز', + code: 7106, + address: + 'آدرس: شیراز بلوار سلمان فارسی روبروی ترمینال کاراندیش شرکت بازرگانی شیراز تسلیم', + phones: ['07137305703', '09177093102'], + postalCode: '7145813116', + mapURL: + 'https://www.google.com/maps/embed?pb=!1m14!1m8!1m3!1d1734.3881544381507!2d52.5638708!3d29.6101883!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x3fb20d00503d4737%3A0xdb308581ec30ba95!2z2YXZiNiq2YjYsSDYs9uM2qnZhNiqINio2LHZgtuMINmF2KfYr9mI2YXZiNiq2YjYsSDZgdix2YjYtCDZiCDYrtiv2YXYp9iqINm-2LMg2KfYsiDZgdix2YjYtCDYr9ixINmB2KfYsdiz!5e0!3m2!1sen!2s!4v1725742454814!5m2!1sen!2s', + }, + ], + }, + { + name: 'قم', + image: qom, + branches: [ + { + name: 'شعبه 1: اقای حسین بیطرفان', + code: 2543, + address: 'قم- باجک3(فرهنگیان)- بلوار مهر- بین کوچه 4 و 6- خدمات موتورسیکلت رزمنده', + phones: ['09125535153'], + postalCode: '3719138634', + mapURL: + 'https://www.google.com/maps/embed?pb=!1m13!1m8!1m3!1d13125.736863215929!2d50.912307!3d34.668989!3m2!1i1024!2i768!4f13.1!3m2!1m1!2zMzHCsDUwJzExLjEiTiA1NMKwMjInMjQuNiJF!5e0!3m2!1sen!2sde!4v1761115249515!5m2!1sen!2sde', + }, + ], + }, + { + name: 'مشهد', + image: mashhad, + branches: [ + { + name: 'نمایندگی مشهد', + code: 9101, + address: 'مشهد', + phones: [], + postalCode: '', + mapURL: '', + }, + ], + }, + { + name: 'کیش', + image: kish, + branches: [ + { + name: 'نمایندگی کیش', + code: 7601, + address: 'کیش', + phones: [], + postalCode: '', + mapURL: '', + }, + ], + }, + { + name: 'تبریز', + image: tabriz, + branches: [ + { + name: 'نمایندگی تبریز', + code: 4101, + address: 'تبریز', + phones: [], + postalCode: '', + mapURL: '', + }, + ], + }, + { + name: 'اصفهان', + image: isfahan, + branches: [ + { + name: 'نمایندگی اصفهان', + code: 3101, + address: 'اصفهان', + phones: [], + postalCode: '', + mapURL: '', + }, + ], + }, +] diff --git a/src/data/landingModels.ts b/src/data/landingModels.ts new file mode 100644 index 0000000..c826470 --- /dev/null +++ b/src/data/landingModels.ts @@ -0,0 +1,285 @@ +import mado230Hero from '../assets/images/mado-230/mado230.png' +import mado230Intro from '../assets/images/mado-230/mado230-1.png' +import mado230Dash from '../assets/images/mado230-2.png' +import mado230ColorMain from '../assets/images/mado-230/mado230-3.png' +import mado230Parts from '../assets/images/mado230-4.png' +import mado230Eco from '../assets/images/mado-230/mado230-5.png' +import mado230Price from '../assets/images/mado-230/mado230-6.png' +import mado230Cyan from '../assets/images/mado-230/cyan.png' +import mado230Gray from '../assets/images/mado-230/gray.png' +import mado230Red from '../assets/images/mado-230/red.png' +import mado230Yellow from '../assets/images/mado-230/yellow.png' +import mado230Black from '../assets/images/mado-230/black.png' +import mado230Battery from '../assets/images/mado-230/litiomi.png' + +import mado130Hero from '../assets/images/mado-130/slider.png' +import mado130Intro from '../assets/images/mado-130/blue-130.png' +import mado130Battery from '../assets/images/mado-130/litiomi.png' +import mado130Eco from '../assets/images/mado-130/environment.png' +import mado130Price from '../assets/images/mado-130/price-pic.png' +import mado130Black from '../assets/images/mado-130/colors/black.png' +import mado130Cyan from '../assets/images/mado-130/colors/cyan.png' +import mado130CyanWhite from '../assets/images/mado-130/colors/cyan-white.png' +import mado130Red from '../assets/images/mado-130/colors/red.png' +import mado130Pink from '../assets/images/mado-130/colors/pink.png' +import weightIcon from '../assets/images/mado-130/weight-icon.svg' + +import mado330Hero from '../assets/images/mado-330/1.png' +import mado330Intro from '../assets/images/mado-330/2.png' +import mado330Eco from '../assets/images/mado-330/4.png' +import mado330Price from '../assets/images/mado-330/5.png' +import mado330Icon1 from '../assets/images/mado-330/icon-1.png' +import mado330Icon2 from '../assets/images/mado-330/icon-2.png' + +import bg1 from '../assets/images/mado-230-bg-1.png' +import bg2 from '../assets/images/mado-230-bg-2.png' +import guarantee from '../assets/images/36month.png' +import guaranteeTypo from '../assets/images/guarantee-typo.png' +import fastCharge from '../assets/images/icons/fast-charge.svg' +import angleDown from '../assets/images/icons/angle-down.svg' + +export type LandingModelId = 'mado-130' | 'mado-230' | 'mado-330' + +export type LandingFeatureBlock = { + eyebrow: string + title: string + body: string +} + +export type LandingModel = { + id: LandingModelId + slug: string + label: string + titleFa: string + menuLabel: string + description: string + heroImage: string + introImage: string + dashImage: string + ecoImage: string + priceImage: string + batteryImage: string + partsImage: string + colors: string[] + colorTitle: string + colorSubtitles: string[] + maxSpeed: string + rangeValue: string + rangeNote: string + introText: string + dashText: string + highlight: { + icon: string + iconWidth: number + title: string + body?: string + } + heroExtras?: Array<{ + image: string + title: string + subtitle: string + }> + featureBlocks?: LandingFeatureBlock[] + batteryItems: string[] + guaranteeItems: string[] + priceItems: string[] + bg1: string + bg2: string + guaranteeImage: string + guaranteeTypo: string + scrollIcon: string +} + +const sharedBattery = [ + 'طول عمر بسیار بالا نسبت به نسل های قبلی باطری های لیتیومی', + 'وزن سبک تر نسبت به باطری های اسیدی', + 'پیمایش بالا و شارژ سریع با برق خانگی ( حدود 3 الی 4 ساعت)', + 'باطری 30آمپر لیتیومی 60 تا 70 کیلومتر با شرایط استاندارد', + 'باطری 45 آمپر لیتیومی 90 تا 110 کیلومتر با شرایط استاندارد', +] + +const sharedGuarantee = [ + 'گارانتی بیش از 70 قطعه اصلی از 12 تا 36 ماه', + 'گارانتی باطری 20 ماه', + 'خدمات پس از فروش گسترده در سراسر کشور', + 'دارای نمایندگی های فعال در اکثر استان های کشور', +] + +const sharedPrice = [ + 'قیمت بسیار مناسب نسبت به محصولات مشابه دیگر', + 'شرایط اقساط بلند مدت', +] + +export const LANDING_MODELS: Record = { + 'mado-230': { + id: 'mado-230', + slug: 'موتور-برقی-مادو-۲۳۰', + label: 'مادو 230', + titleFa: 'مادو ۲۳۰', + menuLabel: 'مادو ۲۳۰', + description: + 'مادو ۲۳۰ یک موتور تمام برقی ساخته ی شرکت مادو موتور هست. این موتور کاملا بی صدا و بسیار با کیفیت هست.', + heroImage: mado230Hero, + introImage: mado230Intro, + dashImage: mado230Dash, + ecoImage: mado230Eco, + priceImage: mado230Price, + batteryImage: mado230Battery, + partsImage: mado230Parts, + colors: [ + mado230ColorMain, + mado230Cyan, + mado230Gray, + mado230Red, + mado230Yellow, + mado230Black, + ], + colorTitle: 'کیفیت رنگ بسیار بالا', + colorSubtitles: ['مطابق با استانداردهای بین المللی'], + maxSpeed: '80', + rangeValue: '120', + rangeNote: '(روی باتــــری 45 آمپـــــر)', + introText: + 'مادو 230 جزء با کیفیت ترین و پر قدرتمندترین موتور سیکت های برقی تولید داخل است که با استفاده از تکنولوژی های روز دنیا تولید شده است', + dashText: + 'میزان سرعت-میزان شارژ-نشانگرچراغ هاو راهنماها-نشانگر دنده عقب -نشانگر خطای موتور-نشانگر خطای ECU', + highlight: { + icon: fastCharge, + iconWidth: 30, + title: 'قابلیت فست شارژ', + body: 'به زودی...', + }, + batteryItems: sharedBattery, + guaranteeItems: sharedGuarantee, + priceItems: sharedPrice, + bg1, + bg2, + guaranteeImage: guarantee, + guaranteeTypo, + scrollIcon: angleDown, + }, + 'mado-130': { + id: 'mado-130', + slug: 'موتور-برقی-مادو-۱۳۰', + label: 'مادو 130', + titleFa: 'مادو ۱۳۰', + menuLabel: 'مادو ۱۳۰', + description: + 'مادو ۱۳۰ یک موتور تمام برقی ساخته ی شرکت مادو موتور هست. سبک، بی‌صدا و مناسب استفاده روزمره.', + heroImage: mado130Hero, + introImage: mado130Intro, + dashImage: mado230Dash, + ecoImage: mado130Eco, + priceImage: mado130Price, + batteryImage: mado130Battery, + partsImage: mado230Parts, + colors: [mado130Black, mado130Cyan, mado130CyanWhite, mado130Red, mado130Pink], + colorTitle: 'کیفیت رنگ متنوع و بسیار بالا', + colorSubtitles: [ + 'مطابق با استانداردهـــــای بین المللی', + 'متناسب با انواع سلیقه ها - حتی شما خانم های عزیز', + ], + maxSpeed: '80', + rangeValue: '70', + rangeNote: '(روی باتــــری 30 آمپـــر)', + introText: + 'مادو 130 جزء با کیفیت ترین و پر قدرتمندترین موتور سیکت های برقی تولید داخل است که با استفاده از تکنولوژی های روز دنیا تولید شده است', + dashText: + 'میزان سرعت-میزان شارژ-نشانگرچراغ هاو راهنماها-نشانگر دنده عقب -نشانگر خطای موتور-نشانگر خطای ECU', + highlight: { + icon: weightIcon, + iconWidth: 50, + title: 'وزن بسیار سبک', + body: 'سبک وزن بودن موتور باعث می شود استفاده ی آن برای افراد مبتدی راحت تر باشد', + }, + batteryItems: sharedBattery, + guaranteeItems: sharedGuarantee, + priceItems: sharedPrice, + bg1, + bg2, + guaranteeImage: guarantee, + guaranteeTypo, + scrollIcon: angleDown, + }, + 'mado-330': { + id: 'mado-330', + slug: 'موتور-برقی-مادو-۳۳۰', + label: 'مادو 330', + titleFa: 'مادو ۳۳۰', + menuLabel: 'مادو ۳۳۰', + description: + 'مادو ۳۳۰ آخرین نسل از موتورهای برقی مادو می‌باشد؛ با ترمز CBS، نور دی‌لایت و کیفیت قطعات بالا.', + heroImage: mado330Hero, + introImage: mado330Intro, + dashImage: mado230Dash, + ecoImage: mado330Eco, + priceImage: mado330Price, + batteryImage: mado130Battery, + partsImage: mado230Parts, + colors: [mado130Black, mado130Cyan, mado130CyanWhite, mado130Red, mado130Pink], + colorTitle: 'کیفیت رنگ متنوع و بسیار بالا', + colorSubtitles: ['مطابق با استانداردهای بین المللی'], + maxSpeed: '80', + rangeValue: '70', + rangeNote: '(روی باتری 30 آمپــر)', + introText: + 'مادو ۳۳۰ آخرین نسل از موتورهای برقی مادو می باشد. در مادو ۳۳۰ همه چیز بهترین هست. کیفیت قطعات، کیلومتر دیجیتالی، ترمز CBS، نور دی لایت، تنوع رنگ بالا و ... . سال های تجربه ی ما در مادو ۳۳۰ به ظهور رسیده است.', + dashText: + 'این نوع کیلومترشمارها علاوه بر ظاهر مدرن، کارایی بسیار بیشتری نسبت به مدل‌های آنالوگ دارند.', + highlight: { + icon: weightIcon, + iconWidth: 50, + title: 'وزن بسیار سبک', + body: 'سبک وزن بودن موتور باعث می شود استفاده ی آن برای افراد مبتدی راحت تر باشد', + }, + heroExtras: [ + { + image: mado330Icon1, + title: 'ترمز ترکیبی', + subtitle: 'CBS Braking System', + }, + { + image: mado330Icon2, + title: 'چراغ مخصوص روز', + subtitle: 'Day Lights', + }, + ], + featureBlocks: [ + { + eyebrow: 'کیلومتر', + title: 'دیجیتالی', + body: 'این نوع کیلومترشمارها علاوه بر ظاهر مدرن، کارایی بسیار بیشتری نسبت به مدل‌های آنالوگ دارند. برای مثال دقت بسیار بالاتر، اطلاعات جامع و کامل تر، خوانایی بهتر در شب، استهلاک کمتر و عیب‌یابی هوشمند', + }, + { + eyebrow: 'ترمز', + title: 'سی بی اس (CBS)', + body: 'در موتورسیکلت‌ها، واژه CBS مخفف عبارت Combined Braking System به معنای سیستم ترمز ترکیبی است. در سیستم CBS وقتی شما ترمز عقب را فشار می‌دهید، سیستم به‌طور خودکار و هم‌زمان نیروی ترمز را بین هر دو چرخ (عقب و جلو) تقسیم می‌کند.', + }, + { + eyebrow: 'نور', + title: 'دی لایت', + body: 'نور دی‌لایت (Daylight) که نام کامل آن Daytime Running Lights (DRL) است، به معنای «چراغ‌های روشنایی در روز» می‌باشد. هدف اصلی دی‌لایت، روشن کردن جاده برای شما نیست؛ بلکه هدف این است که دیگر رانندگان (خودروها و عابران) شما را ببینند.', + }, + ], + batteryItems: sharedBattery, + guaranteeItems: sharedGuarantee, + priceItems: sharedPrice, + bg1, + bg2, + guaranteeImage: guarantee, + guaranteeTypo, + scrollIcon: angleDown, + }, +} + +export const LANDING_MODEL_PATHS = ( + Object.values(LANDING_MODELS) as LandingModel[] +).map((model) => ({ + to: `/landing/${model.id}/${model.slug}`, + label: model.menuLabel, +})) + +export function resolveLandingModel(id?: string): LandingModel | null { + if (!id) return null + return LANDING_MODELS[id as LandingModelId] ?? null +} diff --git a/src/data/provinces.ts b/src/data/provinces.ts new file mode 100644 index 0000000..6378569 --- /dev/null +++ b/src/data/provinces.ts @@ -0,0 +1,33 @@ +export const IRAN_PROVINCES: Record = { + تهران: ['تهران', 'شهریار', 'ری', 'اسلامشهر', 'قدس', 'ملارد', 'پردیس', 'ورامین'], + اصفهان: ['اصفهان', 'کاشان', 'نجف‌آباد', 'خمینی‌شهر', 'شاهین‌شهر'], + 'خراسان رضوی': ['مشهد', 'نیشابور', 'سبزوار', 'تربت حیدریه'], + فارس: ['شیراز', 'مرودشت', 'جهرم', 'لار'], + آذربایجان‌شرقی: ['تبریز', 'مراغه', 'مرند', 'اهر'], + آذربایجان‌غربی: ['ارومیه', 'خوی', 'مهاباد', 'بوکان'], + البرز: ['کرج', 'فردیس', 'نظرآباد', 'هشتگرد'], + قم: ['قم'], + یزد: ['یزد', 'اردکان', 'میبد'], + خوزستان: ['اهواز', 'آبادان', 'دزفول', 'ماهشهر'], + گیلان: ['رشت', 'انزلی', 'لاهیجان', 'رودسر'], + مازندران: ['ساری', 'بابل', 'آمل', 'قائم‌شهر'], + کرمان: ['کرمان', 'رفسنجان', 'جیرفت', 'سیرجان'], + هرمزگان: ['بندرعباس', 'قشم', 'کیش', 'میناب'], + سیستان‌وبلوچستان: ['زاهدان', 'چابهار', 'ایرانشهر'], + کردستان: ['سنندج', 'سقز', 'مریوان'], + کرمانشاه: ['کرمانشاه', 'اسلام‌آباد غرب'], + همدان: ['همدان', 'ملایر', 'نهاوند'], + مرکزی: ['اراک', 'ساوه', 'خمین'], + قزوین: ['قزوین', 'تاکستان', 'آبیک'], + زنجان: ['زنجان', 'ابهر'], + سمنان: ['سمنان', 'شاهرود', 'دامغان'], + گلستان: ['گرگان', 'گنبد کاووس', 'بندر ترکمن'], + بوشهر: ['بوشهر', 'دیر', 'کنگان'], + کهگیلویه‌وبویراحمد: ['یاسوج', 'گچساران'], + لرستان: ['خرم‌آباد', 'بروجرد', 'الیگودرز'], + ایلام: ['ایلام', 'دهلران'], + اردبیل: ['اردبیل', 'مشگین‌شهر'], + چهارمحال‌وبختیاری: ['شهرکرد', 'بروجن'], + 'خراسان شمالی': ['بجنورد', 'شیروان'], + 'خراسان جنوبی': ['بیرجند', 'قائن'], +} diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..41adff0 --- /dev/null +++ b/src/index.css @@ -0,0 +1,156 @@ +@font-face { + font-family: 'IranYekan'; + src: url('./assets/fonts/iranyekan/woff/iranyekanweblight.woff') format('woff'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'IranYekan'; + src: url('./assets/fonts/iranyekan/woff/iranyekanwebmedium.woff') format('woff'); + font-weight: 500; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'IranYekan'; + src: url('./assets/fonts/iranyekan/woff/iranyekanwebbold.woff') format('woff'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Montserrat'; + src: url('./assets/fonts/montserrat/Montserrat-Light.woff') format('woff'); + font-weight: 300; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Montserrat'; + src: url('./assets/fonts/montserrat/Montserrat-Regular.woff') format('woff'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Montserrat'; + src: url('./assets/fonts/montserrat/montserrat-bold.woff') format('woff'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +:root { + --color-navy: #14244c; + --color-blue: #2c86ed; + --color-blue-deep: #2073d2; + --color-gray-bg: #f7f7f7; + --color-muted: #9c9c9c; + --color-text: #000000; + --color-white: #ffffff; + --font-fa: 'IranYekan', Tahoma, sans-serif; + --font-en: 'Montserrat', sans-serif; + --max-width: 1200px; + --radius: 5px; +} + +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + margin: 0; + min-height: 100vh; + font-family: var(--font-fa); + font-weight: 300; + color: var(--color-text); + background: var(--color-white); + -webkit-font-smoothing: antialiased; +} + +#root { + min-height: 100vh; +} + +a { + color: inherit; + text-decoration: none; +} + +button { + font: inherit; + cursor: pointer; + border: none; + background: none; +} + +img { + max-width: 100%; + display: block; +} + +ul { + margin: 0; + padding: 0; + list-style: none; +} + +p { + margin: 0; +} + +h1, +h2, +h3, +h4 { + margin: 0; + font-weight: 500; +} + +.app { + min-height: 100vh; + display: flex; + flex-direction: column; + background: var(--color-gray-bg); +} + +.app main { + flex: 1; +} + +.container { + width: min(100% - 2rem, var(--max-width)); + margin-inline: auto; +} + +.en { + font-family: var(--font-en); +} + +.text-primary { + color: var(--color-navy); +} + +.text-gradient { + background: linear-gradient(90deg, #a6caff, #2c86ed); + -webkit-background-clip: text; + background-clip: text; + color: transparent; + font-weight: 500; +} + +.medium { + font-weight: 500; +} diff --git a/src/lib/api.ts b/src/lib/api.ts new file mode 100644 index 0000000..15f41b1 --- /dev/null +++ b/src/lib/api.ts @@ -0,0 +1,109 @@ +const API_BASE = + (import.meta.env.VITE_API_BASE_URL as string | undefined)?.replace(/\/$/, '') ?? + 'http://api.madomotor.com:3001/api/v1' + +export class ApiError extends Error { + status: number + constructor(status: number, message: string) { + super(message) + this.status = status + } +} + +async function request(path: string, options: RequestInit = {}): Promise { + const response = await fetch(`${API_BASE}${path}`, { + ...options, + headers: { + ...(options.body ? { 'Content-Type': 'application/json' } : {}), + ...(options.headers ?? {}), + }, + }) + + const payload = await response.json().catch(() => null) + if (!response.ok) { + const message = + typeof payload?.message === 'string' + ? payload.message + : Array.isArray(payload?.message) + ? payload.message.join('، ') + : 'خطایی رخ داد' + throw new ApiError(response.status, message) + } + return payload as T +} + +export type PublicProduct = { + id: string + nameFa: string + nameEn: string + price: number + stock: number + category: string + imageUrl: string | null + createdAt: string + updatedAt?: string +} + +export type PublicBlog = { + id: string + title: string + summary: string + author: string + imageUrl: string | null + createdAt: string +} + +export type PublicBlogPost = PublicBlog & { + body: string + published: boolean + updatedAt: string +} + +export type PublicRepresentation = { + id: string + name: string + owner: string + mobile: string + province: string + city: string + address: string + landline: string + createdAt: string +} + +export const listPublicProducts = () => request('/public/products') +export const getPublicProduct = (id: string) => request(`/public/products/${id}`) +export const listPublicBlogs = () => request('/public/blogs') +export const getPublicBlog = (id: string) => request(`/public/blogs/${id}`) +export const listPublicRepresentations = () => + request('/public/representations') + +export function submitRepresentationRequest(body: { + firstName: string + lastName: string + nationalCode?: string + mobile: string + province?: string + city: string + landline?: string + address?: string + postalCode?: string + description?: string +}) { + return request<{ id: string; name: string; city: string; status: string }>( + '/public/representations/request', + { method: 'POST', body: JSON.stringify(body) }, + ) +} + +export function formatFaDate(iso: string) { + try { + return new Intl.DateTimeFormat('fa-IR', { + day: 'numeric', + month: 'long', + year: 'numeric', + }).format(new Date(iso)) + } catch { + return iso + } +} diff --git a/src/lib/faDigits.ts b/src/lib/faDigits.ts new file mode 100644 index 0000000..58368a1 --- /dev/null +++ b/src/lib/faDigits.ts @@ -0,0 +1,6 @@ +const FA_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'] as const + +/** Convert Latin digits in a string to Persian digits. */ +export function toFaDigits(value: string | number): string { + return String(value).replace(/\d/g, (d) => FA_DIGITS[Number(d)]) +} diff --git a/src/lib/paths.ts b/src/lib/paths.ts new file mode 100644 index 0000000..b047931 --- /dev/null +++ b/src/lib/paths.ts @@ -0,0 +1,34 @@ +export const SITE_ORIGIN = 'https://madomotor.ir' + +export const paths = { + home: '/', + products: '/product/list/محصولات', + blog: '/blog/list/اخبار-و-مقالات', + dealers: '/representation/list/نمایندگی-ها', + representationRequest: '/representation/request', + contact: '/contactus/تماس-با-ما', + about: '/aboutus/درباره-ما', + landing130: '/landing/mado-130/موتور-برقی-مادو-۱۳۰', + landing230: '/landing/mado-230/موتور-برقی-مادو-۲۳۰', + landing330: '/landing/mado-330/موتور-برقی-مادو-۳۳۰', +} as const + +export function blogPostPath(id: string, title: string) { + return `/blog/${id}/${toSlug(title)}` +} + +export function productPath(id: string, nameFa: string) { + return `/product/${id}/${toSlug(nameFa)}` +} + +export function toSlug(value: string) { + return value + .trim() + .replace(/\s+/g, '-') + .replace(/[^\u0600-\u06FFa-zA-Z0-9-]/g, '') +} + +export function absoluteUrl(path: string) { + if (path.startsWith('http')) return path + return `${SITE_ORIGIN}${path.startsWith('/') ? path : `/${path}`}` +} diff --git a/src/lib/phones.ts b/src/lib/phones.ts new file mode 100644 index 0000000..558e4e2 --- /dev/null +++ b/src/lib/phones.ts @@ -0,0 +1,67 @@ +const FA_TO_EN: Record = { + '۰': '0', + '۱': '1', + '۲': '2', + '۳': '3', + '۴': '4', + '۵': '5', + '۶': '6', + '۷': '7', + '۸': '8', + '۹': '9', + '٠': '0', + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', +} + +export type PhoneKind = 'mobile' | 'landline' + +export type DealerPhone = { + kind: PhoneKind + display: string + href: string +} + +export function normalizePhoneDigits(raw: string): string { + const latin = raw.replace(/[۰-۹٠-٩]/g, (d) => FA_TO_EN[d] ?? d) + let digits = latin.replace(/\D/g, '') + if (digits.startsWith('0098')) digits = digits.slice(4) + else if (digits.startsWith('98') && digits.length > 10) digits = digits.slice(2) + if (digits.length === 10) digits = `0${digits}` + return digits +} + +export function isMobileDigits(digits: string): boolean { + return /^09\d{9}$/.test(digits) +} + +export function dealerPhones(mobile: string, landline: string): DealerPhone[] { + const seen = new Set() + const list: DealerPhone[] = [] + + const add = (raw: string | null | undefined) => { + const trimmed = (raw ?? '').trim() + if (!trimmed || trimmed === '\\N') return + const digits = normalizePhoneDigits(trimmed) + if (digits.length < 8) return + if (seen.has(digits)) return + seen.add(digits) + const kind: PhoneKind = isMobileDigits(digits) ? 'mobile' : 'landline' + list.push({ + kind, + display: trimmed, + href: `tel:${digits}`, + }) + } + + add(mobile) + add(landline) + return list +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..8e0deb1 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,13 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import { BrowserRouter } from 'react-router-dom' +import App from './App' +import './index.css' + +createRoot(document.getElementById('root')!).render( + + + + + , +) diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx new file mode 100644 index 0000000..45d8916 --- /dev/null +++ b/src/pages/AboutPage.tsx @@ -0,0 +1,75 @@ +import { Link } from 'react-router-dom' +import SeoHead from '../components/SeoHead' +import PrimaryButton from '../components/PrimaryButton' +import { paths } from '../lib/paths' +import infoImage from '../assets/images/info.png' +import feature1 from '../assets/images/icons/1.png' +import feature2 from '../assets/images/icons/2.png' +import feature3 from '../assets/images/icons/3.png' +import feature4 from '../assets/images/icons/4.png' +import './CatalogPages.css' +import './StaticPages.css' + +const features = [ + { icon: feature1, title: 'خدمات پس از فروش', subtitle: 'به گستردگی ایران' }, + { icon: feature2, title: 'قیمت بسیار مناسب', subtitle: 'خرید بدون واسطه' }, + { icon: feature3, title: 'موتور تمام برقی', subtitle: 'لذت آرامش' }, + { icon: feature4, title: 'شارژ سریع', subtitle: 'آماده به کار' }, +] + +export default function AboutPage() { + return ( +
+ +
+
+

+ درباره مادو موتور بیشتر بدانید +

+
+ +
+ موتور برقی مادو +
+

+ درباره مادو موتور +

+

+ شرکت تولید مادو موتور در زمینه تولید و مونتاژ موتورسیکلت برقی به صورت تخصصی و با + سرمایه‌گذاری تقریبی ۹۰۰ میلیارد تومان و ایجاد زیر ساخت لازم برای تولید موتور سیکلت + برقی فعالیت خود را از سال ۱۳۹۵ آغاز نموده است. +

+ +
+
+ +

+ اکنون به یاری خداوند موفق به بومی سازی بسیاری از قطعات موتورسیکلت برقی در داخل کشور شده + بطوریکه بیش از ۹۰ قطعه از ۱۰۰ قطعه اصلی موتورسیکلت با سرمایه گذاری این شرکت در داخل کشور + عزیزمان تولید می‌گردد که سه قطعه اصلی شامل: موتور (قوای محرکه)، باطری و ECU در داخل شرکت + های زیر مجموعه این شرکت تولید می‌گردد که منجر به اشتغال به کار مستقیم و غیر مستقیم حدود + ۱۰۰ نفر گردیده و نتیجه این حجم از بومی سازی، کاهش قیمت تمام شده برای مشتری نهایی و خدمات + پس از فروش قابل قبول و مناسب گردیده است. +

+ +
+ {features.map((item) => ( +
+ +

{item.title}

+

{item.subtitle}

+
+ ))} +
+ +
+ بازگشت به صفحه اصلی +
+
+
+ ) +} diff --git a/src/pages/BlogListPage.tsx b/src/pages/BlogListPage.tsx new file mode 100644 index 0000000..b87ad88 --- /dev/null +++ b/src/pages/BlogListPage.tsx @@ -0,0 +1,71 @@ +import { useEffect, useState } from 'react' +import { Link } from 'react-router-dom' +import SeoHead from '../components/SeoHead' +import { ApiError, formatFaDate, listPublicBlogs, type PublicBlog } from '../lib/api' +import { blogPostPath, paths } from '../lib/paths' +import BlogCover from '../components/BlogCover' +import './CatalogPages.css' + +export default function BlogListPage() { + const [posts, setPosts] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + + useEffect(() => { + void (async () => { + try { + setPosts(await listPublicBlogs()) + } catch (err) { + setError(err instanceof ApiError ? err.message : 'بارگذاری مطالب ناموفق بود') + } finally { + setLoading(false) + } + })() + }, []) + + return ( +
+ +
+
+

اخبار و مطالب

+

آخرین نوشته‌های مادو موتور

+
+ + {loading ?

در حال بارگذاری...

: null} + {error ?

{error}

: null} + + {!loading && !error ? ( +
+ {posts.length === 0 ? ( +

مطلبی برای نمایش وجود ندارد

+ ) : null} + {posts.map((post) => ( + +
+ +
+
+

{post.title}

+

{post.summary}

+
+ {post.author || 'مادو موتور'} + {formatFaDate(post.createdAt)} +
+
+ + ))} +
+ ) : null} + +
+ بازگشت به صفحه اصلی +
+
+
+ ) +} diff --git a/src/pages/BlogPostPage.css b/src/pages/BlogPostPage.css new file mode 100644 index 0000000..8fbe46e --- /dev/null +++ b/src/pages/BlogPostPage.css @@ -0,0 +1,39 @@ +.blog-post__card { + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 12px; + padding: 28px 24px; +} + +.blog-post__cover { + margin: -28px -24px 24px; + overflow: hidden; + border-radius: 12px 12px 0 0; +} + +.blog-post__card h1 { + margin: 0 0 12px; + font-size: 22px; + font-weight: 500; +} + +.blog-post__meta { + display: flex; + justify-content: space-between; + gap: 12px; + color: #888; + font-size: 13px; + margin-bottom: 24px; +} + +.blog-post__body { + line-height: 2; + color: #444; + text-align: justify; +} + +.blog-post__body img { + max-width: 100%; + height: auto; + border-radius: 8px; +} diff --git a/src/pages/BlogPostPage.tsx b/src/pages/BlogPostPage.tsx new file mode 100644 index 0000000..8edc573 --- /dev/null +++ b/src/pages/BlogPostPage.tsx @@ -0,0 +1,77 @@ +import { useEffect, useState } from 'react' +import { Link, useParams } from 'react-router-dom' +import SeoHead from '../components/SeoHead' +import BlogCover from '../components/BlogCover' +import { ApiError, formatFaDate, getPublicBlog, type PublicBlogPost } from '../lib/api' +import { blogPostPath, paths } from '../lib/paths' +import './CatalogPages.css' +import './BlogPostPage.css' + +export default function BlogPostPage() { + const { id } = useParams() + const [post, setPost] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState('') + + useEffect(() => { + if (!id) return + void (async () => { + setLoading(true) + setError('') + try { + setPost(await getPublicBlog(id)) + } catch (err) { + setError(err instanceof ApiError ? err.message : 'بارگذاری مطلب ناموفق بود') + setPost(null) + } finally { + setLoading(false) + } + })() + }, [id]) + + const title = post?.title || 'نمایش مطلب' + const description = post?.summary || 'مطلب مادو موتور' + + return ( +
+ +
+ {loading ?

در حال بارگذاری...

: null} + {error ?

{error}

: null} + + {post ? ( +
+ {post.imageUrl ? ( +
+ +
+ ) : null} +

{post.title}

+
+ {post.author || 'مادو موتور'} + {formatFaDate(post.createdAt)} +
+ {post.body.includes('<') ? ( +
+ ) : ( +
+ {post.body.split('\n').map((line, index) => ( +

{line}

+ ))} +
+ )} +
+ ) : null} + +
+ بازگشت به اخبار و مطالب +
+
+
+ ) +} diff --git a/src/pages/CatalogPages.css b/src/pages/CatalogPages.css new file mode 100644 index 0000000..15f4db5 --- /dev/null +++ b/src/pages/CatalogPages.css @@ -0,0 +1,326 @@ +.catalog { + padding: 48px 0 80px; + background: linear-gradient(180deg, #f7f9fc 0%, #fff 40%); + min-height: 70vh; +} + +.catalog__head { + display: flex; + flex-direction: column; + gap: 10px; + margin-bottom: 28px; +} + +.catalog__head h1 { + margin: 0; + font-size: 28px; + font-weight: 700; + color: var(--color-navy); +} + +.catalog__head p { + margin: 0; + color: var(--color-muted); +} + +.dealers-search { + display: block; + width: 100%; + margin-top: 8px; +} + +.dealers-search input { + width: 100%; + box-sizing: border-box; + border: 1px solid var(--color-navy); + border-radius: var(--radius, 8px); + background: #fff; + padding: 14px 16px; + font: inherit; + font-size: 15px; + color: var(--color-navy); + outline: none; +} + +.dealers-search input::placeholder { + color: #9aa3b2; +} + +.dealers-search input:focus { + border-color: var(--color-blue); + box-shadow: 0 0 0 3px rgba(37, 99, 176, 0.12); +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.catalog__status { + padding: 28px 0; + color: var(--color-muted); +} + +.catalog__status--error { + color: #d32f2f; +} + +.catalog__grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} + +.catalog__grid--blogs { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.catalog-card { + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 12px; + overflow: hidden; + display: flex; + flex-direction: column; + color: inherit; + text-decoration: none; +} + +.catalog-card__media { + aspect-ratio: 4 / 3; + background: #f0f3f8; +} + +.catalog-card__media img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.catalog-card__placeholder { + width: 100%; + height: 100%; + display: grid; + place-items: center; + color: var(--color-blue); + font-weight: 700; + font-size: 28px; +} + +.catalog-card__media .blog-cover { + height: 100%; + border-radius: 0; +} + +.catalog-card__body { + padding: 16px 18px 18px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.catalog-card__body h2 { + margin: 0; + font-size: 18px; + font-weight: 700; +} + +.catalog-card__body p { + margin: 0; + color: #666; + line-height: 1.7; + font-size: 14px; +} + +.catalog-card__tag { + font-size: 12px; + color: var(--color-blue); +} + +.catalog-card__price { + display: flex; + align-items: baseline; + gap: 6px; + margin-top: 8px; +} + +.catalog-card__price strong { + font-family: var(--font-en); + font-size: 20px; + color: var(--color-navy); +} + +.catalog-card__price span, +.catalog-card__meta { + font-size: 12px; + color: var(--color-muted); +} + +.catalog-card__meta { + display: flex; + justify-content: space-between; + gap: 12px; + margin-top: auto; + padding-top: 10px; +} + +.dealers-cta { + margin-top: 36px; + background: #fff; + border-top: 1px solid #e8e8e8; + border-bottom: 1px solid #e8e8e8; + padding: 18px 0; +} + +.dealers-cta__inner { + display: flex; + flex-wrap: wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; +} + +.dealers-cta__inner p { + margin: 0; + color: #333; + font-size: 16px; +} + +.catalog__foot { + margin-top: 28px; +} + +.catalog__foot a { + color: var(--color-blue); + text-decoration: none; +} + +.dealers-filter { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: 20px; +} + +.dealers-filter button { + border: 1px solid #dde3ee; + background: #fff; + border-radius: 999px; + padding: 8px 14px; + cursor: pointer; + font: inherit; +} + +.dealers-filter button.is-active { + background: var(--color-blue); + border-color: var(--color-blue); + color: #fff; +} + +.dealers-list { + display: flex; + flex-direction: column; + gap: 12px; +} + +.dealer-row { + display: grid; + grid-template-columns: 1.2fr 1.4fr auto; + gap: 16px; + align-items: center; + padding: 18px 20px; + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 12px; +} + +.dealer-row h2 { + margin: 0 0 4px; + font-size: 18px; +} + +.dealer-row p { + margin: 0; + color: #666; + font-size: 14px; +} + +.dealer-row__contacts { + display: flex; + flex-direction: column; + gap: 8px; + min-width: 11.5rem; +} + +.dealer-row__phone { + display: inline-flex; + align-items: center; + justify-content: flex-start; + gap: 8px; + text-decoration: none; + font-family: var(--font-en); + font-size: 14px; + line-height: 1; +} + +.dealer-row__phone--mobile { + color: var(--color-blue); +} + +.dealer-row__phone--landline { + color: #222; +} + +.dealer-row__phone-icon { + width: 16px; + height: 16px; + flex-shrink: 0; +} + +.product-detail__card { + display: grid; + grid-template-columns: minmax(0, 420px) minmax(0, 1fr); + gap: 28px; + background: #fff; + border: 1px solid #e8e8e8; + border-radius: 12px; + overflow: hidden; + padding: 20px; +} + +.product-detail__media { + aspect-ratio: 4 / 3; + background: #f0f3f8; + border-radius: 8px; + overflow: hidden; +} + +.product-detail__media img { + width: 100%; + height: 100%; + object-fit: cover; + display: block; +} + +.product-detail__body h1 { + margin: 12px 0 8px; + font-size: 26px; + color: var(--color-navy); +} + +@media (max-width: 900px) { + .catalog__grid, + .catalog__grid--blogs, + .dealer-row, + .product-detail__card { + grid-template-columns: 1fr; + } +} diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx new file mode 100644 index 0000000..1ae3d53 --- /dev/null +++ b/src/pages/ContactPage.tsx @@ -0,0 +1,74 @@ +import { Link } from 'react-router-dom' +import SeoHead from '../components/SeoHead' +import { paths } from '../lib/paths' +import { toFaDigits } from '../lib/faDigits' +import './CatalogPages.css' +import './StaticPages.css' + +export default function ContactPage() { + return ( +
+ +
+
+

+ روش ارتباطی با مادو موتور +

+
+ + + +