commit ff07c46630bf4e38a408f06548135415f3db5844 Author: MihirM17 Date: Sun Jul 5 18:58:13 2026 +0530 First Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# 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? 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..d6af7e3 --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "options": { + "typeAware": true + }, + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} +``` + +See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. diff --git a/index.html b/index.html new file mode 100644 index 0000000..243953e --- /dev/null +++ b/index.html @@ -0,0 +1,18 @@ + + + + + + + + + + + + Mani Candles | Luxury Handmade Soy Candles India + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0a61145 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2644 @@ +{ + "name": "frontend", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "frontend", + "version": "0.0.0", + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", + "@tanstack/react-query": "^5.101.2", + "axios": "^1.18.1", + "framer-motion": "^12.42.2", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-helmet-async": "^3.0.0", + "react-hook-form": "^7.81.0", + "react-icons": "^5.7.0", + "react-router-dom": "^7.18.1", + "swiper": "^14.0.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", + "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", + "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.14.0", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", + "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.14.1", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", + "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz", + "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@mui/core-downloads-tracker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-9.2.0.tgz", + "integrity": "sha512-+XMav+ZaXkZKUFUgzjrfMEedfyJKxxviAske2q8N8CWDMeqZdDU2lWMkiUPiB388hGaDqhwvOAwkrsc/pUyp8g==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/icons-material": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-9.2.0.tgz", + "integrity": "sha512-VgBd3z7Qc3vd/thcNSMC03nHRh/U4DzMUd+1dRyJTbm/hGo7+N6N4GDuJZDNHa6LZhhwG6Cu1X3DNvrVv8sNag==", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@mui/material": "^9.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-9.2.0.tgz", + "integrity": "sha512-+YTRSgGKGrrRo2XJZXs7JRA6qHoHWvNtxyqxnrRJTBmIuLOUpxxh7m4G9lF4tWberxGFY+EqkkRPgJCl+fSMJg==", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/core-downloads-tracker": "^9.2.0", + "@mui/system": "^9.2.0", + "@mui/types": "^9.1.1", + "@mui/utils": "^9.2.0", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.12", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1", + "react-is": "^19.2.6", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^9.2.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-9.2.0.tgz", + "integrity": "sha512-w9wpyDxGPGnAACPB2hKhCDmILJIAvQxrfjUbIAEa0AznX1rOjaz5N+yB1uuw8ixnJcpEh/tPbD9oEe19wcWPHw==", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/utils": "^9.2.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-9.1.1.tgz", + "integrity": "sha512-neaYKdJfvEG54q8efHLJR7swpHG/gfSv9xGqW5iTSMsubD7yPCPFrhVBt284j1DOF3uZaaDJSHQL7gz6jGF21Q==", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@emotion/cache": "^11.14.0", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-9.2.0.tgz", + "integrity": "sha512-YvUJwKoGVtbnOm2PyPi5TvX2d1rOA6sqSpEWVs4WmXNIaFTuYmNUaVdU2o1NKUEe31URnD3E8ZVUMcsLQXwcYg==", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/private-theming": "^9.2.0", + "@mui/styled-engine": "^9.1.1", + "@mui/types": "^9.1.1", + "@mui/utils": "^9.2.0", + "clsx": "^2.1.1", + "csstype": "^3.2.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-9.1.1.tgz", + "integrity": "sha512-Zjt7u8wNvDg40rPTGoL+TnfkpuSKjwubsNSFRH1KAVZLcaV4I3AFNHIFbvH7p4F3alEibSbdd90xAgn5Rnfndg==", + "dependencies": { + "@babel/runtime": "^7.29.2" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-9.2.0.tgz", + "integrity": "sha512-OsUH5zhlSOM4xmLl53+agug1M1UyWb4zxFxWQCqwKTKUeQPvTENtg3JhrroBD2qpCLKsX5W/DYGERJ4mBUbc8g==", + "dependencies": { + "@babel/runtime": "^7.29.2", + "@mui/types": "^9.1.1", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.6" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.138.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.138.0.tgz", + "integrity": "sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz", + "integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz", + "integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz", + "integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz", + "integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz", + "integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz", + "integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz", + "integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz", + "integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz", + "integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz", + "integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz", + "integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz", + "integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz", + "integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz", + "integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz", + "integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz", + "integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz", + "integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz", + "integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz", + "integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.4.tgz", + "integrity": "sha512-EZLpf/8y7GXkkra90ML47kzik/GMP3EMcE9bPyHmRfxLC6z9+aW5A8poCsoxjrT5GfEcNAAvWwUHjvP1pUQkfw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.4.tgz", + "integrity": "sha512-aUi+HBvmYb7j8krl1+qJgkG8C17fO79gk3c+jPw4S8glRFc1DTija9S3EyaTSQUm5GJXYKDAsugBEhFHH2vYiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.4.tgz", + "integrity": "sha512-F7hHC3gwY11+vByKPRWqwGbeXWVgKmL+pTGCinaEhdihzBV2aQ0fvZOch9cXYUOKuKKq429HeYXOqQLc7wFCEg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.4.tgz", + "integrity": "sha512-sI5yw+7s92SK6odiEhD5lKCBlWcpjHS5qyqpVQbZAJ0fIzEUXrmbl3DH2ybR3PZogulNJF+COLtmA8hUfvkCCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.4.tgz", + "integrity": "sha512-mCi0OKgEieFircrtVYmQAFGszRtMnZ6fpZAXrxanXAu7lqZcsK1E1RAaZNG0uKAnxox3B1f4EyQNnoyMfN1vAA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.4.tgz", + "integrity": "sha512-B9Ial3Kv5sh0SHnB1g/QWcUQCEvCF6QKGAl4zXypYj65mVI+B4AhFBwPtSN7pDrJeIx8Z7zdy4ntx+wQABom7w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.4.tgz", + "integrity": "sha512-lZVym0PuHE1KZ22gmFTC15lAkrg9iTszR617oYRB/iPY1A56ywoJzVKOJBKaot5RiikCObmur6pogpse3gRcng==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.4.tgz", + "integrity": "sha512-t2DNiLJWNTbnEHyUzTumldML6ET4/g16467LZoDDJ3tSxGvguL5/NyC2lCsNKuyRycg9XeDQF5SSv+TNOhQEXg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.4.tgz", + "integrity": "sha512-0WIRnL1Uw4BvTZRLQt+PVgo6ZKTJadlC2btP+/EOXv2f/DWbY0rEgl+y834mIVwP1FkTlWVTrGGJXf12lru7EQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.4.tgz", + "integrity": "sha512-JWtGshGfX+oENAKonoNkqEJX+7hC8yfhi9GUyPX1VX4mdh1y5r+ZiJLR5XzAB0aoP6s/PcILsGjKq8O0mm24bw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.4.tgz", + "integrity": "sha512-rT6yQcxUuXs4CnbofqwHRRV0iem349rLMYpTjkgQGLjrY4ado/eDzwPZPTCgTOlF6Nkp8NEv70yLMTn6qkWxsQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.4.tgz", + "integrity": "sha512-KXMGoboq5cyaCQjDA4GLuRiOwBQ0EyFnJoVViLeZ45/3rFItRODEr+NdsBcVpll40hhNArlm/speWGRvj08LzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.4.tgz", + "integrity": "sha512-5K83rb36oJiY7BCyE9zLZtGcPV4g5wvq+xwdO0XPIwDVZI8cyB/AUjkNXGb92/rnmezEkjMOpgY61rtwjQtFwg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.4.tgz", + "integrity": "sha512-PnWBtw3TV5KOg69HQQDR0mnQuyCmSGR2pAB4DC1rPF808fgKeTUMj2EOEyKATpgiuxuR5APQmiDO7PDgEjTFSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.4.tgz", + "integrity": "sha512-M1lpniBePobTfsa7Ks9a199e1akxsXn+GYBUKsEzv3YFzOm1HJAMNwKI3qr0Zq+mxwx9gOZoTdP1yXRYsZUocQ==", + "cpu": [ + "x64" + ], + "dev": true, + "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 + }, + "node_modules/@tanstack/query-core": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.2.tgz", + "integrity": "sha512-hH5MLoJhF7KaIGd7q3xTXGXvslI+GYlM1Z/35aSHHWaCJWB7XvTSHYuV3eM7tw+aE0mT/xMro4M4Q9rCGHT0lw==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.2.tgz", + "integrity": "sha512-seDkr6kzGzX1okaaTtZPtgA688CDPlXUz1C6xSg0ESqn04Vuc8tlrYms1s3de+znBqhPVxFRfpAfUf+6XvfPWg==", + "dependencies": { + "@tanstack/query-core": "5.101.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==" + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.12", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", + "integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==", + "peerDependencies": { + "@types/react": "*" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "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", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/axios": { + "version": "1.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", + "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "dependencies": { + "follow-redirects": "^1.16.0", + "form-data": "^4.0.5", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, + "node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "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==" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, + "node_modules/follow-redirects": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/framer-motion": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.2.tgz", + "integrity": "sha512-5XY9luDiu0oHfHBjpDthFMh0ES+122w6p/papSJBweMkO8Sn+PW2QaEgRblQBpWFnuvZS5qvarpt/hO2pjGmnw==", + "dependencies": { + "motion-dom": "^12.42.2", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/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, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "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.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/motion-dom": { + "version": "12.42.2", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.2.tgz", + "integrity": "sha512-5gIMWLp/PycBtJRJWRgjxke5n8dlvkSn2DrYW+tr3XcqAZY1xZh6BJyooJXCM8wdfM7wfMjkBJNLge1CKPUIRA==", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/oxlint": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.72.0.tgz", + "integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==", + "dev": true, + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.72.0", + "@oxlint/binding-android-arm64": "1.72.0", + "@oxlint/binding-darwin-arm64": "1.72.0", + "@oxlint/binding-darwin-x64": "1.72.0", + "@oxlint/binding-freebsd-x64": "1.72.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.72.0", + "@oxlint/binding-linux-arm-musleabihf": "1.72.0", + "@oxlint/binding-linux-arm64-gnu": "1.72.0", + "@oxlint/binding-linux-arm64-musl": "1.72.0", + "@oxlint/binding-linux-ppc64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-musl": "1.72.0", + "@oxlint/binding-linux-s390x-gnu": "1.72.0", + "@oxlint/binding-linux-x64-gnu": "1.72.0", + "@oxlint/binding-linux-x64-musl": "1.72.0", + "@oxlint/binding-openharmony-arm64": "1.72.0", + "@oxlint/binding-win32-arm64-msvc": "1.72.0", + "@oxlint/binding-win32-ia32-msvc": "1.72.0", + "@oxlint/binding-win32-x64-msvc": "1.72.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "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" + } + ], + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-fast-compare": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-3.2.2.tgz", + "integrity": "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==" + }, + "node_modules/react-helmet-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-3.0.0.tgz", + "integrity": "sha512-nA3IEZfXiclgrz4KLxAhqJqIfFDuvzQwlKwpdmzZIuC1KNSghDEIXmyU0TKtbM+NafnkICcwx8CECFrZ/sL/1w==", + "dependencies": { + "invariant": "^2.2.4", + "react-fast-compare": "^3.2.2", + "shallowequal": "^1.1.0" + }, + "peerDependencies": { + "react": "^16.6.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-hook-form": { + "version": "7.81.0", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.81.0.tgz", + "integrity": "sha512-ocbmr2p5KBMoAfj4WCUvped33lVi1Kd5DuDUvQDnB6VEAacOjPI/jMbtDdbhco4y9ct4xUuCmMY0b/C9L0QHjw==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/react-icons": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.7.0.tgz", + "integrity": "sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==", + "peerDependencies": { + "react": "*" + } + }, + "node_modules/react-is": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.7.tgz", + "integrity": "sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==" + }, + "node_modules/react-router": { + "version": "7.18.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz", + "integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==", + "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.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz", + "integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==", + "dependencies": { + "react-router": "7.18.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/rolldown": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.4.tgz", + "integrity": "sha512-IjZYiLxZwpnhwhdBH2ugdTGVSdhCQUmLxLoqyjiL0JxYjyRst+5a0P3xfrTxJ5F638j4Mvvw5FAX5XE6eHpXbA==", + "dev": true, + "dependencies": { + "@oxc-project/types": "=0.138.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.4", + "@rolldown/binding-darwin-arm64": "1.1.4", + "@rolldown/binding-darwin-x64": "1.1.4", + "@rolldown/binding-freebsd-x64": "1.1.4", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.4", + "@rolldown/binding-linux-arm64-gnu": "1.1.4", + "@rolldown/binding-linux-arm64-musl": "1.1.4", + "@rolldown/binding-linux-ppc64-gnu": "1.1.4", + "@rolldown/binding-linux-s390x-gnu": "1.1.4", + "@rolldown/binding-linux-x64-gnu": "1.1.4", + "@rolldown/binding-linux-x64-musl": "1.1.4", + "@rolldown/binding-openharmony-arm64": "1.1.4", + "@rolldown/binding-wasm32-wasi": "1.1.4", + "@rolldown/binding-win32-arm64-msvc": "1.1.4", + "@rolldown/binding-win32-x64-msvc": "1.1.4" + } + }, + "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==" + }, + "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==" + }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, + "node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/swiper": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-14.0.1.tgz", + "integrity": "sha512-dibQY282S5rn6S9Ba+GTIvofyY4Qujy526a0cPaJnjR2S18Z7HMGKZBr8k/3ukboeKW/ig5h0q//7CYip0MCQw==", + "funding": [ + { + "type": "custom", + "url": "https://sponsors.nolimits4web.com" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" + } + ], + "engines": { + "node": ">= 4.7.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, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, + "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, + "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 + }, + "node_modules/vite": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.3.tgz", + "integrity": "sha512-Ds+gBRbj0lwRO2Y5hwnUBdxSwlAve9LeRyU4sNnAr0ewW0gWF0n5bgXgUzbgZ49MV9BVUAQUFYVcDUcilUExMA==", + "dev": true, + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "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.3.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 + } + } + }, + "node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "engines": { + "node": ">= 6" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3001b8d --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "name": "frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", + "@tanstack/react-query": "^5.101.2", + "axios": "^1.18.1", + "framer-motion": "^12.42.2", + "react": "^19.2.7", + "react-dom": "^19.2.7", + "react-helmet-async": "^3.0.0", + "react-hook-form": "^7.81.0", + "react-icons": "^5.7.0", + "react-router-dom": "^7.18.1", + "swiper": "^14.0.1" + }, + "devDependencies": { + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1" + } +} 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.jpg b/public/logo.jpg new file mode 100644 index 0000000..6908d93 Binary files /dev/null and b/public/logo.jpg differ diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..c2728e6 --- /dev/null +++ b/src/App.css @@ -0,0 +1,4 @@ +/* + Deprecated template file. + Global styles have been migrated to src/index.css, and Material UI is utilized for theme design variables. +*/ diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..04b48f1 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,4 @@ +// Deprecated template file. +// The main application wrapper and routing configuration has been relocated to src/app/App.tsx +// in accordance with feature-based architecture guidelines. +export default {}; diff --git a/src/app/App.tsx b/src/app/App.tsx new file mode 100644 index 0000000..4f9a9d6 --- /dev/null +++ b/src/app/App.tsx @@ -0,0 +1,51 @@ +import React, { useState } from 'react'; +import { BrowserRouter } from 'react-router-dom'; +import { ThemeProvider } from '@mui/material/styles'; +import { CssBaseline } from '@mui/material'; +import { HelmetProvider } from 'react-helmet-async'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import theme from '../theme/theme'; +import { CartProvider } from '../contexts/CartContext'; +import { WishlistProvider } from '../contexts/WishlistContext'; +import AppRoutes from '../routes/AppRoutes'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + retry: 1, + }, + }, +}); + +export const App: React.FC = () => { + // Global wholesale vs retail context state + const [isB2BMode, setIsB2BMode] = useState(() => { + const saved = localStorage.getItem('mani_candles_b2b_mode'); + return saved === 'true'; + }); + + const handleSetB2BMode = (val: boolean) => { + localStorage.setItem('mani_candles_b2b_mode', val ? 'true' : 'false'); + setIsB2BMode(val); + }; + + return ( + + + + + + + + + + + + + + + ); +}; + +export default App; 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/logo.jpg b/src/assets/logo.jpg new file mode 100644 index 0000000..6908d93 Binary files /dev/null and b/src/assets/logo.jpg differ diff --git a/src/assets/react.svg b/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/vite.svg b/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/src/components/common/AnnouncementBar.tsx b/src/components/common/AnnouncementBar.tsx new file mode 100644 index 0000000..514625e --- /dev/null +++ b/src/components/common/AnnouncementBar.tsx @@ -0,0 +1,58 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Typography } from '@mui/material'; + +const messages = [ + 'Free complimentary shipping across India on B2C orders above ₹999', + 'Corporate Gifting & Wedding return favors: Custom branding & wholesale prices available', + 'Design your own olfactory signature with our Custom Candle Builder', + 'Sustainable luxury: 100% natural hand-poured soy wax candles' +]; + +export const AnnouncementBar: React.FC = () => { + const [currentMessageIndex, setCurrentMessageIndex] = useState(0); + const [fade, setFade] = useState(true); + + useEffect(() => { + const rotateInterval = setInterval(() => { + setFade(false); + setTimeout(() => { + setCurrentMessageIndex((prevIndex) => (prevIndex + 1) % messages.length); + setFade(true); + }, 500); // match transition duration + }, 5000); + + return () => clearInterval(rotateInterval); + }, []); + + return ( + + + {messages[currentMessageIndex]} + + + ); +}; + +export default AnnouncementBar; diff --git a/src/components/common/Breadcrumbs.tsx b/src/components/common/Breadcrumbs.tsx new file mode 100644 index 0000000..887edbb --- /dev/null +++ b/src/components/common/Breadcrumbs.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { Breadcrumbs as MuiBreadcrumbs, Link, Typography, Container, Box } from '@mui/material'; +import { Link as RouterLink, useLocation } from 'react-router-dom'; +import { IoIosArrowForward } from 'react-icons/io'; + +export const Breadcrumbs: React.FC = () => { + const location = useLocation(); + const pathnames = location.pathname.split('/').filter((x) => x); + + // Don't show breadcrumbs on homepage + if (pathnames.length === 0) return null; + + return ( + + + } + aria-label="breadcrumb" + > + + Home + + {pathnames.map((value, index) => { + const last = index === pathnames.length - 1; + const to = `/${pathnames.slice(0, index + 1).join('/')}`; + const displayName = value + .replace(/-/g, ' ') + .replace(/\b\w/g, (c) => c.toUpperCase()); // Capitalize first letters + + return last ? ( + + {displayName} + + ) : ( + + {displayName} + + ); + })} + + + + ); +}; + +export default Breadcrumbs; diff --git a/src/components/common/CartDrawer.tsx b/src/components/common/CartDrawer.tsx new file mode 100644 index 0000000..4437a41 --- /dev/null +++ b/src/components/common/CartDrawer.tsx @@ -0,0 +1,256 @@ +import React, { useState } from 'react'; +import { + Drawer, + Box, + Typography, + IconButton, + List, + ListItem, + ListItemAvatar, + Avatar, + ListItemText, + ListItemSecondaryAction, + Button, + Divider, + Chip, + Dialog, + DialogTitle, + DialogContent, + DialogActions, +} from '@mui/material'; +import { MdClose, MdOutlineDeleteOutline, MdAdd, MdRemove } from 'react-icons/md'; +import { MdOutlineShoppingBag } from 'react-icons/md'; +import { useCart } from '../../contexts/CartContext'; +import { formatCurrency } from '../../utils/helpers'; +import { useNavigate } from 'react-router-dom'; + +export const CartDrawer: React.FC = () => { + const navigate = useNavigate(); + const { cartItems, isCartOpen, setIsCartOpen, removeFromCart, updateQuantity, cartTotal, clearCart } = useCart(); + const [checkoutSuccess, setCheckoutSuccess] = useState(false); + + const handleCheckout = () => { + // Simulate checkout action + setCheckoutSuccess(true); + }; + + const handleFinishCheckout = () => { + clearCart(); + setCheckoutSuccess(false); + setIsCartOpen(false); + navigate('/'); + }; + + return ( + <> + setIsCartOpen(false)} + disableScrollLock + slotProps={{ paper: { sx: { + width: { xs: '100%', sm: 440 }, + backgroundColor: 'background.default', + } } }} + > + {/* Drawer Header */} + + + Shopping Cart ({cartItems.length}) + + setIsCartOpen(false)}> + + + + + {/* Drawer Content */} + + {cartItems.length === 0 ? ( + + + + Your cart is empty + + + Add items to your cart from our shop or design a personalized blend in the custom workspace. + + + + ) : ( + + {cartItems.map((item, index) => { + const isB2B = item.mode === 'B2B'; + const unitPrice = isB2B ? item.product.b2bPrice : item.product.price; + return ( + + + + + + + + {item.product.name} + + + + } + secondary={ + + {/* Render customizations if custom candle */} + {item.customizations && ( + + + Jar: {item.customizations.jar} | Wax: {item.customizations.wax} + + + Blend: {item.customizations.scentBlend} + + {item.customizations.labelText && ( + + Label Text: "{item.customizations.labelText}" ({item.customizations.labelFont}) + + )} + + )} + + + {formatCurrency(unitPrice)} each + + + {/* Quantity Selector */} + + updateQuantity(item.product.id, item.quantity - 1, item.mode)} + sx={{ p: 0.5 }} + > + + + + {item.quantity} + + updateQuantity(item.product.id, item.quantity + 1, item.mode)} + sx={{ p: 0.5 }} + > + + + + + } + /> + + removeFromCart(item.product.id, item.mode)} + sx={{ color: 'text.secondary', '&:hover': { color: 'error.main' } }} + > + + + + + + + ); + })} + + )} + + + {/* Drawer Footer Summary */} + {cartItems.length > 0 && ( + + + + Subtotal + + + {formatCurrency(cartTotal)} + + + + * Shipping & taxes calculated at checkout. GST invoice option available for B2B orders. + + + + + + )} + + + {/* Checkout Simulation Dialog */} + + + Order Initiated + + + + Thank you for shopping with Mani Candles! + + + {cartItems.some(item => item.mode === 'B2B') + ? 'Our corporate B2B sales desk is generating a custom GST invoice. We will reach out to you within 2 business hours.' + : 'Your secure B2C payment portal has been verified. A tracking link is dispatched to your registered email.'} + + + Estimated Delivery: 3-5 Business Days across India. + + + + + + + + ); +}; + +export default CartDrawer; diff --git a/src/components/common/CookieConsent.tsx b/src/components/common/CookieConsent.tsx new file mode 100644 index 0000000..106807d --- /dev/null +++ b/src/components/common/CookieConsent.tsx @@ -0,0 +1,68 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Typography, Button, Slide } from '@mui/material'; + +export const CookieConsent: React.FC = () => { + const [open, setOpen] = useState(false); + + useEffect(() => { + const consent = localStorage.getItem('mani_candles_cookie_consent'); + if (!consent) { + // Show consent after a short delay + const timer = setTimeout(() => setOpen(true), 2000); + return () => clearTimeout(timer); + } + }, []); + + const handleAccept = () => { + localStorage.setItem('mani_candles_cookie_consent', 'accepted'); + setOpen(false); + }; + + return ( + + + + We use cookies to elevate your sensory browsing experience, analyze site traffic, and optimize our B2B and B2C services. By accepting, you consent to our privacy terms. + + + + + + + + ); +}; + +export default CookieConsent; diff --git a/src/components/common/Footer.tsx b/src/components/common/Footer.tsx new file mode 100644 index 0000000..7945162 --- /dev/null +++ b/src/components/common/Footer.tsx @@ -0,0 +1,211 @@ +import React, { useState } from 'react'; +import { Box, Container, Grid, Typography, Link, TextField, Button, Divider, IconButton } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { IoLogoInstagram, IoLogoFacebook, IoLogoPinterest, IoLogoTwitter } from 'react-icons/io'; + + +export const Footer: React.FC = () => { + const [email, setEmail] = useState(''); + const [subscribed, setSubscribed] = useState(false); + + const handleSubscribe = (e: React.FormEvent) => { + e.preventDefault(); + if (email) { + setSubscribed(true); + setEmail(''); + } + }; + + return ( + + + + {/* Brand Info */} + + + + + MANI CANDLES + + + + Artisanal hand-poured luxury candles crafted in small batches in India. Utilizing 100% natural soy and coconut wax blends, therapeutic grade botanical extracts, and lead-free wicks to illuminate your sanctuary cleanly and beautifully. + + + + + + + + + + + + + + + + + + {/* Quick Links (B2C) */} + + + SHOP + + + + Shop All + + + Classic Line + + + Luxury Collection + + + Seasonal Scents + + + Custom Builder + + + + + {/* Wholesaler & B2B Links */} + + + B2B SERVICES + + + + Corporate Gifting + + + Hotels & Resorts + + + Private Label / OEM + + + Custom Manufacture + + + Bulk Inquiries + + + + + {/* Newsletter Input */} + + + THE CIRCLE JOURNAL + + + Receive exclusive alerts on seasonal pourings, brand collaborations, slow-living guides, and special discount codes. + + + {subscribed ? ( + + Thank you for subscribing. Welcome to the circle. + + ) : ( +
+ + setEmail(e.target.value)} + required + variant="outlined" + size="small" + sx={{ + '& .MuiOutlinedInput-root': { + backgroundColor: 'rgba(255, 255, 255, 0.08)', + borderRadius: 1, + color: '#F5EFEB', + '& fieldset': { borderColor: 'rgba(255, 255, 255, 0.15)' }, + '&:hover fieldset': { borderColor: 'primary.light' }, + '&.Mui-focused fieldset': { borderColor: 'primary.light' }, + }, + '& input::placeholder': { color: '#F5EFEB', opacity: 0.5 }, + }} + /> + + +
+ )} +
+
+ + + + {/* Footer Bottom */} + + + + © {new Date().getFullYear()} Mani Candles Private Limited. All rights reserved. Made in India. + + + + + Privacy + + + Terms + + + Shipping + + + Refunds + + + +
+
+ ); +}; + +export default Footer; diff --git a/src/components/common/Header.tsx b/src/components/common/Header.tsx new file mode 100644 index 0000000..37620b1 --- /dev/null +++ b/src/components/common/Header.tsx @@ -0,0 +1,442 @@ +import React, { useState } from 'react'; +import { + AppBar, + Toolbar, + Typography, + Box, + IconButton, + Badge, + Button, + Menu, + MenuItem, + Drawer, + List, + ListItem, + ListItemButton, + ListItemText, + Divider, + Container, + FormControlLabel, + Switch, + TextField, + InputAdornment, +} from '@mui/material'; +import { Link as RouterLink, useNavigate } from 'react-router-dom'; +import { IoIosHeartEmpty, IoIosSearch, IoIosMenu, IoIosClose } from 'react-icons/io'; +import { MdOutlineShoppingBag } from 'react-icons/md'; +import { useCart } from '../../contexts/CartContext'; +import { useWishlist } from '../../contexts/WishlistContext'; + +export const Header: React.FC<{ isB2BMode: boolean; setIsB2BMode: (val: boolean) => void }> = ({ + isB2BMode, + setIsB2BMode, +}) => { + const navigate = useNavigate(); + const { cartCount, setIsCartOpen } = useCart(); + const { wishlistItems, setIsWishlistOpen } = useWishlist(); + + // Mobile navigation state + const [mobileOpen, setMobileOpen] = useState(false); + + // Search overlay state + const [searchOpen, setSearchOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(''); + + // Dropdown states + const [shopAnchor, setShopAnchor] = useState(null); + const [b2bAnchor, setB2BAnchor] = useState(null); + + const handleSearchSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery.trim()) { + navigate(`/shop?search=${encodeURIComponent(searchQuery)}`); + setSearchQuery(''); + setSearchOpen(false); + } + }; + + return ( + <> + + + + {/* Mobile Burger Menu */} + setMobileOpen(true)} + sx={{ display: { xs: 'flex', md: 'none' }, mr: 1 }} + > + + + + {/* Brand Logo */} + + + + MANI CANDLES + + + + {/* Desktop Navigation */} + + {/* Shop Link with dropdown */} + + setShopAnchor(null)} + disableScrollLock + slotProps={{ paper: { sx: { + mt: 1, + boxShadow: '0 8px 30px rgba(0,0,0,0.06)', + border: '1px solid #E6DFD5', + borderRadius: 2, + p: 1, + } } }} + > + { setShopAnchor(null); navigate('/shop'); }}>Shop All + { setShopAnchor(null); navigate('/shop?category=classic'); }}>Classic Collection + { setShopAnchor(null); navigate('/shop?category=luxury'); }}>Luxury Collection + { setShopAnchor(null); navigate('/shop?category=seasonal'); }}>Seasonal Line + { setShopAnchor(null); navigate('/shop?category=giftbox'); }}>Heritage Gift Boxes + + { setShopAnchor(null); navigate('/custom-candle'); }} sx={{ color: 'primary.main', fontWeight: 600 }}>Custom Candle Builder + + + {/* B2B Services with dropdown */} + + setB2BAnchor(null)} + disableScrollLock + slotProps={{ paper: { sx: { + mt: 1, + boxShadow: '0 8px 30px rgba(0,0,0,0.06)', + border: '1px solid #E6DFD5', + borderRadius: 2, + p: 1, + } } }} + > + { setB2BAnchor(null); navigate('/b2b/corporate-gifting'); }}>Corporate Gifting + { setB2BAnchor(null); navigate('/b2b/hotels-hospitality'); }}>Hotels & Resorts + { setB2BAnchor(null); navigate('/b2b/private-label'); }}>Private Label & OEM + { setB2BAnchor(null); navigate('/custom-candle'); }}>Custom Candle Orders + + + + + + + + + + {/* Action Bar: B2C/B2B Switch, Search, Wishlist, Cart */} + + {/* B2B Toggler */} + setIsB2BMode(e.target.checked)} + color="primary" + size="small" + /> + } + label={ + + {isB2BMode ? 'Wholesale (B2B)' : 'Retail (B2C)'} + + } + sx={{ mr: 0 }} + /> + + setSearchOpen(!searchOpen)} color="inherit"> + + + + setIsWishlistOpen(true)} color="inherit"> + + + + + + setIsCartOpen(true)} color="inherit"> + + + + + + + + + {/* Search Overlay Dropdown */} + {searchOpen && ( + + +
+ setSearchQuery(e.target.value)} + variant="outlined" + autoFocus + slotProps={{ input: { + startAdornment: ( + + + + ), + endAdornment: ( + + setSearchOpen(false)}> + + + + ), + sx: { + borderRadius: 6, + backgroundColor: '#FFFFFF', + '& fieldset': { borderColor: '#E6DFD5' }, + '&:hover fieldset': { borderColor: '#C5A880' }, + }, + } }} + /> + +
+
+ )} +
+ + {/* Mobile Menu Drawer */} + setMobileOpen(false)} + disableScrollLock + slotProps={{ paper: { sx: { width: 280, backgroundColor: 'background.default', p: 2 } } }} + > + + + NAVIGATION + + setMobileOpen(false)}> + + + + + + + + { + setMobileOpen(false); + navigate('/shop'); + }} + > + Shop All Candles} /> + + + + { + setMobileOpen(false); + navigate('/custom-candle'); + }} + > + Custom Candle Builder} + /> + + + + + B2B Solutions + + + { + setMobileOpen(false); + navigate('/b2b/corporate-gifting'); + }} + > + Corporate Gifting} /> + + + + { + setMobileOpen(false); + navigate('/b2b/hotels-hospitality'); + }} + > + Hotels & Hospitality} /> + + + + { + setMobileOpen(false); + navigate('/b2b/private-label'); + }} + > + Private Label & OEM} /> + + + + + { + setMobileOpen(false); + navigate('/journal'); + }} + > + The Journal} /> + + + + { + setMobileOpen(false); + navigate('/about'); + }} + > + Our Story} /> + + + + { + setMobileOpen(false); + navigate('/contact'); + }} + > + Contact Us} /> + + + + + + ); +}; + +export default Header; diff --git a/src/components/common/NewsletterPopup.tsx b/src/components/common/NewsletterPopup.tsx new file mode 100644 index 0000000..9046110 --- /dev/null +++ b/src/components/common/NewsletterPopup.tsx @@ -0,0 +1,157 @@ +import React, { useState, useEffect } from 'react'; +import { Dialog, DialogContent, Box, Typography, TextField, Button, IconButton } from '@mui/material'; +import { MdClose } from 'react-icons/md'; + +export const NewsletterPopup: React.FC = () => { + const [open, setOpen] = useState(false); + const [email, setEmail] = useState(''); + const [subscribed, setSubscribed] = useState(false); + + useEffect(() => { + const dismissed = sessionStorage.getItem('mani_candles_newsletter_dismissed'); + if (!dismissed) { + // Show popup after 8 seconds of page browsing + const timer = setTimeout(() => { + setOpen(true); + }, 8000); + return () => clearTimeout(timer); + } + }, []); + + const handleClose = () => { + sessionStorage.setItem('mani_candles_newsletter_dismissed', 'true'); + setOpen(false); + }; + + const handleSubscribe = (e: React.FormEvent) => { + e.preventDefault(); + if (email) { + setSubscribed(true); + setTimeout(() => { + handleClose(); + }, 3000); + } + }; + + return ( + + + + + + + + + + {/* Image Side */} + + + {/* Form Side */} + + + The Journal & Circle + + + Join Mani Circle + + + Subscribe to unlock 10% off your first order, receive private collection previews, and download our corporate bulk catalogs. + + + {subscribed ? ( + + + Welcome to the circle. + + + Your invitation and discount code have been dispatched to your inbox. + + + ) : ( +
+ setEmail(e.target.value)} + required + variant="outlined" + size="small" + sx={{ + mb: 2, + '& .MuiOutlinedInput-root': { + backgroundColor: '#FFFFFF', + borderRadius: 2, + '& fieldset': { borderColor: '#E6DFD5' }, + '&:hover fieldset': { borderColor: '#C5A880' }, + }, + }} + /> + + + )} + + + We respect your inbox. Unsubscribe at any time. + +
+
+
+
+ ); +}; + +export default NewsletterPopup; diff --git a/src/components/common/SEOHelmet.tsx b/src/components/common/SEOHelmet.tsx new file mode 100644 index 0000000..8ab2928 --- /dev/null +++ b/src/components/common/SEOHelmet.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Helmet } from 'react-helmet-async'; + +interface SEOHelmetProps { + title: string; + description: string; + canonicalUrl?: string; + ogImage?: string; + schemaJson?: object; +} + +export const SEOHelmet: React.FC = ({ + title, + description, + canonicalUrl = window.location.href, + ogImage = 'https://images.unsplash.com/photo-1603006905003-be475563bc59?auto=format&fit=crop&q=80&w=800', + schemaJson, +}) => { + const fullTitle = title.includes('Mani Candles') ? title : `${title} | Mani Candles`; + return ( + + {fullTitle} + + + + {/* Open Graph / Facebook */} + + + + + + + {/* Twitter */} + + + + + + {/* Schema.org Structured Data */} + {schemaJson && ( + + )} + + ); +}; +export default SEOHelmet; diff --git a/src/components/common/ScrollCandle.tsx b/src/components/common/ScrollCandle.tsx new file mode 100644 index 0000000..2a6d005 --- /dev/null +++ b/src/components/common/ScrollCandle.tsx @@ -0,0 +1,90 @@ +import React, { useState, useEffect } from 'react'; +import { Box, Typography } from '@mui/material'; + +export const ScrollCandle: React.FC = () => { + const [candleHeight, setCandleHeight] = useState(65); + const [isExtinguished, setIsExtinguished] = useState(false); + const [visible, setVisible] = useState(true); // Always visible initially + + useEffect(() => { + const handleScroll = () => { + const scrollTop = window.scrollY; + const docHeight = document.documentElement.scrollHeight - window.innerHeight; + const scrollPercent = docHeight > 0 ? scrollTop / docHeight : 0; + + // Show scroll candle only if there is a scrollable area on the page + if (docHeight > 30) { + setVisible(true); + } else { + setVisible(false); + } + + // Height decreases from 65vh to 6vh + const newHeight = Math.max(6, 65 - scrollPercent * 59); + setCandleHeight(newHeight); + + if (scrollPercent >= 0.96) { + setIsExtinguished(true); + } else { + setIsExtinguished(false); + } + }; + + window.addEventListener('scroll', handleScroll, { passive: true }); + // Trigger initially + handleScroll(); + + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + if (!visible) return null; + + return ( + + {/* Flame wrapper */} + + + + + + + {/* Smoke rises if extinguished */} + {isExtinguished && ( + + + + + + )} + + {/* Wick */} + + + {/* Wick Collar */} + + + {/* Candle Body */} + + + + + + {/* Base */} + + + {/* Label */} + + {isExtinguished ? 'Burnt out' : 'Scroll to Burn'} + + + ); +}; + +export default ScrollCandle; diff --git a/src/components/common/ScrollToTop.tsx b/src/components/common/ScrollToTop.tsx new file mode 100644 index 0000000..422772e --- /dev/null +++ b/src/components/common/ScrollToTop.tsx @@ -0,0 +1,55 @@ +import React, { useState, useEffect } from 'react'; +import { Fab, Zoom } from '@mui/material'; +import { IoIosArrowUp } from 'react-icons/io'; + +export const ScrollToTop: React.FC = () => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + const handleScroll = () => { + if (window.scrollY > 300) { + setVisible(true); + } else { + setVisible(false); + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + const scrollToTop = () => { + window.scrollTo({ + top: 0, + behavior: 'smooth', + }); + }; + + return ( + + + + + + ); +}; + +export default ScrollToTop; diff --git a/src/components/common/SkeletonLoader.tsx b/src/components/common/SkeletonLoader.tsx new file mode 100644 index 0000000..9b44475 --- /dev/null +++ b/src/components/common/SkeletonLoader.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Card, Box, Skeleton, Grid } from '@mui/material'; + +export const ProductCardSkeleton: React.FC = () => { + return ( + + + + + + + + + + + + ); +}; + +export const BlogCardSkeleton: React.FC = () => { + return ( + + + + + + + + + + + + + + + + ); +}; + +export const ProductDetailSkeleton: React.FC = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + ); +}; diff --git a/src/components/common/WhatsAppButton.tsx b/src/components/common/WhatsAppButton.tsx new file mode 100644 index 0000000..d744e10 --- /dev/null +++ b/src/components/common/WhatsAppButton.tsx @@ -0,0 +1,67 @@ +import React, { useState } from 'react'; +import { Fab, Tooltip, Box, Typography } from '@mui/material'; +import { IoLogoWhatsapp } from 'react-icons/io'; + +export const WhatsAppButton: React.FC = () => { + const [hovered, setHovered] = useState(false); + const phoneNumber = '919999999999'; // Simulated brand WhatsApp contact + const message = encodeURIComponent( + 'Hi Mani Candles! I am browsing your digital store and would like to inquire about B2C candle delivery or B2B corporate catalogs.' + ); + const whatsappUrl = `https://wa.me/${phoneNumber}?text=${message}`; + + return ( + setHovered(true)} + onMouseLeave={() => setHovered(false)} + sx={{ + position: 'fixed', + bottom: 24, + right: { xs: 24, md: 80 }, // Shifted on desktop to make room for the scroll candle + zIndex: 1000, + display: 'flex', + alignItems: 'center', + gap: 1.5, + }} + > + {hovered && ( + + + Inquire on WhatsApp + + + )} + + + + + + + ); +}; + +export default WhatsAppButton; diff --git a/src/components/common/WishlistDrawer.tsx b/src/components/common/WishlistDrawer.tsx new file mode 100644 index 0000000..555bd02 --- /dev/null +++ b/src/components/common/WishlistDrawer.tsx @@ -0,0 +1,128 @@ +import React from 'react'; +import { + Drawer, + Box, + Typography, + IconButton, + List, + ListItem, + ListItemAvatar, + Avatar, + ListItemText, + ListItemSecondaryAction, + Button, + Divider, +} from '@mui/material'; +import { MdClose, MdOutlineDeleteOutline } from 'react-icons/md'; +import { IoIosHeartEmpty } from 'react-icons/io'; +import { useWishlist } from '../../contexts/WishlistContext'; +import { useCart } from '../../contexts/CartContext'; +import { formatCurrency } from '../../utils/helpers'; +import { useNavigate } from 'react-router-dom'; + +export const WishlistDrawer: React.FC = () => { + const navigate = useNavigate(); + const { wishlistItems, isWishlistOpen, setIsWishlistOpen, removeFromWishlist } = useWishlist(); + const { addToCart } = useCart(); + + const handleMoveToCart = (product: any) => { + addToCart(product, 1, 'B2C'); // Moves as single item B2C + removeFromWishlist(product.id); + }; + + return ( + setIsWishlistOpen(false)} + disableScrollLock + slotProps={{ paper: { sx: { + width: { xs: '100%', sm: 400 }, + backgroundColor: 'background.default', + } } }} + > + {/* Drawer Header */} + + + Your Wishlist ({wishlistItems.length}) + + setIsWishlistOpen(false)}> + + + + + {/* Drawer Content */} + + {wishlistItems.length === 0 ? ( + + + + Your wishlist is empty + + + Save your favorite luxury pouring scents here to review and purchase them later. + + + + ) : ( + + {wishlistItems.map((item) => ( + + + + + + + {item.name} + + } + secondary={ + + + {formatCurrency(item.price)} + + + + } + /> + + removeFromWishlist(item.id)} sx={{ color: 'text.secondary', '&:hover': { color: 'error.main' } }}> + + + + + + + ))} + + )} + + + ); +}; + +export default WishlistDrawer; diff --git a/src/contexts/CartContext.tsx b/src/contexts/CartContext.tsx new file mode 100644 index 0000000..cc18883 --- /dev/null +++ b/src/contexts/CartContext.tsx @@ -0,0 +1,109 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import type { Product, CartItem } from '../types'; + +interface CartContextType { + cartItems: CartItem[]; + addToCart: (product: Product, quantity: number, mode: 'B2C' | 'B2B', customizations?: CartItem['customizations']) => void; + removeFromCart: (productId: string, mode: 'B2C' | 'B2B') => void; + updateQuantity: (productId: string, quantity: number, mode: 'B2C' | 'B2B') => void; + clearCart: () => void; + cartTotal: number; + cartCount: number; + isCartOpen: boolean; + setIsCartOpen: (open: boolean) => void; +} + +const CartContext = createContext(undefined); + +export const CartProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [cartItems, setCartItems] = useState(() => { + const saved = localStorage.getItem('mani_candles_cart'); + return saved ? JSON.parse(saved) : []; + }); + const [isCartOpen, setIsCartOpen] = useState(false); + + useEffect(() => { + localStorage.setItem('mani_candles_cart', JSON.stringify(cartItems)); + }, [cartItems]); + + const addToCart = ( + product: Product, + quantity: number, + mode: 'B2C' | 'B2B', + customizations?: CartItem['customizations'] + ) => { + setCartItems((prevItems) => { + // Find if item already exists with matching id, mode, and customizations + const existingIndex = prevItems.findIndex( + (item) => + item.product.id === product.id && + item.mode === mode && + JSON.stringify(item.customizations) === JSON.stringify(customizations) + ); + + if (existingIndex > -1) { + const newItems = [...prevItems]; + newItems[existingIndex].quantity += quantity; + return newItems; + } + + return [...prevItems, { product, quantity, mode, customizations }]; + }); + setIsCartOpen(true); // Auto-open cart drawer on add + }; + + const removeFromCart = (productId: string, mode: 'B2C' | 'B2B') => { + setCartItems((prevItems) => + prevItems.filter((item) => !(item.product.id === productId && item.mode === mode)) + ); + }; + + const updateQuantity = (productId: string, quantity: number, mode: 'B2C' | 'B2B') => { + if (quantity <= 0) { + removeFromCart(productId, mode); + return; + } + setCartItems((prevItems) => + prevItems.map((item) => + item.product.id === productId && item.mode === mode ? { ...item, quantity } : item + ) + ); + }; + + const clearCart = () => { + setCartItems([]); + }; + + const cartTotal = cartItems.reduce((acc, item) => { + const unitPrice = item.mode === 'B2B' ? item.product.b2bPrice : item.product.price; + return acc + unitPrice * item.quantity; + }, 0); + + const cartCount = cartItems.reduce((acc, item) => acc + item.quantity, 0); + + return ( + + {children} + + ); +}; + +export const useCart = () => { + const context = useContext(CartContext); + if (!context) { + throw new Error('useCart must be used within a CartProvider'); + } + return context; +}; diff --git a/src/contexts/WishlistContext.tsx b/src/contexts/WishlistContext.tsx new file mode 100644 index 0000000..68eea01 --- /dev/null +++ b/src/contexts/WishlistContext.tsx @@ -0,0 +1,73 @@ +import React, { createContext, useContext, useState, useEffect } from 'react'; +import type { Product } from '../types'; + +interface WishlistContextType { + wishlistItems: Product[]; + addToWishlist: (product: Product) => void; + removeFromWishlist: (productId: string) => void; + isInWishlist: (productId: string) => boolean; + isWishlistOpen: boolean; + setIsWishlistOpen: (open: boolean) => void; + toggleWishlist: (product: Product) => void; +} + +const WishlistContext = createContext(undefined); + +export const WishlistProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { + const [wishlistItems, setWishlistItems] = useState(() => { + const saved = localStorage.getItem('mani_candles_wishlist'); + return saved ? JSON.parse(saved) : []; + }); + const [isWishlistOpen, setIsWishlistOpen] = useState(false); + + useEffect(() => { + localStorage.setItem('mani_candles_wishlist', JSON.stringify(wishlistItems)); + }, [wishlistItems]); + + const addToWishlist = (product: Product) => { + setWishlistItems((prev) => { + if (prev.some((item) => item.id === product.id)) return prev; + return [...prev, product]; + }); + }; + + const removeFromWishlist = (productId: string) => { + setWishlistItems((prev) => prev.filter((item) => item.id !== productId)); + }; + + const isInWishlist = (productId: string) => { + return wishlistItems.some((item) => item.id === productId); + }; + + const toggleWishlist = (product: Product) => { + if (isInWishlist(product.id)) { + removeFromWishlist(product.id); + } else { + addToWishlist(product); + } + }; + + return ( + + {children} + + ); +}; + +export const useWishlist = () => { + const context = useContext(WishlistContext); + if (!context) { + throw new Error('useWishlist must be used within a WishlistProvider'); + } + return context; +}; diff --git a/src/features/about/pages/AboutPage.tsx b/src/features/about/pages/AboutPage.tsx new file mode 100644 index 0000000..8f68440 --- /dev/null +++ b/src/features/about/pages/AboutPage.tsx @@ -0,0 +1,140 @@ +import React from 'react'; +import { Container, Grid, Box, Typography, Paper, Avatar } from '@mui/material'; +import SEOHelmet from '../../../components/common/SEOHelmet'; + +const TIMELINE = [ + { year: '2023', title: 'The Dream in Kochi', desc: 'Mani Nair starts testing candle waxes in his kitchen, seeking a cleaner, soot-free burn using natural Indian spices and extracts.' }, + { year: '2024', title: 'Studio Launch', desc: 'Establishing our dedicated small-batch pouring studio in Kochi, Kerala, committing entirely to GMO-free soy wax and organic cotton wicks.' }, + { year: '2025', title: 'B2B Hospitality Expansion', desc: 'Partnering with premium heritage hotels and wellness resorts in Southern India to design their signature ambient scent profiles.' }, + { year: '2026', title: 'Bespoke Customizer', desc: 'Launching our digital flagship workspace, empowering B2C customers globally to blend custom scent percentages online.' } +]; + +export const AboutPage: React.FC = () => { + return ( + + + + {/* Brand Hero */} + + + + Heritage & Craftsmanship + + + Our Story + + + We believe that a candle is not just a source of light, but a medium of presence. Mani Candles was born out of a desire to capture the rich olfactory memory of India in a clean, sustainable, and sophisticated format. + + + + + + {/* The Hand-Poured Process */} + + + + + + + The Studio Ritual + + + Hand-Poured in India + + + Every single candle we produce is mixed and hand-poured in small batches in our Kerala studio. This slow-living dedication ensures that fragrance oils are incorporated evenly at precise thermodynamic cooling states. + + + From centering lead-free cotton wicks by hand to wrapping finished jars in recycled linen bags, we honor the deliberate timeline of authentic craftsmanship. + + + + + {/* Timeline representation */} + + + Our Journey Timeline + + + {TIMELINE.map((item, i) => ( + + + + {item.year} + + + {item.title} + + + {item.desc} + + + + ))} + + + + {/* Founder Spot */} + + + + + + + + Founder\'s Vision + + + "Scent is a Bridge to Memory" + + + "Growing up in Southern India, scents were central to our festivals and evenings—the damp earth after monsoon rain, mogra garlands, and slow wood-fire smoke. We created Mani Candles to share this rich Indian olfactory vocabulary in its cleanest, most elegant form with the modern world." + + + Mani Nair, Founder & Head Chandler + + + + + + + ); +}; + +export default AboutPage; diff --git a/src/features/b2b/components/B2BInquiryForm.tsx b/src/features/b2b/components/B2BInquiryForm.tsx new file mode 100644 index 0000000..def8c82 --- /dev/null +++ b/src/features/b2b/components/B2BInquiryForm.tsx @@ -0,0 +1,281 @@ +import React, { useState } from 'react'; +import { useForm, Controller } from 'react-hook-form'; +import { + Box, + TextField, + Button, + Typography, + Grid, + MenuItem, + Paper, + Alert, + Fade, +} from '@mui/material'; +import type { B2BInquiry } from '../../../types'; + +export const B2BInquiryForm: React.FC = () => { + const [success, setSuccess] = useState(false); + + const { + control, + handleSubmit, + reset, + formState: { errors, isSubmitting } + } = useForm({ + defaultValues: { + name: '', + email: '', + phone: '', + companyName: '', + website: '', + businessType: 'corporate', + estimatedQuantity: 50, + neededBy: '', + message: '' + } + }); + + const onSubmit = async (data: B2BInquiry) => { + // Simulate API delivery + await new Promise(resolve => setTimeout(resolve, 1500)); + console.log('B2B Inquiry Data Submitted:', data); + setSuccess(true); + reset(); + setTimeout(() => setSuccess(false), 8000); + }; + + return ( + + + + Request a Bespoke Proposal + + + Complete the fields below and our corporate alchemist desk will compile pricing catalog files and custom renders. + + + + {success && ( + + + Inquiry Logged Successfully! Your corporate contact token has been registered. An alchemist from our team will contact you within 2 business hours. + + + )} + +
+ + {/* Full Name */} + + ( + + )} + /> + + + {/* Corporate Email */} + + ( + + )} + /> + + + {/* Phone Number */} + + ( + + )} + /> + + + {/* Company Name */} + + ( + + )} + /> + + + {/* Website (Optional) */} + + ( + + )} + /> + + + {/* Business Type */} + + ( + + Corporate Gifting + Hotels & Hospitality + Weddings & Event Planners + Retail Distribution / Store + Interior Designer + Custom Manufacture / Private Label + + )} + /> + + + {/* Estimated Quantity */} + + ( + + )} + /> + + + {/* Needed By Date */} + + ( + + )} + /> + + + {/* Custom Message */} + + ( + + )} + /> + + + {/* Submit Button */} + + + + +
+
+ ); +}; + +export default B2BInquiryForm; diff --git a/src/features/b2b/pages/CorporateGiftingPage.tsx b/src/features/b2b/pages/CorporateGiftingPage.tsx new file mode 100644 index 0000000..b598108 --- /dev/null +++ b/src/features/b2b/pages/CorporateGiftingPage.tsx @@ -0,0 +1,142 @@ +import React from 'react'; +import { Container, Grid, Box, Typography, Paper, Accordion, AccordionSummary, AccordionDetails } from '@mui/material'; +import { IoIosArrowDown } from 'react-icons/io'; +import { MdOutlineCardGiftcard, MdOutlineWorkspacePremium, MdSecurity } from 'react-icons/md'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import B2BInquiryForm from '../components/B2BInquiryForm'; + +const BENEFITS = [ + { + icon: , + title: 'Custom Brand Embossing', + desc: 'Engrave your corporate brand identity or event message directly onto our ceramic glass vessels and luxury outer slide sleeves.' + }, + { + icon: , + title: 'Premium Customized Packing', + desc: 'Select from textured rigid gift boxes, linen wrapping sleeves, or handwoven canvas bags accompanied by customized copper foil thank-you cards.' + }, + { + icon: , + title: 'GST Invoice & Compliance', + desc: 'Receive formal business invoices with dedicated GSTIN registrations across India to secure corporate tax credits easily.' + } +]; + +export const CorporateGiftingPage: React.FC = () => { + return ( + + + + {/* Hero Section */} + + + + Premium B2B Services + + + Luxury Corporate Gifting + + + Transform standard corporate tokens into memorable sensory experiences. Discover hand-poured soy candles formulated to project warmth, craftsmanship, and brand excellence. + + + + + + {/* Value Pillars Grid */} + + {BENEFITS.map((ben, i) => ( + + + {ben.icon} + + {ben.title} + + + {ben.desc} + + + + ))} + + + {/* Form and Side Info */} + + + + The Custom Standard + + + Designed For Impact + + + Whether rewarding your internal leadership circles, honoring VIP clients, or celebrating Diwali, New Year, or corporate milestones, our candles are calibrated to impress. + + + We support scaling configurations from 20 units to 5,000+ units, dispatching batch samples to ensure complete satisfaction prior to final line pours. + + + + + India-Wide Delivery Guarantee + + + We handle secure shipping distribution directly to individual client doors or bulk delivery coordinates using temperature-controlled freight logistics. + + + + + + + + + + {/* Corporate FAQs */} + + + Corporate Gifting FAQ + + + + }> + What is the lead time for corporate custom orders? + + + + Standard corporate orders with ready fragrances require 7-10 business days. Custom logo engravings and personalized packaging boxes generally require 14-20 business days from layout sign-off and deposit receipt. + + + + + + }> + Can we request physical scent samples before ordering? + + + + Yes, we offer complimentary tin sampler sets or custom product samples for verified corporate email accounts. Please log your request through the inquiry form with your target requirements. + + + + + + + + ); +}; + +export default CorporateGiftingPage; diff --git a/src/features/b2b/pages/HospitalityPage.tsx b/src/features/b2b/pages/HospitalityPage.tsx new file mode 100644 index 0000000..91ad093 --- /dev/null +++ b/src/features/b2b/pages/HospitalityPage.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { Container, Grid, Box, Typography, Paper, } from '@mui/material'; + +import { MdOutlineBed, MdSpa, MdOutlineRestaurant } from 'react-icons/md'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import B2BInquiryForm from '../components/B2BInquiryForm'; + +const SERVICES = [ + { + icon: , + title: 'Luxury Turn-down Sets', + desc: 'Bespoke mini-votive soy candles curated to complement luxury bed turn-down services, presenting guests with relaxing lavender and sandalwood scents.' + }, + { + icon: , + title: 'Spa & Wellness Olfactories', + desc: 'Formulate calming wood and deodar cedar essential oil candles to scent massage therapists blocks, bath retreats, and relaxation zones.' + }, + { + icon: , + title: 'Ambient Café Candles', + desc: 'Odorless or lightly spiced (clove, citrus) clean-burning coconut wax candles to illuminate dinner tables without interfering with food aromas.' + } +]; + +export const HospitalityPage: React.FC = () => { + return ( + + + + {/* Hero Section */} + + + + Artisanal Hotel Partners + + + Hotels & Resorts Scenting + + + Elevate guest retention through custom-tailored ambient scenting. Establish an exclusive olfactory fingerprint for your resort lobbies, rooms, and wellness centers. + + + + + + {/* Services Grid */} + + {SERVICES.map((ser, i) => ( + + + {ser.icon} + + {ser.title} + + + {ser.desc} + + + + ))} + + + + + + Scent Branding + + + Olfactory Identity + + + A hotel\'s scent signature builds instant guest recognition. We collaborate with luxury boutique hoteliers to formulate unique signature aromas that reflect the local ecology—whether high Himalayan deodar forest or tropical Kerala coconut and cardamom. + + + All candles are hand-poured with lead-free cotton or wooden wicks to guarantee soot-free, zero-smoke burning, preserving high indoor air quality standards. + + + + + + + + + + ); +}; + +export default HospitalityPage; diff --git a/src/features/b2b/pages/PrivateLabelPage.tsx b/src/features/b2b/pages/PrivateLabelPage.tsx new file mode 100644 index 0000000..74a1151 --- /dev/null +++ b/src/features/b2b/pages/PrivateLabelPage.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { Container, Grid, Box, Typography, Paper, } from '@mui/material'; + +import { MdOutlineScience, MdPalette, MdPrecisionManufacturing } from 'react-icons/md'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import B2BInquiryForm from '../components/B2BInquiryForm'; + +const STEPS = [ + { + icon: , + title: 'Custom Fragrance R&D', + desc: 'Work with our master chandlers to extract custom distillations or blend unique synthetic-botanical scent signatures for your cosmetics or lifestyle brand.' + }, + { + icon: , + title: 'Vessel & Package Sourcing', + desc: 'Source bespoke ceramic pots, amber glass vessels, wooden lids, and custom hot-foil printed box packaging from our curated vendor networks in India.' + }, + { + icon: , + title: 'Small Batch Manufacturing', + desc: 'Our studio handles full-scale compounding, hand-pouring, wick centering, label printing, regulatory testing, and logistics packaging.' + } +]; + +export const PrivateLabelPage: React.FC = () => { + return ( + + + + {/* Hero Section */} + + + + OEM Contract Manufacturing + + + Private Label Candles + + + Bring your brand fragrance concepts to market. Partner with our studio for full-stack, customized contract manufacturing of luxury soy candles in India. + + + + + + {/* Steps Grid */} + + {STEPS.map((step, i) => ( + + + {step.icon} + + {step.title} + + + {step.desc} + + + + ))} + + + + + + End-to-End Capabilities + + + Scale Handcrafted Quality + + + Starting a candle line can be challenging due to vessel cracking, wick curling, and fragrance bleeding. Our R&D laboratory performs extensive hot-throw testing, soot tests, and shelf-life reviews, ensuring your candles meet international luxury standards. + + + We support flexible minimum quantities to help independent boutique brands, beauty salons, and wedding coordinators launch confidently. + + + + + + + + + + ); +}; + +export default PrivateLabelPage; diff --git a/src/features/contact/pages/ContactPage.tsx b/src/features/contact/pages/ContactPage.tsx new file mode 100644 index 0000000..acbbec5 --- /dev/null +++ b/src/features/contact/pages/ContactPage.tsx @@ -0,0 +1,222 @@ +import React, { useState } from 'react'; +import { useForm, Controller } from 'react-hook-form'; +import { + Container, + Grid, + Box, + Typography, + Paper, + TextField, + Button, + Alert, + Accordion, + AccordionSummary, + AccordionDetails, +} from '@mui/material'; +import { IoIosArrowDown, IoLogoWhatsapp } from 'react-icons/io'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import { mockFAQs } from '../../../utils/data'; + +interface ContactFormData { + name: string; + email: string; + subject: string; + message: string; +} + +export const ContactPage: React.FC = () => { + const [sent, setSent] = useState(false); + const { control, handleSubmit, reset, formState: { errors, isSubmitting } } = useForm({ + defaultValues: { name: '', email: '', subject: '', message: '' } + }); + + const onSubmit = async (data: ContactFormData) => { + await new Promise(resolve => setTimeout(resolve, 1000)); + console.log('Contact form submitted:', data); + setSent(true); + reset(); + setTimeout(() => setSent(false), 5000); + }; + + return ( + + + + + {/* Title */} + + + Connect With Us + + + Get in Touch + + + Whether inquiring about e-commerce tracking, B2B wholesale prices, or custom corporate gifting proposals. + + + + + {/* Left Column: Coordinates */} + + + + + THE STUDIO + + + Mani Candles Private Limited
+ 3rd Floor, Aluva Atelier,
+ Kochi, Kerala, India - 683101 +
+ + Email: support@manicandles.com + + + Phone: +91 99999 99999 + +
+ + + + BUSINESS HOURS + + + Monday to Saturday: 9:00 AM - 6:00 PM IST
+ Closed on Sundays and National Holidays. +
+
+ + +
+
+ + {/* Right Column: Contact Form */} + + + {sent && ( + + Your support message has been dispatched. We will reply to your registered email shortly. + + )} + +
+ + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + ( + + )} + /> + + + + + +
+
+
+
+ + {/* Google Map Mock Frame */} + + + Studio Location Map + + + + Google Maps Interactive Frame (Aluva Studio Kochi, Kerala) + + + + + {/* FAQ Accordions */} + + + Frequently Asked Questions + + + {mockFAQs.map((faq, index) => ( + + }> + {faq.question} + + + + {faq.answer} + + + + ))} + + +
+
+ ); +}; + +export default ContactPage; diff --git a/src/features/custom/pages/CustomCandlePage.tsx b/src/features/custom/pages/CustomCandlePage.tsx new file mode 100644 index 0000000..b958f56 --- /dev/null +++ b/src/features/custom/pages/CustomCandlePage.tsx @@ -0,0 +1,597 @@ +import React, { useState, useMemo } from 'react'; +import { + Container, + Grid, + Box, + Typography, + Paper, + Tabs, + Tab, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, + Slider, + TextField, + Button, + Divider, + Alert, + } from '@mui/material'; + +import { MdOutlineShoppingBag } from 'react-icons/md'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import { useCart } from '../../../contexts/CartContext'; +import { formatCurrency } from '../../../utils/helpers'; + +// Helper structure for custom builder +const JARS = [ + { id: 'amber', name: 'Amber Glass', color: 'linear-gradient(135deg, rgba(206,128,52,0.85) 0%, rgba(122,66,13,0.9) 100%)', price: 0, desc: 'Diffuses warm amber glow' }, + { id: 'frosted', name: 'Frosted Glass', color: 'linear-gradient(135deg, rgba(255,255,255,0.7) 0%, rgba(220,220,220,0.85) 100%)', price: 50, desc: 'Clean, minimalist aesthetics' }, + { id: 'cream', name: 'Ceramic Cream', color: 'linear-gradient(135deg, #FAF6F0 0%, #E6DFD5 100%)', price: 150, desc: 'Earthy, rustic luxury clay' }, + { id: 'lavender', name: 'Amethyst Lavender', color: 'linear-gradient(135deg, rgba(202,176,219,0.85) 0%, rgba(118,59,166,0.9) 100%)', price: 100, desc: 'Sophisticated violet aura' }, + { id: 'black', name: 'Matte Black Ceramic', color: 'linear-gradient(135deg, #2B2521 0%, #110F0E 100%)', price: 150, desc: 'Opulent, sophisticated styling' }, +]; + +const WAXES = [ + { id: 'soy', name: '100% Soy Wax', desc: 'Soot-free clean burn, biodegradable', price: 0 }, + { id: 'coconut', name: 'Coconut Wax Blend', desc: 'Superior cold & hot scent throw', price: 100 }, + { id: 'beeswax', name: 'Raw Honey Beeswax', desc: 'Natural air purifier, sweet scent', price: 200 }, +]; + +const SCENTS = [ + { id: 'sandalwood', name: 'Mysore Sandalwood', category: 'Woody' }, + { id: 'jasmine', name: 'Madurai Jasmine', category: 'Floral' }, + { id: 'saffron', name: 'Kashmiri Saffron', category: 'Spice' }, + { id: 'oud', name: 'Assam Smoky Oud', category: 'Woody' }, + { id: 'lime', name: 'Kerala Kaffir Lime', category: 'Citrus' }, + { id: 'cardamom', name: 'Malabar Cardamom', category: 'Spice' }, +]; + +interface CustomCandlePageProps { + isB2BMode: boolean; +} + +export const CustomCandlePage: React.FC = ({ isB2BMode }) => { + const { addToCart } = useCart(); + + // Customizer options state + const [activeTab, setActiveTab] = useState(0); + const [selectedJar, setSelectedJar] = useState(JARS[0]); + const [selectedWax, setSelectedWax] = useState(WAXES[0]); + + // Scents percentages (sums to 100%) + const [scentPrimary, setScentPrimary] = useState(SCENTS[0]); + const [scentSecondary, setScentSecondary] = useState(SCENTS[1]); + const [ratio, setRatio] = useState(60); // Primary Scent ratio + const [scentStrength, setScentStrength] = useState('Balanced'); + + // Label text and font state + const [labelText, setLabelText] = useState('My Sanctuary'); + const [labelSubText, setLabelSubText] = useState('Hand-Poured Luxury'); + const [labelFont, setLabelFont] = useState('serif'); + + const [quantity, setQuantity] = useState(1); + + // Scent ratio calculation + const primaryPct = ratio; + const secondaryPct = 100 - ratio; + + // Base pricing + const basePrice = isB2BMode ? 750 : 1299; + + // Dynamic Total Price + const totalPrice = useMemo(() => { + const jarSurcharge = selectedJar.price; + const waxSurcharge = selectedWax.price; + const strengthSurcharge = scentStrength === 'Intense' ? 100 : scentStrength === 'Subtle' ? -50 : 0; + const unitCost = basePrice + jarSurcharge + waxSurcharge + strengthSurcharge; + return unitCost * quantity; + }, [selectedJar, selectedWax, scentStrength, basePrice, quantity]); + + const handleAddToCart = () => { + const customProduct = { + id: `custom-${selectedJar.id}-${selectedWax.id}-${scentPrimary.id}-${scentSecondary.id}`, + name: `Custom Signature Blend`, + tagline: `Personalized blend: ${primaryPct}% ${scentPrimary.name} & ${secondaryPct}% ${scentSecondary.name}`, + description: `A custom-blended luxury candle formulated with ${selectedWax.name} in a signature ${selectedJar.name} container. Personalized label reads: "${labelText}".`, + details: 'Hand-crafted by request in our Kochi studio.', + price: basePrice + selectedJar.price + selectedWax.price + (scentStrength === 'Intense' ? 100 : scentStrength === 'Subtle' ? -50 : 0), + b2bPrice: Math.floor((basePrice + selectedJar.price + selectedWax.price) * 0.7), + minB2BQuantity: 20, + images: [ + 'https://images.unsplash.com/photo-1603006905003-be475563bc59?auto=format&fit=crop&q=80&w=800' + ], + category: 'classic' as const, + fragranceProfile: 'woody' as const, + burnTime: 50, + waxType: selectedWax.name === '100% Soy Wax' ? ('Soy Wax' as const) : ('Blend' as const), + weight: '200g / 7.0 oz', + fragranceNotes: { + top: [scentPrimary.name], + middle: [scentSecondary.name], + base: ['Warm Musk'] + }, + ingredients: [selectedWax.name, 'Premium Botanical Oils', 'Cotton Wick'], + careInstructions: ['Trim wick to 1/4 inch before lighting', 'Burn 2-3 hours on first lighting'], + rating: 5.0, + reviewsCount: 0, + reviews: [], + inventory: 99 + }; + + addToCart(customProduct, quantity, isB2BMode ? 'B2B' : 'B2C', { + wax: selectedWax.name, + jar: selectedJar.name, + scentBlend: `${primaryPct}% ${scentPrimary.name} / ${secondaryPct}% ${scentSecondary.name} (${scentStrength} Strength)`, + labelText: `${labelText} - ${labelSubText}`, + labelFont: labelFont === 'serif' ? 'Cormorant Garamond' : 'Inter' + }); + }; + + return ( + + + + {/* Header Title */} + + + Artisanal Workspace + + + Custom Candle Builder + + + Step into the role of head perfumer. Blend your custom olfactory signature, select a premium vessel, and print a custom label to manifest your sanctuary. + + + + {isB2BMode && ( + + Wholesaler Mode Active: Minimum quantity for custom wholesale candle batches is 20 units. Perfect for wedding favors, corporate gifting, or hotel branding. + + )} + + + {/* Left Column: Visual Mock Jar (Interactive Canvas) */} + + + {/* Flame & Wick */} + + {/* Flame Graphic */} + + + + {/* Candle Vessel Shape */} + + {/* Wax Inside Overlay (Soy/Beeswax color change) */} + + + {/* Customized Label Sticker */} + + + Mani Signature + + 15 ? '0.75rem' : '0.95rem', + fontWeight: labelFont === 'serif' ? 600 : 500, + color: '#1C1917', + lineHeight: 1.1, + wordBreak: 'break-word', + mb: 0.5, + }} + > + {labelText || 'Your Label'} + + + {labelSubText || 'Hand-Poured'} + + + + {primaryPct}% {scentPrimary.name.split(' ')[1] || scentPrimary.name} + + + + + {/* Specs Label Summary */} + + + {labelText} + + + {selectedWax.name} • {selectedJar.name} ({primaryPct}%/{secondaryPct}%) + + + + + + {/* Right Column: Steps Selector Panel */} + + + setActiveTab(val)} + variant="fullWidth" + textColor="primary" + indicatorColor="primary" + sx={{ borderBottom: '1px solid #EFE9F3', mb: 4 }} + > + + + + + + {/* Tab 1: Vessels & Waxes */} + {activeTab === 0 && ( + + {/* Jar Vessels */} + + + Select Vessel Finish + + setSelectedJar(JARS.find(j => j.id === e.target.value) || JARS[0])}> + + {JARS.map((jar) => ( + + setSelectedJar(jar)} + > + + + + + {jar.name} + + + {jar.desc} {jar.price > 0 && `(+₹${jar.price})`} + + + + + + ))} + + + + + {/* Wax Type */} + + + Select Organic Wax Type + + setSelectedWax(WAXES.find(w => w.id === e.target.value) || WAXES[0])}> + + {WAXES.map((wax) => ( + + setSelectedWax(wax)} + > + + + + + {wax.name} + + + {wax.desc} {wax.price > 0 && `(+₹${wax.price})`} + + + + + + ))} + + + + + )} + + {/* Tab 2: Scent Blend ratios */} + {activeTab === 1 && ( + + {/* Select Primary Scent */} + + + + Primary Fragrance + setScentPrimary(SCENTS.find(s => s.id === e.target.value) || SCENTS[0])} + slotProps={{ select: { native: true } }} + variant="outlined" + size="small" + sx={{ '& select': { py: 1.2 } }} + > + {SCENTS.map(s => )} + + + + + + + Secondary Fragrance + setScentSecondary(SCENTS.find(s => s.id === e.target.value) || SCENTS[1])} + slotProps={{ select: { native: true } }} + variant="outlined" + size="small" + sx={{ '& select': { py: 1.2 } }} + > + {SCENTS.filter(s => s.id !== scentPrimary.id).map(s => ( + + ))} + + + + + + {/* Scent Blend Ratio Slider */} + + + + {scentPrimary.name}: {primaryPct}% + + + {scentSecondary.name}: {secondaryPct}% + + + setRatio(val as number)} + min={40} + max={90} + color="primary" + /> + + * Recommend keeping ratios between 50/50 to 80/20 for stable olfactory performance. + + + + {/* Scent Intensity */} + + + Olfactory Concentration (Strength) + + setScentStrength(e.target.value)}> + } label="Subtle (-₹50)" /> + } label="Balanced (+₹0)" /> + } label="Intense (+₹100)" /> + + + + )} + + {/* Tab 3: Custom label details */} + {activeTab === 2 && ( + + setLabelText(e.target.value.substring(0, 24))} + variant="outlined" + size="small" + placeholder="E.g., Amber Evening, Soul Space" + helperText="Max 24 characters" + /> + + setLabelSubText(e.target.value.substring(0, 32))} + variant="outlined" + size="small" + placeholder="E.g., Hand-Poured for Priya" + helperText="Max 32 characters" + /> + + + + Select Label Font Style + + setLabelFont(e.target.value)}> + } label={Classical Serif} /> + } label={Modern Sans-Serif} /> + + + + )} + + + + {/* Quantity and Cart Checkout Controls */} + + + + Total Estimated Cost + + + {formatCurrency(totalPrice)} + + + + + {/* Quantity adjustment */} + + + + {quantity} + + + + + + + + + {isB2BMode && quantity < 20 && ( + + * Wholesaler mode requires a minimum order of 20 custom candles. + + )} + + + + + + ); +}; + +export default CustomCandlePage; diff --git a/src/features/home/pages/HomePage.tsx b/src/features/home/pages/HomePage.tsx new file mode 100644 index 0000000..a349a35 --- /dev/null +++ b/src/features/home/pages/HomePage.tsx @@ -0,0 +1,490 @@ +import React, { useState, useMemo } from 'react'; +import { Container, Grid, Box, Typography, Button, Paper, Card, CardContent, } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import { IoIosArrowForward } from 'react-icons/io'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import { mockProducts } from '../../../utils/data'; +import { formatCurrency } from '../../../utils/helpers'; +import ProductCard from '../../shop/components/ProductCard'; + +const SCENT_PROFILES = [ + { id: 'woody', label: 'Woody & Earthy', desc: 'Notes of deodar cedar, assam oud, and sandalwood. Evokes ancient forest walks.', product: 'sandalwood-saffron' }, + { id: 'floral', label: 'Floral & Sweet', desc: 'Intoxicating mogra flowers, night-blooming jasmine, and rose petals.', product: 'jasmine-nectar' }, + { id: 'oriental', label: 'Spicy & Oriental', desc: 'Malabar cardamom, kashmiri saffron, ginger root, and clove spices.', product: 'cardamom-oud' }, + { id: 'citrus', label: 'Citrus & Fresh', desc: 'Zesty Kerala kaffir lime, lemongrass, and clean mountain eucalyptus.', product: 'kerala-coconut-lime' } +]; + +interface HomePageProps { + isB2BMode: boolean; +} + +export const HomePage: React.FC = ({ isB2BMode }) => { + + const [activeScentId, setActiveScentId] = useState('woody'); + + // Filter scent based on interactive select + const activeScent = useMemo(() => { + return SCENT_PROFILES.find(s => s.id === activeScentId) || SCENT_PROFILES[0]; + }, [activeScentId]); + + const recommendedScentProduct = useMemo(() => { + return mockProducts.find(p => p.id === activeScent.product) || mockProducts[0]; + }, [activeScent]); + + // Get Best Sellers list + const bestSellers = useMemo(() => { + return mockProducts.filter(p => p.isBestSeller).slice(0, 3); + }, []); + + return ( + + + + {/* Animated Hero Section */} + + {/* Fog Layers */} + + + + + {/* Embers */} + + {Array.from({ length: 15 }).map((_, i) => ( + + ))} + + + + + {/* Left Column: Brand Copy */} + + + + Mani Candles Co. + + + Handmade • Natural • Heartmade + + + Discover slow-poured luxury soy candles crafted with essential botanical extracts and lead-free cotton wicks, designed to illuminate your sanctuary with meditative calm. + + + + + + + + + {/* Right Column: Dynamic Breathing Vessel */} + + + {/* Breathing Aura */} + + + {/* Product Photo */} + + + + + + + {/* Bouncing Scroll Indicator */} + + Scroll Down + + + + {/* Featured Collections Section */} + + + + Curated Formulations + + + Featured Collections + + + + + + + + + + Classic Collection + + + + + + + + + + + Luxury Collection + + + + + + + + + + + Seasonal Line + + + + + + + + + + + Heritage Gift Boxes + + + + + + + + {/* Best Sellers */} + + + + + + Atelier Favorites + + + Best Selling Pourings + + + + + + + {bestSellers.map(product => ( + + + + ))} + + + + + {/* Interactive Olfactory Scent Wheel */} + + + {/* Left: Info */} + + + Olfactory Selector + + + Find Your Olfactory Signature + + + Fragrances govern emotional energy. Click through our primary scent categories below to preview recommended luxury candles based on their olfactory ingredients. + + + + {SCENT_PROFILES.map(scent => ( + setActiveScentId(scent.id)} + sx={{ + p: 2.5, + cursor: 'pointer', + borderRadius: 3, + borderColor: activeScentId === scent.id ? 'primary.main' : '#E6DFD5', + backgroundColor: activeScentId === scent.id ? 'rgba(142, 117, 87, 0.03)' : '#FFFFFF', + transition: 'all 0.3s ease', + '&:hover': { borderColor: 'primary.light' } + }} + > + + {scent.label} + + + {scent.desc} + + + ))} + + + + {/* Right: Recommendation Card Display */} + + + + RECOMMENDED FOR YOU + + + + {recommendedScentProduct.name} + + + {recommendedScentProduct.tagline} + + + + + + + + {/* Promotes Custom Candle Builder */} + + + + Infinite Blends + + + Design Your Signature Scent + + + Tired of off-the-shelf options? Select custom wax components, blend multiple fragrance oils with precision percentages, and type custom labels in our workspace. + + + + + + {/* B2B Gateways */} + + + + Corporate & Hospitality + + + Corporate & Custom B2B Services + + + + + + + + Corporate Gifting + + + Logo embossing, luxury rigid boxes, custom inserts, and GST compliance invoices for VIP client lists. + + + + + + + + + Hotels & Hospitality + + + Bespoke turn-down mini candles and lobby ambient scenting configurations for resorts and spas. + + + + + + + + + Private Label / OEM + + + Full contract manufacturing, lab stability testing, custom vessel sourcing and small-batch packaging. + + + + + + + + ); +}; + +export default HomePage; diff --git a/src/features/journal/components/ArticleCard.tsx b/src/features/journal/components/ArticleCard.tsx new file mode 100644 index 0000000..44e93f4 --- /dev/null +++ b/src/features/journal/components/ArticleCard.tsx @@ -0,0 +1,110 @@ +import React from 'react'; +import { Card, CardMedia, CardContent, Typography, Box, Avatar } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import type { BlogArticle } from '../../../types'; + +interface ArticleCardProps { + article: BlogArticle; +} + +export const ArticleCard: React.FC = ({ article }) => { + return ( + + + + + + + + + {article.category} + + + {article.readTime} + + + + + {article.title} + + + + {article.excerpt} + + + + + + + {article.author.name} + + + {article.author.role} + + + + + + ); +}; + +export default ArticleCard; diff --git a/src/features/journal/pages/ArticleDetailPage.tsx b/src/features/journal/pages/ArticleDetailPage.tsx new file mode 100644 index 0000000..8719a0c --- /dev/null +++ b/src/features/journal/pages/ArticleDetailPage.tsx @@ -0,0 +1,132 @@ +import React, { useMemo } from 'react'; +import { useParams, useNavigate } from 'react-router-dom'; +import { Container, Box, Chip, Typography, Avatar, Button, Divider, Grid } from '@mui/material'; +import { IoIosArrowBack } from 'react-icons/io'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import { mockArticles } from '../../../utils/data'; +import ArticleCard from '../components/ArticleCard'; + +export const ArticleDetailPage: React.FC = () => { + const { slug } = useParams<{ slug: string }>(); + const navigate = useNavigate(); + + // Find active article + const article = useMemo(() => { + return mockArticles.find((a) => a.slug === slug) || mockArticles[0]; + }, [slug]); + + // Generate other articles recommendation + const recommendations = useMemo(() => { + return mockArticles.filter((a) => a.id !== article.id).slice(0, 2); + }, [article]); + + return ( + + + + + {/* Back Button */} + + + {/* Article Meta Header */} + + + + + {article.date} • {article.readTime} + + + + + {article.title} + + + {/* Author Block */} + + + + + {article.author.name} + + + {article.author.role} + + + + + + {/* Cover Photo */} + + + {/* Article Content Body */} + + + {/* Tag chips */} + + {article.tags.map((tag) => ( + + ))} + + + + + {/* Recommended Reads */} + + + Related Readings + + + {recommendations.map((a) => ( + + + + ))} + + + + + ); +}; + +export default ArticleDetailPage; diff --git a/src/features/journal/pages/JournalPage.tsx b/src/features/journal/pages/JournalPage.tsx new file mode 100644 index 0000000..de7734b --- /dev/null +++ b/src/features/journal/pages/JournalPage.tsx @@ -0,0 +1,152 @@ +import React, { useState, useMemo } from 'react'; +import { Container, Grid, Box, Typography, Paper, Chip, Button } from '@mui/material'; +import { Link as RouterLink } from 'react-router-dom'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import ArticleCard from '../components/ArticleCard'; +import { mockArticles } from '../../../utils/data'; + +const CATEGORIES = ['All', 'Lifestyle', 'Craftsmanship', 'Heritage']; + +export const JournalPage: React.FC = () => { + const [selectedCategory, setSelectedCategory] = useState('All'); + + // Find featured post + const featuredArticle = useMemo(() => { + return mockArticles.find(a => a.featured) || mockArticles[0]; + }, []); + + // Filter remaining posts + const filteredArticles = useMemo(() => { + return mockArticles.filter(article => { + // Exclude featured article from the grid + if (article.id === featuredArticle.id) return false; + if (selectedCategory !== 'All' && article.category !== selectedCategory) return false; + return true; + }); + }, [selectedCategory, featuredArticle]); + + return ( + + + + + {/* Page Title */} + + + Editorial & Circle + + + The Candle Circle Journal + + + Reflections on botanical alchemy, conscious decor, and the historical archives of scent craftsmanship. + + + + {/* Featured Banner Post */} + {featuredArticle && ( + + + + + + + + + FEATURED READ • {featuredArticle.category} + + + {featuredArticle.readTime} + + + + {featuredArticle.title} + + + {featuredArticle.excerpt} + + + + + + )} + + {/* Category Navigation Pills */} + + {CATEGORIES.map(cat => ( + setSelectedCategory(cat)} + color={selectedCategory === cat ? 'primary' : 'default'} + variant={selectedCategory === cat ? 'filled' : 'outlined'} + sx={{ px: 2, py: 2, fontSize: '0.8rem', cursor: 'pointer' }} + /> + ))} + + + {/* Grid List */} + {filteredArticles.length === 0 ? ( + + + No other articles found in this category. + + + ) : ( + + {filteredArticles.map(article => ( + + + + ))} + + )} + + + ); +}; + +export default JournalPage; diff --git a/src/features/legal/pages/PrivacyPolicyPage.tsx b/src/features/legal/pages/PrivacyPolicyPage.tsx new file mode 100644 index 0000000..6de655f --- /dev/null +++ b/src/features/legal/pages/PrivacyPolicyPage.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import { Container, Typography, Box, Paper, Divider } from '@mui/material'; +import SEOHelmet from '../../../components/common/SEOHelmet'; + +export const PrivacyPolicyPage: React.FC = () => { + return ( + + + + + Legal & Compliance + + + Privacy Policy + + + Last Updated: July 5, 2026 + + + + + + 1. Introduction + + + At Mani Candles, we respect your privacy and are committed to protecting it. This Privacy Policy explains how we collect, use, and safeguard your personal information when you browse our retail B2C catalog, design custom formulations in our interactive candle workspace, or place corporate bulk orders. + + + + + + 2. Information We Collect + + + We collect information directly from you when you complete purchases, submit B2B corporate inquiries, register for newsletter benefits, or build custom formulations: + + +
    +
  • Contact details: Name, business email, delivery address, phone number.
  • +
  • B2B Account details: Company name, GST numbers, retail certificates, custom packaging assets.
  • +
  • Payment details: Cryptographically tokenized payment confirmations (credit cards, UPI, net banking). We never store raw payment details.
  • +
+
+ + + + + 3. How We Use Your Data + + + We use your data to fulfill e-commerce shipments, calculate precise wholesale B2B pricing tiers, customize candle labels, dispatch newsletter invitations, and comply with tax regulations in India. We do not sell or lease your personal information to third-party advertising companies. + + + + + + 4. Contact Information + + + For queries regarding your personal data deletion or questions about our security standards, contact our privacy desk at:
+ Email: privacy@manicandles.com
+ Address: Mani Candles Studio, Aluva, Kochi, Kerala, India - 683101 +
+
+
+ ); +}; + +export default PrivacyPolicyPage; diff --git a/src/features/legal/pages/RefundPolicyPage.tsx b/src/features/legal/pages/RefundPolicyPage.tsx new file mode 100644 index 0000000..5673e5e --- /dev/null +++ b/src/features/legal/pages/RefundPolicyPage.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Container, Typography, Box, Paper, Divider } from '@mui/material'; +import SEOHelmet from '../../../components/common/SEOHelmet'; + +export const RefundPolicyPage: React.FC = () => { + return ( + + + + + Legal & Compliance + + + Refund Policy + + + Last Updated: July 5, 2026 + + + + + + 1. 7-Day Return Policy (B2C) + + + We want you to love your home scent. If you are not satisfied with your retail B2C order: + + +
    +
  • You can return any unburned, unused candle in its original packaging within 7 calendar days of delivery.
  • +
  • Due to health and safety regulations, we cannot accept returns for candles that have been lit, burned, or removed from their luxury product sleeve.
  • +
  • Return shipping costs are the responsibility of the customer.
  • +
+
+ + + + + 2. Custom Candle Exclusions + + + Please note that personalized candles designed in our interactive custom builder—where custom scents are blended, or labels are printed with bespoke texts—are classified as custom-built commissions. They are entirely non-returnable and non-refundable unless they suffer from manufacturing defects or transit breakage. + + + + + + 3. B2B Wholesale Order Policies + + + B2B custom packaging, private label productions, and custom corporate candle batches are non-refundable once the manufacturing contract has been signed and deposits received. We provide pilot product samples before bulk manufacturing to align on scent tolerances. + + + + + + 4. How to Process a Refund + + + To initiate a return, write to our customer support desk at support@manicandles.com. Include your Order ID and photos of the unburned products. Once approved, refunds are credited back to your original payment account within 5-7 bank business days. + +
+
+ ); +}; + +export default RefundPolicyPage; diff --git a/src/features/legal/pages/ShippingPolicyPage.tsx b/src/features/legal/pages/ShippingPolicyPage.tsx new file mode 100644 index 0000000..bf4c752 --- /dev/null +++ b/src/features/legal/pages/ShippingPolicyPage.tsx @@ -0,0 +1,61 @@ +import React from 'react'; +import { Container, Typography, Box, Paper, Divider } from '@mui/material'; +import SEOHelmet from '../../../components/common/SEOHelmet'; + +export const ShippingPolicyPage: React.FC = () => { + return ( + + + + + Legal & Compliance + + + Shipping Policy + + + Last Updated: July 5, 2026 + + + + + + 1. B2C Delivery Timelines & Fees + + + We partner with premium courier services (Blue Dart, Delhivery) to ship across India: + + +
    +
  • Shipping Rates: Complimentary for orders above ₹999. A flat shipping fee of ₹99 is applicable below this amount.
  • +
  • Processing Time: 1-2 business days for ready candles. Customized candles require 3-4 business days for custom printing and drying before shipment.
  • +
  • Transit Timelines: Metro cities (3-5 business days), rest of India (5-7 business days).
  • +
+
+ + + + + 2. B2B & Bulk Logistics + + + For corporate gifts, hospitality clients, and private labels, shipping is quoted ex-factory Kochi or on actuals. Since large orders are heavy and require wooden pallet packaging, they are dispatched via trusted road logistics carriers (TCI, V-Trans). Transit times range between 5 to 10 days depending on the state destination. + + + + + + 3. Damage in Transit + + + Candles are fragile glass items. While we pack them with eco-friendly protective layers, if you receive a damaged candle, please email a photo or unboxing video to orders@manicandles.com within 48 hours of delivery. We will dispatch a complimentary replacement candle immediately. + +
+
+ ); +}; + +export default ShippingPolicyPage; diff --git a/src/features/legal/pages/TermsConditionsPage.tsx b/src/features/legal/pages/TermsConditionsPage.tsx new file mode 100644 index 0000000..94ed564 --- /dev/null +++ b/src/features/legal/pages/TermsConditionsPage.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { Container, Typography, Box, Paper, Divider } from '@mui/material'; +import SEOHelmet from '../../../components/common/SEOHelmet'; + +export const TermsConditionsPage: React.FC = () => { + return ( + + + + + Legal & Compliance + + + Terms & Conditions + + + Last Updated: July 5, 2026 + + + + + + 1. Terms of Use + + + By accessing and browsing this website, or utilizing our interactive custom candle customized workspace, you agree to comply with our Terms & Conditions. These terms apply to all visitors, B2C retail shoppers, and B2B corporate buyers. + + + + + + 2. Custom Candle Builder & Intellectual Property + + + When you use our Custom Candle Builder to input personalized text, upload branding files, or specify fragrance names: + + +
    +
  • You warrant that you own or have the proper license for any brand logos or text uploaded.
  • +
  • We reserve the right to refuse printing any labels containing hate speech, copyrighted brands, or offensive phrasing.
  • +
  • Customized orders cannot be cancelled or refunded once wax pouring and printing have begun.
  • +
+
+ + + + + 3. B2B Wholesale Accounts + + + Our B2B prices are reserved strictly for registered businesses with a valid GSTIN number in India. All corporate quotes are valid for 30 calendar days. Bulk shipments are dispatched only upon receipt of 100% advance payments unless credit terms have been formally approved in writing. + + + + + + 4. Jurisdiction + + + These terms shall be governed by and construed in accordance with the laws of India. Any disputes arising out of your purchases or use of this website shall be subject to the exclusive jurisdiction of the courts located in Ernakulam, Kerala, India. + +
+
+ ); +}; + +export default TermsConditionsPage; diff --git a/src/features/shop/components/ProductCard.tsx b/src/features/shop/components/ProductCard.tsx new file mode 100644 index 0000000..a071a8c --- /dev/null +++ b/src/features/shop/components/ProductCard.tsx @@ -0,0 +1,248 @@ +import React, { useState } from 'react'; +import { Card, CardMedia, CardContent, Typography, Box, IconButton, Button, Chip } from '@mui/material'; +import { IoIosHeartEmpty, IoIosHeart, IoIosEye } from 'react-icons/io'; +import { MdOutlineShoppingBag } from 'react-icons/md'; +import { Link as RouterLink } from 'react-router-dom'; +import type { Product } from '../../../types'; +import { useWishlist } from '../../../contexts/WishlistContext'; +import { useCart } from '../../../contexts/CartContext'; +import { formatCurrency } from '../../../utils/helpers'; +import QuickViewModal from './QuickViewModal'; + +interface ProductCardProps { + product: Product; + isB2BMode: boolean; +} + +export const ProductCard: React.FC = ({ product, isB2BMode }) => { + const { toggleWishlist, isInWishlist } = useWishlist(); + const { addToCart } = useCart(); + const [quickViewOpen, setQuickViewOpen] = useState(false); + + const favorited = isInWishlist(product.id); + const activePrice = isB2BMode ? product.b2bPrice : product.price; + + const handleAddToCartClick = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + // In B2B mode, add the minimum B2B quantity immediately. In B2C, add 1. + const qty = isB2BMode ? product.minB2BQuantity : 1; + addToCart(product, qty, isB2BMode ? 'B2B' : 'B2C'); + }; + + return ( + <> + + {/* Badges (New / Best Seller) */} + + {product.isBestSeller && ( + + )} + {product.isNew && ( + + )} + + + {/* Wishlist Button */} + { + e.preventDefault(); + e.stopPropagation(); + toggleWishlist(product); + }} + sx={{ + position: 'absolute', + top: 12, + right: 12, + zIndex: 3, + backgroundColor: 'rgba(255, 255, 255, 0.9)', + color: favorited ? 'error.main' : 'secondary.main', + boxShadow: '0 4px 10px rgba(0,0,0,0.05)', + '&:hover': { + backgroundColor: 'primary.main', + color: '#FFFFFF', + }, + }} + > + {favorited ? : } + + + {/* Image Frame */} + + + + {/* Quick Actions (Slide-up on desktop hover) */} + + + { + e.preventDefault(); + e.stopPropagation(); + setQuickViewOpen(true); + }} + sx={{ + backgroundColor: 'rgba(255,255,255,0.95)', + color: 'secondary.main', + borderRadius: 2, + '&:hover': { + backgroundColor: 'secondary.main', + color: 'background.default', + }, + }} + > + + + + + + {/* Content */} + + + {product.category} COLLECTION + + + {product.name} + + + {product.tagline} + + + + + + {formatCurrency(activePrice)} + + {isB2BMode && ( + + Min order: {product.minB2BQuantity} pcs + + )} + + + {product.weight} + + + + + + {/* Quick View Dialog */} + setQuickViewOpen(false)} + product={product} + isB2BMode={isB2BMode} + /> + + ); +}; + +export default ProductCard; diff --git a/src/features/shop/components/QuickViewModal.tsx b/src/features/shop/components/QuickViewModal.tsx new file mode 100644 index 0000000..73198db --- /dev/null +++ b/src/features/shop/components/QuickViewModal.tsx @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogContent, + Box, + Typography, + Grid, + Button, + IconButton, + Divider, + Rating, +} from '@mui/material'; +import { MdClose, MdOutlineShoppingBag, MdAdd, MdRemove } from 'react-icons/md'; +import type { Product } from '../../../types'; +import { useCart } from '../../../contexts/CartContext'; +import { formatCurrency } from '../../../utils/helpers'; +import { useNavigate } from 'react-router-dom'; + +interface QuickViewModalProps { + open: boolean; + onClose: () => void; + product: Product; + isB2BMode: boolean; +} + +export const QuickViewModal: React.FC = ({ open, onClose, product, isB2BMode }) => { + const navigate = useNavigate(); + const { addToCart } = useCart(); + const [quantity, setQuantity] = useState(isB2BMode ? product.minB2BQuantity : 1); + + const activePrice = isB2BMode ? product.b2bPrice : product.price; + + const handleAddToCart = () => { + addToCart(product, quantity, isB2BMode ? 'B2B' : 'B2C'); + onClose(); + }; + + const handleDetailsClick = () => { + onClose(); + navigate(`/shop/${product.id}`); + }; + + return ( + + + + + + + + + + {/* Product Image Panel */} + + + + + {/* Product Info Panel */} + + + {product.category} Collection + + + {product.name} + + + + + ({product.reviewsCount} verified reviews) + + + + + + + {formatCurrency(activePrice)} {isB2BMode && / Unit (Min Qty: {product.minB2BQuantity})} + + + + {product.description} + + + {/* Quick Specs */} + + + Wax Composition + {product.waxType} + + + Burn Duration + ~{product.burnTime} Hours + + + Weight + {product.weight} + + + Olfactory Profile + {product.fragranceProfile} + + + + {/* Actions Bar */} + + + setQuantity(q => Math.max(isB2BMode ? product.minB2BQuantity : 1, q - 1))} + disabled={isB2BMode && quantity <= product.minB2BQuantity} + > + + + + {quantity} + + setQuantity(q => q + 1)}> + + + + + + + + + + + + + ); +}; + +export default QuickViewModal; diff --git a/src/features/shop/pages/ProductDetailPage.tsx b/src/features/shop/pages/ProductDetailPage.tsx new file mode 100644 index 0000000..8ee2921 --- /dev/null +++ b/src/features/shop/pages/ProductDetailPage.tsx @@ -0,0 +1,441 @@ +import React, { useState, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; +import { + Container, + Grid, + Box, + Typography, + Button, + Rating, + Divider, + Tabs, + Tab, + TextField, + Chip, + Alert, + Dialog, + DialogTitle, + DialogContent, + DialogActions, + IconButton, +} from '@mui/material'; +import { MdOutlineShoppingBag, MdAdd, MdRemove } from 'react-icons/md'; +import { IoIosHeartEmpty, IoIosHeart, IoLogoWhatsapp } from 'react-icons/io'; + +import SEOHelmet from '../../../components/common/SEOHelmet'; +import { mockProducts } from '../../../utils/data'; +import { useCart } from '../../../contexts/CartContext'; +import { useWishlist } from '../../../contexts/WishlistContext'; +import { formatCurrency } from '../../../utils/helpers'; +import ProductCard from '../components/ProductCard'; + +interface ProductDetailPageProps { + isB2BMode: boolean; +} + +export const ProductDetailPage: React.FC = ({ isB2BMode }) => { + const { id } = useParams<{ id: string }>(); + + const { addToCart } = useCart(); + const { toggleWishlist, isInWishlist } = useWishlist(); + + // Find active product + const product = useMemo(() => { + return mockProducts.find((p) => p.id === id) || mockProducts[0]; + }, [id]); + + const [activeImageIndex, setActiveImageIndex] = useState(0); + const [quantity, setQuantity] = useState(isB2BMode ? product.minB2BQuantity : 1); + const [activeTab, setActiveTab] = useState(0); + const [b2bInquiryOpen, setB2BInquiryOpen] = useState(false); + const [inquiryFormSent, setInquiryFormSent] = useState(false); + + // Sync B2B minimum quantity + React.useEffect(() => { + setQuantity(isB2BMode ? product.minB2BQuantity : 1); + }, [isB2BMode, product]); + + const isFavorited = isInWishlist(product.id); + const activePrice = isB2BMode ? product.b2bPrice : product.price; + + // Pre-fill WhatsApp message + const whatsappUrl = useMemo(() => { + const phoneNumber = '919999999999'; + const text = encodeURIComponent( + `Hi Mani Candles, I am interested in purchasing ${product.name} (ID: ${product.id}). Mode: ${isB2BMode ? 'B2B Wholesale' : 'B2C Retail'}. Please share availability.` + ); + return `https://wa.me/${phoneNumber}?text=${text}`; + }, [product, isB2BMode]); + + const handleAddToCart = () => { + addToCart(product, quantity, isB2BMode ? 'B2B' : 'B2C'); + }; + + const handleB2BInquirySubmit = (e: React.FormEvent) => { + e.preventDefault(); + setInquiryFormSent(true); + }; + + const handleCloseB2BInquiry = () => { + setB2BInquiryOpen(false); + setInquiryFormSent(false); + }; + + // Generate related items (excluding current product) + const relatedProducts = useMemo(() => { + return mockProducts.filter((p) => p.id !== product.id).slice(0, 3); + }, [product]); + + return ( + + + + + {isB2BMode && ( + + Corporate Wholesale view active: Special discounted price of {formatCurrency(product.b2bPrice)} per unit is unlocked for minimum batch orders of {product.minB2BQuantity} items. + + )} + + + {/* Left Column: Image Gallery */} + + + {/* Main Image Frame */} + + + {/* Thumbnails grid */} + + {product.images.map((img, index) => ( + setActiveImageIndex(index)} + sx={{ + width: 80, + height: 80, + borderRadius: 2, + border: activeImageIndex === index ? '2px solid #8E7557' : '1px solid #E6DFD5', + backgroundImage: `url(${img})`, + backgroundSize: 'cover', + backgroundPosition: 'center', + cursor: 'pointer', + flexShrink: 0, + transition: 'all 0.3s ease', + '&:hover': { borderColor: 'primary.light' }, + }} + /> + ))} + + + + + {/* Right Column: Spec / Details Panel */} + + + + + {product.category} COLLECTION + + + toggleWishlist(product)} sx={{ border: '1px solid #E6DFD5', color: isFavorited ? 'error.main' : 'secondary.main' }}> + {isFavorited ? : } + + + + + {product.name} + + + + + + {product.rating} ({product.reviewsCount} verified reviews) + + + + + {formatCurrency(activePrice)}{' '} + {isB2BMode && ( + + / unit (Min: {product.minB2BQuantity} units) + + )} + + + + {product.description} + + + + + {/* Scent Architecture */} + + + Scent Profile & Architecture + + + + TOP NOTE + {product.fragranceNotes.top.join(', ')} + + + HEART NOTE + {product.fragranceNotes.middle.join(', ')} + + + BASE NOTE + {product.fragranceNotes.base.join(', ')} + + + + + + + {/* E-commerce Purchase Controls */} + + + setQuantity((q) => Math.max(isB2BMode ? product.minB2BQuantity : 1, q - 1))} + disabled={isB2BMode && quantity <= product.minB2BQuantity} + > + + + + {quantity} + + setQuantity((q) => q + 1)}> + + + + + + + + {/* WhatsApp Checkout Integration & B2B Inquiry Buttons */} + + + + + + + + + + {/* Specifications Tab & FAQ Accordion Section */} + + setActiveTab(val)} + textColor="primary" + indicatorColor="primary" + sx={{ borderBottom: '1px solid #E6DFD5', mb: 4 }} + > + + + + + + + {activeTab === 0 && ( + + + + + Vessel Style + Artisanal Clay/Glass Jars + + + Net Weight + {product.weight} + + + Wax Base + {product.waxType} + + + + + + + Burn Duration + ~{product.burnTime} Hours + + + Fragrance Composition + 100% Botanical Extracts & Essential Oils + + + Wick Material + Organic Lead-free Cotton + + + + + )} + + {activeTab === 1 && ( + + + HOW TO GET THE PERFECT BURN + + + {product.careInstructions.map((inst, index) => ( + + {index + 1}. {inst} + + ))} + + + )} + + {activeTab === 2 && ( + + + We believe in complete ingredient transparency. Mani Candles are formulated using pure natural waxes and high-grade essential oils. This candle contains: + + + {product.ingredients.map((ing, i) => ( + + ))} + + + )} + + {activeTab === 3 && ( + + + Client Testimonials & Feedback + + + {product.reviews.map((rev) => ( + + + + {rev.author}{' '} + {rev.verified && ( + + )} + + + {rev.date} + + + + + "{rev.comment}" + + + ))} + + + )} + + + {/* Related Products Grid */} + + + Olfactory Pairings & Recommendations + + + {relatedProducts.map((p) => ( + + + + ))} + + + + + {/* B2B Request Quote Dialog */} + + + Bulk / Custom Sample Inquiry + + + {inquiryFormSent ? ( + + + Inquiry Logged Successfully + + + Our corporate sales desk will dispatch catalog pricing and sample coordinates to your company email shortly. + + + ) : ( +
+ + + + + + + + + + + +
+ )} +
+
+
+ ); +}; + +export default ProductDetailPage; diff --git a/src/features/shop/pages/ShopPage.tsx b/src/features/shop/pages/ShopPage.tsx new file mode 100644 index 0000000..0a14c1d --- /dev/null +++ b/src/features/shop/pages/ShopPage.tsx @@ -0,0 +1,369 @@ +import React, { useState, useMemo, useEffect } from 'react'; +import { useSearchParams } from 'react-router-dom'; +import { + Container, + Grid, + Box, + Typography, + Paper, + FormGroup, + FormControlLabel, + Checkbox, + Slider, + TextField, + MenuItem, + Button, + Divider, +} from '@mui/material'; +import { IoIosFunnel } from 'react-icons/io'; +import SEOHelmet from '../../../components/common/SEOHelmet'; +import ProductCard from '../components/ProductCard'; +import { mockProducts } from '../../../utils/data'; + +const CATEGORIES = [ + { id: 'classic', label: 'Classic Collection' }, + { id: 'luxury', label: 'Luxury Collection' }, + { id: 'seasonal', label: 'Seasonal Line' }, + { id: 'giftbox', label: 'Heritage Gift Boxes' } +]; + +const FRAGRANCES = [ + { id: 'woody', label: 'Woody' }, + { id: 'floral', label: 'Floral' }, + { id: 'citrus', label: 'Citrus' }, + { id: 'oriental', label: 'Oriental' }, + { id: 'fresh', label: 'Fresh & Airy' } +]; + +const WAXES = [ + { id: 'Soy Wax', label: '100% Soy Wax' }, + { id: 'Coconut Wax', label: 'Coconut Wax' }, + { id: 'Beeswax', label: 'Raw Beeswax' }, + { id: 'Blend', label: 'Soy/Coconut Blend' } +]; + +interface ShopPageProps { + isB2BMode: boolean; +} + +export const ShopPage: React.FC = ({ isB2BMode }) => { + const [searchParams, setSearchParams] = useSearchParams(); + + // Filters State + const [searchQuery, setSearchQuery] = useState(searchParams.get('search') || ''); + const [selectedCategories, setSelectedCategories] = useState([]); + const [selectedFragrances, setSelectedFragrances] = useState([]); + const [selectedWaxes, setSelectedWaxes] = useState([]); + const [burnTimeRange, setBurnTimeRange] = useState([20, 120]); + const [sortBy, setSortBy] = useState('popular'); + + // Sync with URL parameters + useEffect(() => { + const categoryParam = searchParams.get('category'); + if (categoryParam) { + setSelectedCategories([categoryParam]); + } else { + setSelectedCategories([]); + } + + const searchParam = searchParams.get('search'); + if (searchParam) { + setSearchQuery(searchParam); + } + }, [searchParams]); + + // Checkbox handlers + const handleCategoryChange = (cat: string) => { + setSelectedCategories(prev => + prev.includes(cat) ? prev.filter(c => c !== cat) : [...prev, cat] + ); + }; + + const handleFragranceChange = (frag: string) => { + setSelectedFragrances(prev => + prev.includes(frag) ? prev.filter(f => f !== frag) : [...prev, frag] + ); + }; + + const handleWaxChange = (wax: string) => { + setSelectedWaxes(prev => + prev.includes(wax) ? prev.filter(w => w !== wax) : [...prev, wax] + ); + }; + + const clearAllFilters = () => { + setSearchQuery(''); + setSelectedCategories([]); + setSelectedFragrances([]); + setSelectedWaxes([]); + setBurnTimeRange([20, 120]); + setSortBy('popular'); + setSearchParams({}); + }; + + // Filtered Products computation + const filteredProducts = useMemo(() => { + return mockProducts + .filter(product => { + // Search filter + if (searchQuery.trim()) { + const query = searchQuery.toLowerCase(); + const matchName = product.name.toLowerCase().includes(query); + const matchTagline = product.tagline.toLowerCase().includes(query); + const matchProfile = product.fragranceProfile.toLowerCase().includes(query); + if (!matchName && !matchTagline && !matchProfile) return false; + } + + // Category filter + if (selectedCategories.length > 0 && !selectedCategories.includes(product.category)) { + return false; + } + + // Fragrance filter + if (selectedFragrances.length > 0 && !selectedFragrances.includes(product.fragranceProfile)) { + return false; + } + + // Wax filter + if (selectedWaxes.length > 0 && !selectedWaxes.includes(product.waxType)) { + return false; + } + + // Burn time filter + if (product.burnTime < burnTimeRange[0] || product.burnTime > burnTimeRange[1]) { + return false; + } + + return true; + }) + .sort((a, b) => { + const aPrice = isB2BMode ? a.b2bPrice : a.price; + const bPrice = isB2BMode ? b.b2bPrice : b.price; + + if (sortBy === 'price-low') return aPrice - bPrice; + if (sortBy === 'price-high') return bPrice - aPrice; + if (sortBy === 'rating') return b.rating - a.rating; + // Default popular/bestseller + return (b.isBestSeller ? 1 : 0) - (a.isBestSeller ? 1 : 0); + }); + }, [searchQuery, selectedCategories, selectedFragrances, selectedWaxes, burnTimeRange, sortBy, isB2BMode]); + + return ( + + + + + {/* Desktop Filter Sidebar */} + + + + + FILTERS + + + + + {/* Text Search inside sidebar */} + + setSearchQuery(e.target.value)} + variant="outlined" + sx={{ + '& .MuiOutlinedInput-root': { + borderRadius: 2, + backgroundColor: 'background.default', + '& fieldset': { borderColor: '#E6DFD5' }, + }, + }} + /> + + + {/* Categories Filter */} + + + CATEGORY + + + {CATEGORIES.map(cat => ( + handleCategoryChange(cat.id)} + /> + } + label={{cat.label}} + /> + ))} + + + + + + {/* Fragrance Profile */} + + + OLFACTORY NOTES + + + {FRAGRANCES.map(frag => ( + handleFragranceChange(frag.id)} + /> + } + label={{frag.label}} + /> + ))} + + + + + + {/* Wax Type */} + + + WAX INGREDIENT + + + {WAXES.map(wax => ( + handleWaxChange(wax.id)} + /> + } + label={{wax.label}} + /> + ))} + + + + + + {/* Burn Time Slider */} + + + BURN TIME (HOURS) + + setBurnTimeRange(val as number[])} + valueLabelDisplay="auto" + min={20} + max={120} + color="primary" + /> + + {burnTimeRange[0]} hrs + {burnTimeRange[1]} hrs + + + + + + {/* Catalog Right Panel */} + + {/* Top Toolbar */} + + + + The Candle Atelier + + + Showing {filteredProducts.length} premium pouring formulas + + + + + {/* Mobile Filter Button */} + + + {/* Sort selector */} + setSortBy(e.target.value)} + variant="outlined" + label="Sort By" + sx={{ + width: 180, + '& .MuiOutlinedInput-root': { + borderRadius: 2, + '& fieldset': { borderColor: '#E6DFD5' }, + }, + }} + > + Best Sellers + Price: Low to High + Price: High to Low + Top Rated + + + + + {/* Catalog Grid */} + {filteredProducts.length === 0 ? ( + + + No candles match your filters + + + Try adjusting your burn times, fragrance categories, or clearing queries. + + + + ) : ( + + {filteredProducts.map(product => ( + + + + ))} + + )} + + + + + ); +}; + +export default ShopPage; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..d68e909 --- /dev/null +++ b/src/index.css @@ -0,0 +1,576 @@ +:root { + --brand-purple: #5B1E8C; + --brand-purple-dark: #3A1260; + --brand-purple-mid: #7B3DB8; + --brand-lavender: #DCC6F2; + --brand-light-lavender: #EFE4FA; + --brand-card: #F8F2FD; + --brand-gold: #D4A24C; + --brand-gold-light: #FFD36B; + --brand-gold-core: #FFF3C4; + --brand-text: #2D0D52; + --brand-muted: #7B5A9E; + --brand-border: #D5BEF0; + --font-sans: 'Inter', sans-serif; + --font-serif: 'Cormorant Garamond', serif; + --font-display: 'Cinzel', serif; + --font-script: 'Great Vibes', cursive; + + font-family: var(--font-sans); + line-height: 1.5; + font-weight: 400; + + color-scheme: light; + color: var(--brand-text); + background-color: var(--brand-light-lavender); +} + +body { + margin: 0; + padding: 0; + min-height: 100vh; + overflow-x: hidden; +} + +/* Custom Fine Scrollbar */ +::-webkit-scrollbar { + width: 6px; + height: 6px; +} + +::-webkit-scrollbar-track { + background: var(--brand-light-lavender); +} + +::-webkit-scrollbar-thumb { + background: var(--brand-muted); + border-radius: 3px; +} + +::-webkit-scrollbar-thumb:hover { + background: var(--brand-purple); +} + +/* Glassmorphism Styles */ +.glass-header { + background: rgba(239, 228, 250, 0.85) !important; + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); +} + +.glass-card { + background: rgba(255, 255, 255, 0.7) !important; + backdrop-filter: blur(12px) !important; + -webkit-backdrop-filter: blur(12px) !important; + border: 1px solid rgba(213, 190, 240, 0.6) !important; +} + +/* ---------------------------------------------------- + SCROLL BURNING CANDLE STYLES + ---------------------------------------------------- */ +.scroll-candle-wrap { + position: fixed; + right: 28px; /* Far right edge */ + bottom: 16px; + z-index: 999; + display: flex; + flex-direction: column; + align-items: center; + pointer-events: none; + filter: drop-shadow(0 0 12px rgba(91, 30, 140, 0.25)); + transition: opacity 0.5s ease; +} + +/* Responsive adjustment to hide scroll candle on mobile screens */ +@media (max-width: 600px) { + .scroll-candle-wrap { + display: none !important; + } +} + +.candle-flame-wrap { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + margin-bottom: -2px; + transition: opacity 0.5s ease, transform 0.5s ease; +} + +.flame-glow { + position: absolute; + bottom: 0; + left: 50%; + transform: translate(-50%); + width: 48px; + height: 48px; + border-radius: 50%; + background: radial-gradient(circle, rgba(255, 200, 50, 0.45) 0%, transparent 70%); + animation: glow-pulse 1.8s ease-in-out infinite alternate; + z-index: 0; +} + +@keyframes glow-pulse { + 0% { + transform: translate(-50%) scale(1); + opacity: 0.7; + } + 100% { + transform: translate(-50%) scale(1.35); + opacity: 1; + } +} + +.flame-outer { + position: relative; + z-index: 2; + width: 18px; + height: 34px; + background: linear-gradient(to top, #ff6a00, #fa0, #ffe066); + border-radius: 50% 50% 30% 30% / 60% 60% 40% 40%; + transform-origin: bottom center; + animation: flicker-outer 0.9s ease-in-out infinite alternate; + box-shadow: 0 0 8px 3px rgba(255, 140, 0, 0.6), 0 0 18px 6px rgba(255, 100, 0, 0.4); +} + +@keyframes flicker-outer { + 0% { + transform: scaleX(1) scaleY(1) rotate(-2deg); + } + 25% { + transform: scaleX(0.88) scaleY(1.05) rotate(2deg); + } + 50% { + transform: scaleX(1.05) scaleY(0.95) rotate(-1deg); + } + 75% { + transform: scaleX(0.92) scaleY(1.08) rotate(3deg); + } + 100% { + transform: scaleX(1.02) scaleY(1.02) rotate(-2deg); + } +} + +.flame-inner { + position: absolute; + bottom: 4px; + left: 50%; + transform: translate(-50%); + z-index: 3; + width: 8px; + height: 18px; + background: linear-gradient(to top, #fff8c5, #fff, rgba(255, 240, 240, 0)); + border-radius: 50% 50% 30% 30% / 60% 60% 40% 40%; + animation: flicker-inner 0.7s ease-in-out infinite alternate; + opacity: 0.9; +} + +@keyframes flicker-inner { + 0% { + transform: translate(-50%) scaleX(1) scaleY(1); + } + 50% { + transform: translate(-50%) scaleX(0.85) scaleY(1.1); + } + 100% { + transform: translate(-50%) scaleX(1.1) scaleY(0.9); + } +} + +.smoke-wrap { + position: absolute; + top: -18px; + left: 50%; + transform: translate(-50%); + width: 20px; + height: 20px; + pointer-events: none; + z-index: 4; +} + +.smoke-particle { + position: absolute; + bottom: 0; + left: 50%; + width: 4px; + height: 4px; + border-radius: 50%; + background: rgba(200, 180, 220, 0.45); + transform: translate(-50%); + animation: smoke-rise 2.4s ease-out infinite; + opacity: 0; +} + +.smoke-particle:nth-child(2) { + animation-delay: 0.8s; + left: 40%; +} + +.smoke-particle:nth-child(3) { + animation-delay: 1.6s; + left: 60%; +} + +@keyframes smoke-rise { + 0% { + transform: translate(-50%) translateY(0) scale(0.6); + opacity: 0; + } + 20% { + opacity: 0.5; + } + 100% { + transform: translate(-50%) translateY(-22px) scale(2); + opacity: 0; + } +} + +.candle-wick { + width: 2px; + height: 10px; + background: linear-gradient(to bottom, #3a2000, #1a0f00); + border-radius: 1px; + position: relative; + z-index: 2; + margin-bottom: -1px; +} + +.candle-wick-holder { + width: 12px; + height: 3px; + background: linear-gradient(to right, #bf953f, #fcf6ba, #aa771c); + border-radius: 1px; + margin-bottom: -1px; + z-index: 2; +} + +.candle-body { + position: relative; + width: 12px; /* Sleek, tall luxury rod */ + background: linear-gradient(90deg, + #3a1260 0%, + #5b1e8c 30%, + #7b3db8 50%, + #5b1e8c 70%, + #1e0a27 100% + ); + border-radius: 2px 2px 3px 3px; + transition: height 0.15s ease-out; + box-shadow: + inset -1px 0 3px rgba(0, 0, 0, 0.4), + inset 1px 0 3px rgba(255, 255, 255, 0.2), + 0 0 12px rgba(91, 30, 140, 0.2); + overflow: visible; +} + +.wax-drip { + position: absolute; + top: 2px; + width: 3px; + background: linear-gradient(to bottom, #fcf6ba, #aa771c); + border-radius: 0 0 2px 2px; + animation: drip-grow 5s ease-in-out infinite; +} + +.wax-drip-1 { + left: -2px; + height: 12px; + animation-delay: 0s; +} + +.wax-drip-2 { + right: -2px; + height: 8px; + animation-delay: 1.5s; +} + +@keyframes drip-grow { + 0% { + height: 3px; + opacity: 0.7; + } + 60% { + height: 14px; + opacity: 1; + } + 100% { + height: 3px; + opacity: 0.7; + } +} + +.candle-base { + width: 20px; + height: 8px; + background: linear-gradient(to right, #bf953f, #fcf6ba, #b38728, #aa771c); + border-radius: 1px 1px 2px 2px; + box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3); +} + +.candle-scroll-label { + margin-top: 6px; + font-family: var(--font-script); + font-size: 0.8rem; + color: var(--brand-gold); + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + letter-spacing: 0.05em; + white-space: nowrap; + text-align: center; + transition: opacity 0.3s ease; +} + +/* ---------------------------------------------------- + ANIMATED HERO SECTION (EMBERS & FOG) STYLES + ---------------------------------------------------- */ +.arrival-hero { + position: relative; + height: 85vh; + overflow: hidden; + display: flex; + align-items: center; + background: radial-gradient(circle at center, rgba(212, 162, 76, 0.08), transparent 35%), + linear-gradient(135deg, #22112f, #3a1260, #5b1e8c); +} + +.arrival-hero::after { + content: ''; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: radial-gradient(circle at center, transparent 45%, rgba(0, 0, 0, 0.35)); + pointer-events: none; + z-index: 2; +} + +.arrival-content { + position: relative; + z-index: 5; + height: 100%; + width: 100%; +} + +.arrival-copy h1 { + text-shadow: 0 10px 40px rgba(255, 255, 255, 0.08); +} + +.arrival-candle { + filter: drop-shadow(0 30px 60px rgba(0, 0, 0, 0.45)); + transition: filter 2s ease, transform 2s ease; +} + +.arrival-glow { + position: absolute; + width: 350px; + height: 350px; + border-radius: 50%; + filter: blur(90px); + z-index: 1; + background: radial-gradient(circle, rgba(255, 211, 107, 0.25) 0%, transparent 70%); + animation: breathe 6s ease-in-out infinite; +} + +@keyframes breathe { + 0%, + 100% { + transform: scale(1); + opacity: 0.5; + } + 50% { + transform: scale(1.15); + opacity: 0.9; + } +} + +/* Fog Drift Background */ +.fog { + position: absolute; + width: 900px; + height: 900px; + border-radius: 50%; + background: radial-gradient(circle, rgba(255, 255, 255, 0.03), transparent 70%); + filter: blur(120px); + pointer-events: none; + z-index: 1; +} + +.fog-1 { + top: -300px; + left: -200px; + animation: drift 30s infinite alternate; +} + +.fog-2 { + right: -250px; + top: -100px; + animation: drift 35s infinite alternate-reverse; +} + +.fog-3 { + bottom: -350px; + left: 25%; + animation: drift 40s infinite alternate; +} + +@keyframes drift { + 0% { + transform: translate(0); + } + 100% { + transform: translate(120px, 40px); + } +} + +/* Floating Embers */ +.embers { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + pointer-events: none; + z-index: 2; +} + +.embers span { + position: absolute; + width: 4px; + height: 4px; + border-radius: 50%; + background: var(--brand-gold-light); + box-shadow: 0 0 12px var(--brand-gold-light), 0 0 24px rgba(212, 162, 76, 0.6); + animation: ember 12s linear infinite; + opacity: 0; +} + +@keyframes ember { + 0% { + transform: translateY(110vh) translateX(0) scale(0.8); + opacity: 0; + } + 10% { + opacity: 0.8; + } + 90% { + opacity: 0.8; + } + 100% { + transform: translateY(-20vh) translateX(60px) scale(1.2); + opacity: 0; + } +} + +/* Scroll bounce indicator */ +.scroll-indicator { + position: absolute; + bottom: 30px; + left: 50%; + transform: translate(-50%); + color: rgba(255, 255, 255, 0.7); + font-family: var(--font-display); + font-size: 0.8rem; + letter-spacing: 0.3em; + text-transform: uppercase; + animation: bounce 2s infinite; + z-index: 3; +} + +@keyframes bounce { + 0%, + 100% { + transform: translate(-50%) translateY(0); + } + 50% { + transform: translate(-50%) translateY(8px); + } +} + +/* Ticker Announcement Bar override styles */ +.ticker-bar { + background: var(--brand-purple-dark); + border-bottom: 1px solid rgba(212, 162, 76, 0.25); + height: 36px; + overflow: hidden; + display: flex; + align-items: center; +} + +.ticker-track { + display: flex; + align-items: center; + gap: 0; + animation: ticker-scroll 28s linear infinite; + white-space: nowrap; + will-change: transform; +} + +@keyframes ticker-scroll { + 0% { + transform: translate3d(0, 0, 0); + } + 100% { + transform: translate3d(-50%, 0, 0); + } +} + +.ticker-item { + display: inline-flex; + align-items: center; + gap: 0.6rem; + font-family: var(--font-display); + font-size: 0.65rem; + letter-spacing: 0.2em; + text-transform: uppercase; + color: rgba(255, 255, 255, 0.85); + padding: 0 2.5rem; +} + +.ticker-dot { + width: 5px; + height: 5px; + border-radius: 50%; + background: var(--brand-gold); + flex-shrink: 0; + display: inline-block; +} + +/* Swiper styling overrides for elegant navigation */ +.swiper-pagination-bullet-active { + background: var(--brand-purple) !important; + width: 20px !important; + border-radius: 4px !important; + transition: all 0.3s ease; +} + +.swiper-button-next, +.swiper-button-prev { + color: var(--brand-purple) !important; + transform: scale(0.6); + background: rgba(255, 255, 255, 0.8); + border-radius: 50%; + width: 50px !important; + height: 50px !important; + box-shadow: 0 4px 10px rgba(0, 0, 0, 0.05); + transition: all 0.3s ease; +} + +.swiper-button-next:hover, +.swiper-button-prev:hover { + background: var(--brand-purple); + color: #ffffff !important; +} + +.swiper-button-next::after, +.swiper-button-prev::after { + font-size: 20px !important; + font-weight: bold; +} + +/* Hide Swiper default arrows on mobile */ +@media (max-width: 600px) { + .swiper-button-next, + .swiper-button-prev { + display: none !important; + } +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..e99750c --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './app/App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/routes/AppRoutes.tsx b/src/routes/AppRoutes.tsx new file mode 100644 index 0000000..8ad9674 --- /dev/null +++ b/src/routes/AppRoutes.tsx @@ -0,0 +1,97 @@ +import React from 'react'; +import { Routes, Route, Navigate } from 'react-router-dom'; +import { Box } from '@mui/material'; + +// Common Components +import Header from '../components/common/Header'; +import Footer from '../components/common/Footer'; +import AnnouncementBar from '../components/common/AnnouncementBar'; +import WishlistDrawer from '../components/common/WishlistDrawer'; +import CartDrawer from '../components/common/CartDrawer'; +import WhatsAppButton from '../components/common/WhatsAppButton'; +import ScrollToTop from '../components/common/ScrollToTop'; +import CookieConsent from '../components/common/CookieConsent'; +import NewsletterPopup from '../components/common/NewsletterPopup'; +import Breadcrumbs from '../components/common/Breadcrumbs'; +import ScrollCandle from '../components/common/ScrollCandle'; + +// Pages +import HomePage from '../features/home/pages/HomePage'; +import ShopPage from '../features/shop/pages/ShopPage'; +import ProductDetailPage from '../features/shop/pages/ProductDetailPage'; +import CustomCandlePage from '../features/custom/pages/CustomCandlePage'; +import CorporateGiftingPage from '../features/b2b/pages/CorporateGiftingPage'; +import HospitalityPage from '../features/b2b/pages/HospitalityPage'; +import PrivateLabelPage from '../features/b2b/pages/PrivateLabelPage'; +import JournalPage from '../features/journal/pages/JournalPage'; +import ArticleDetailPage from '../features/journal/pages/ArticleDetailPage'; +import AboutPage from '../features/about/pages/AboutPage'; +import ContactPage from '../features/contact/pages/ContactPage'; + +// Policy Pages +import PrivacyPolicyPage from '../features/legal/pages/PrivacyPolicyPage'; +import TermsConditionsPage from '../features/legal/pages/TermsConditionsPage'; +import ShippingPolicyPage from '../features/legal/pages/ShippingPolicyPage'; +import RefundPolicyPage from '../features/legal/pages/RefundPolicyPage'; + +interface AppRoutesProps { + isB2BMode: boolean; + setIsB2BMode: (val: boolean) => void; +} + +export const AppRoutes: React.FC = ({ isB2BMode, setIsB2BMode }) => { + return ( + + {/* Dynamic Announcement Bar */} + + + {/* Sticky Header with B2C/B2B toggler */} +
+ + {/* Breadcrumb Path Trace */} + + + {/* Main View Area */} + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + {/* Legal policy routes */} + } /> + } /> + } /> + } /> + + {/* Fallback */} + } /> + + + + {/* Brand Footer */} +