diff --git a/.babel-plugin-macrosrc.json b/.babel-plugin-macrosrc.json
new file mode 100644
index 0000000..6e2bde1
--- /dev/null
+++ b/.babel-plugin-macrosrc.json
@@ -0,0 +1,5 @@
+{
+ "fontawesome-svg-core": {
+ "license": "free"
+ }
+}
diff --git a/.eslintrc.cjs b/.eslintrc.cjs
new file mode 100644
index 0000000..d6c9537
--- /dev/null
+++ b/.eslintrc.cjs
@@ -0,0 +1,18 @@
+module.exports = {
+ root: true,
+ env: { browser: true, es2020: true },
+ extends: [
+ 'eslint:recommended',
+ 'plugin:@typescript-eslint/recommended',
+ 'plugin:react-hooks/recommended',
+ ],
+ ignorePatterns: ['dist', '.eslintrc.cjs'],
+ parser: '@typescript-eslint/parser',
+ plugins: ['react-refresh'],
+ rules: {
+ 'react-refresh/only-export-components': [
+ 'warn',
+ { allowConstantExport: true },
+ ],
+ },
+}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9cdae37
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# 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?
+
+# environment variables
+.env
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1ebe379
--- /dev/null
+++ b/README.md
@@ -0,0 +1,27 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
+
+Currently, two official plugins are available:
+
+- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
+
+- Configure the top-level `parserOptions` property like this:
+
+```js
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ project: ['./tsconfig.json', './tsconfig.node.json'],
+ tsconfigRootDir: __dirname,
+ },
+```
+
+- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
+- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
+- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
diff --git a/db/current-weather.json b/db/current-weather.json
new file mode 100644
index 0000000..1e27514
--- /dev/null
+++ b/db/current-weather.json
@@ -0,0 +1,43 @@
+{
+ "coord": {
+ "lon": 96.161,
+ "lat": 16.7967
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "base": "stations",
+ "main": {
+ "temp": 29.03,
+ "feels_like": 32.79,
+ "temp_min": 29.03,
+ "temp_max": 29.03,
+ "pressure": 1013,
+ "humidity": 70
+ },
+ "visibility": 7000,
+ "wind": {
+ "speed": 1.54,
+ "deg": 320
+ },
+ "clouds": {
+ "all": 20
+ },
+ "dt": 1704547157,
+ "sys": {
+ "type": 1,
+ "id": 9322,
+ "country": "MM",
+ "sunrise": 1704499540,
+ "sunset": 1704539731
+ },
+ "timezone": 23400,
+ "id": 1298824,
+ "name": "Yangon",
+ "cod": 200
+}
diff --git a/db/forecasts.json b/db/forecasts.json
new file mode 100644
index 0000000..85e3f27
--- /dev/null
+++ b/db/forecasts.json
@@ -0,0 +1,1460 @@
+{
+ "cod": "200",
+ "message": 0,
+ "cnt": 40,
+ "list": [
+ {
+ "dt": 1704898800,
+ "main": {
+ "temp": 27.17,
+ "feels_like": 28.07,
+ "temp_min": 23.56,
+ "temp_max": 27.17,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1008,
+ "humidity": 57,
+ "temp_kf": 3.61
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 1
+ },
+ "wind": {
+ "speed": 1.43,
+ "deg": 288,
+ "gust": 1.8
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-10 15:00:00"
+ },
+ {
+ "dt": 1704909600,
+ "main": {
+ "temp": 24.35,
+ "feels_like": 24.17,
+ "temp_min": 22.04,
+ "temp_max": 24.35,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1007,
+ "humidity": 51,
+ "temp_kf": 2.31
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 1
+ },
+ "wind": {
+ "speed": 2.83,
+ "deg": 301,
+ "gust": 3.31
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-10 18:00:00"
+ },
+ {
+ "dt": 1704920400,
+ "main": {
+ "temp": 19.94,
+ "feels_like": 19.22,
+ "temp_min": 19.94,
+ "temp_max": 19.94,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 47,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 3
+ },
+ "wind": {
+ "speed": 3.01,
+ "deg": 330,
+ "gust": 4.93
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-10 21:00:00"
+ },
+ {
+ "dt": 1704931200,
+ "main": {
+ "temp": 18.9,
+ "feels_like": 18.2,
+ "temp_min": 18.9,
+ "temp_max": 18.9,
+ "pressure": 1012,
+ "sea_level": 1012,
+ "grnd_level": 1009,
+ "humidity": 52,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 7
+ },
+ "wind": {
+ "speed": 2.13,
+ "deg": 355,
+ "gust": 2.66
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-11 00:00:00"
+ },
+ {
+ "dt": 1704942000,
+ "main": {
+ "temp": 25.03,
+ "feels_like": 24.63,
+ "temp_min": 25.03,
+ "temp_max": 25.03,
+ "pressure": 1014,
+ "sea_level": 1014,
+ "grnd_level": 1011,
+ "humidity": 40,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 804,
+ "main": "Clouds",
+ "description": "overcast clouds",
+ "icon": "04d"
+ }
+ ],
+ "clouds": {
+ "all": 97
+ },
+ "wind": {
+ "speed": 2.44,
+ "deg": 22,
+ "gust": 2.76
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-11 03:00:00"
+ },
+ {
+ "dt": 1704952800,
+ "main": {
+ "temp": 31.03,
+ "feels_like": 29.51,
+ "temp_min": 31.03,
+ "temp_max": 31.03,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 27,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "clouds": {
+ "all": 64
+ },
+ "wind": {
+ "speed": 2.03,
+ "deg": 345,
+ "gust": 1.81
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-11 06:00:00"
+ },
+ {
+ "dt": 1704963600,
+ "main": {
+ "temp": 32.53,
+ "feels_like": 30.69,
+ "temp_min": 32.53,
+ "temp_max": 32.53,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1006,
+ "humidity": 23,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02d"
+ }
+ ],
+ "clouds": {
+ "all": 15
+ },
+ "wind": {
+ "speed": 2.75,
+ "deg": 308,
+ "gust": 2.75
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-11 09:00:00"
+ },
+ {
+ "dt": 1704974400,
+ "main": {
+ "temp": 26.33,
+ "feels_like": 26.33,
+ "temp_min": 26.33,
+ "temp_max": 26.33,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 35,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 23
+ },
+ "wind": {
+ "speed": 1.78,
+ "deg": 285,
+ "gust": 2.6
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-11 12:00:00"
+ },
+ {
+ "dt": 1704985200,
+ "main": {
+ "temp": 23.91,
+ "feels_like": 23.4,
+ "temp_min": 23.91,
+ "temp_max": 23.91,
+ "pressure": 1012,
+ "sea_level": 1012,
+ "grnd_level": 1009,
+ "humidity": 40,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04n"
+ }
+ ],
+ "clouds": {
+ "all": 63
+ },
+ "wind": {
+ "speed": 2.12,
+ "deg": 228,
+ "gust": 2.38
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-11 15:00:00"
+ },
+ {
+ "dt": 1704996000,
+ "main": {
+ "temp": 21.4,
+ "feels_like": 20.93,
+ "temp_min": 21.4,
+ "temp_max": 21.4,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 51,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "clouds": {
+ "all": 35
+ },
+ "wind": {
+ "speed": 2.16,
+ "deg": 251,
+ "gust": 2.41
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-11 18:00:00"
+ },
+ {
+ "dt": 1705006800,
+ "main": {
+ "temp": 20.34,
+ "feels_like": 19.79,
+ "temp_min": 20.34,
+ "temp_max": 20.34,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 52,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "clouds": {
+ "all": 29
+ },
+ "wind": {
+ "speed": 2.44,
+ "deg": 293,
+ "gust": 2.79
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-11 21:00:00"
+ },
+ {
+ "dt": 1705017600,
+ "main": {
+ "temp": 19.49,
+ "feels_like": 18.9,
+ "temp_min": 19.49,
+ "temp_max": 19.49,
+ "pressure": 1012,
+ "sea_level": 1012,
+ "grnd_level": 1008,
+ "humidity": 54,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "clouds": {
+ "all": 33
+ },
+ "wind": {
+ "speed": 1.08,
+ "deg": 4,
+ "gust": 1.22
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-12 00:00:00"
+ },
+ {
+ "dt": 1705028400,
+ "main": {
+ "temp": 26.11,
+ "feels_like": 26.11,
+ "temp_min": 26.11,
+ "temp_max": 26.11,
+ "pressure": 1013,
+ "sea_level": 1013,
+ "grnd_level": 1010,
+ "humidity": 38,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "clouds": {
+ "all": 63
+ },
+ "wind": {
+ "speed": 1.62,
+ "deg": 63,
+ "gust": 1.54
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-12 03:00:00"
+ },
+ {
+ "dt": 1705039200,
+ "main": {
+ "temp": 31.54,
+ "feels_like": 30.11,
+ "temp_min": 31.54,
+ "temp_max": 31.54,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 28,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "clouds": {
+ "all": 46
+ },
+ "wind": {
+ "speed": 1.22,
+ "deg": 4,
+ "gust": 0.84
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-12 06:00:00"
+ },
+ {
+ "dt": 1705050000,
+ "main": {
+ "temp": 32.61,
+ "feels_like": 30.86,
+ "temp_min": 32.61,
+ "temp_max": 32.61,
+ "pressure": 1008,
+ "sea_level": 1008,
+ "grnd_level": 1005,
+ "humidity": 24,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01d"
+ }
+ ],
+ "clouds": {
+ "all": 2
+ },
+ "wind": {
+ "speed": 1.02,
+ "deg": 300,
+ "gust": 1.25
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-12 09:00:00"
+ },
+ {
+ "dt": 1705060800,
+ "main": {
+ "temp": 25.68,
+ "feels_like": 25.43,
+ "temp_min": 25.68,
+ "temp_max": 25.68,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1006,
+ "humidity": 43,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 6
+ },
+ "wind": {
+ "speed": 1.63,
+ "deg": 205,
+ "gust": 3.13
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-12 12:00:00"
+ },
+ {
+ "dt": 1705071600,
+ "main": {
+ "temp": 23.84,
+ "feels_like": 23.59,
+ "temp_min": 23.84,
+ "temp_max": 23.84,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 50,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 11
+ },
+ "wind": {
+ "speed": 2.32,
+ "deg": 224,
+ "gust": 2.66
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-12 15:00:00"
+ },
+ {
+ "dt": 1705082400,
+ "main": {
+ "temp": 21.74,
+ "feels_like": 21.35,
+ "temp_min": 21.74,
+ "temp_max": 21.74,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 53,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 11
+ },
+ "wind": {
+ "speed": 2.58,
+ "deg": 272,
+ "gust": 3.02
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-12 18:00:00"
+ },
+ {
+ "dt": 1705093200,
+ "main": {
+ "temp": 20.63,
+ "feels_like": 20.08,
+ "temp_min": 20.63,
+ "temp_max": 20.63,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1006,
+ "humidity": 51,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 6
+ },
+ "wind": {
+ "speed": 1.58,
+ "deg": 317,
+ "gust": 1.72
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-12 21:00:00"
+ },
+ {
+ "dt": 1705104000,
+ "main": {
+ "temp": 19.64,
+ "feels_like": 19.07,
+ "temp_min": 19.64,
+ "temp_max": 19.64,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 54,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 7
+ },
+ "wind": {
+ "speed": 1.95,
+ "deg": 354,
+ "gust": 2.06
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-13 00:00:00"
+ },
+ {
+ "dt": 1705114800,
+ "main": {
+ "temp": 25.95,
+ "feels_like": 25.95,
+ "temp_min": 25.95,
+ "temp_max": 25.95,
+ "pressure": 1013,
+ "sea_level": 1013,
+ "grnd_level": 1010,
+ "humidity": 40,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02d"
+ }
+ ],
+ "clouds": {
+ "all": 12
+ },
+ "wind": {
+ "speed": 2.05,
+ "deg": 18,
+ "gust": 1.93
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-13 03:00:00"
+ },
+ {
+ "dt": 1705125600,
+ "main": {
+ "temp": 31.66,
+ "feels_like": 30.44,
+ "temp_min": 31.66,
+ "temp_max": 31.66,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 30,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03d"
+ }
+ ],
+ "clouds": {
+ "all": 25
+ },
+ "wind": {
+ "speed": 0.8,
+ "deg": 50,
+ "gust": 1.01
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-13 06:00:00"
+ },
+ {
+ "dt": 1705136400,
+ "main": {
+ "temp": 32.93,
+ "feels_like": 31.3,
+ "temp_min": 32.93,
+ "temp_max": 32.93,
+ "pressure": 1007,
+ "sea_level": 1007,
+ "grnd_level": 1004,
+ "humidity": 25,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02d"
+ }
+ ],
+ "clouds": {
+ "all": 17
+ },
+ "wind": {
+ "speed": 0.54,
+ "deg": 229,
+ "gust": 1.08
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-13 09:00:00"
+ },
+ {
+ "dt": 1705147200,
+ "main": {
+ "temp": 25.83,
+ "feels_like": 25.72,
+ "temp_min": 25.83,
+ "temp_max": 25.83,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1006,
+ "humidity": 48,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "clouds": {
+ "all": 27
+ },
+ "wind": {
+ "speed": 2.12,
+ "deg": 185,
+ "gust": 3.3
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-13 12:00:00"
+ },
+ {
+ "dt": 1705158000,
+ "main": {
+ "temp": 23.86,
+ "feels_like": 23.69,
+ "temp_min": 23.86,
+ "temp_max": 23.86,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 53,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 13
+ },
+ "wind": {
+ "speed": 2.16,
+ "deg": 209,
+ "gust": 2.49
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-13 15:00:00"
+ },
+ {
+ "dt": 1705168800,
+ "main": {
+ "temp": 22.25,
+ "feels_like": 21.97,
+ "temp_min": 22.25,
+ "temp_max": 22.25,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 55,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 18
+ },
+ "wind": {
+ "speed": 2.42,
+ "deg": 272,
+ "gust": 2.75
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-13 18:00:00"
+ },
+ {
+ "dt": 1705179600,
+ "main": {
+ "temp": 20.97,
+ "feels_like": 20.61,
+ "temp_min": 20.97,
+ "temp_max": 20.97,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 57,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 16
+ },
+ "wind": {
+ "speed": 1.52,
+ "deg": 308,
+ "gust": 1.64
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-13 21:00:00"
+ },
+ {
+ "dt": 1705190400,
+ "main": {
+ "temp": 20.24,
+ "feels_like": 19.78,
+ "temp_min": 20.24,
+ "temp_max": 20.24,
+ "pressure": 1012,
+ "sea_level": 1012,
+ "grnd_level": 1008,
+ "humidity": 56,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 16
+ },
+ "wind": {
+ "speed": 1.69,
+ "deg": 0,
+ "gust": 1.85
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-14 00:00:00"
+ },
+ {
+ "dt": 1705201200,
+ "main": {
+ "temp": 26.71,
+ "feels_like": 26.7,
+ "temp_min": 26.71,
+ "temp_max": 26.71,
+ "pressure": 1013,
+ "sea_level": 1013,
+ "grnd_level": 1010,
+ "humidity": 41,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01d"
+ }
+ ],
+ "clouds": {
+ "all": 2
+ },
+ "wind": {
+ "speed": 1.38,
+ "deg": 61,
+ "gust": 1.59
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-14 03:00:00"
+ },
+ {
+ "dt": 1705212000,
+ "main": {
+ "temp": 32.43,
+ "feels_like": 31.19,
+ "temp_min": 32.43,
+ "temp_max": 32.43,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 29,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01d"
+ }
+ ],
+ "clouds": {
+ "all": 2
+ },
+ "wind": {
+ "speed": 1.18,
+ "deg": 173,
+ "gust": 2.01
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-14 06:00:00"
+ },
+ {
+ "dt": 1705222800,
+ "main": {
+ "temp": 33.16,
+ "feels_like": 32.2,
+ "temp_min": 33.16,
+ "temp_max": 33.16,
+ "pressure": 1008,
+ "sea_level": 1008,
+ "grnd_level": 1005,
+ "humidity": 30,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01d"
+ }
+ ],
+ "clouds": {
+ "all": 1
+ },
+ "wind": {
+ "speed": 2.46,
+ "deg": 191,
+ "gust": 2.97
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-14 09:00:00"
+ },
+ {
+ "dt": 1705233600,
+ "main": {
+ "temp": 26.58,
+ "feels_like": 26.58,
+ "temp_min": 26.58,
+ "temp_max": 26.58,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1006,
+ "humidity": 47,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 4
+ },
+ "wind": {
+ "speed": 2.88,
+ "deg": 182,
+ "gust": 4.24
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-14 12:00:00"
+ },
+ {
+ "dt": 1705244400,
+ "main": {
+ "temp": 24.4,
+ "feels_like": 24.33,
+ "temp_min": 24.4,
+ "temp_max": 24.4,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 55,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 19
+ },
+ "wind": {
+ "speed": 2.84,
+ "deg": 226,
+ "gust": 4.3
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-14 15:00:00"
+ },
+ {
+ "dt": 1705255200,
+ "main": {
+ "temp": 22.59,
+ "feels_like": 22.55,
+ "temp_min": 22.59,
+ "temp_max": 22.59,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 63,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 14
+ },
+ "wind": {
+ "speed": 2.73,
+ "deg": 251,
+ "gust": 4.46
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-14 18:00:00"
+ },
+ {
+ "dt": 1705266000,
+ "main": {
+ "temp": 21.26,
+ "feels_like": 21.24,
+ "temp_min": 21.26,
+ "temp_max": 21.26,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 69,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 801,
+ "main": "Clouds",
+ "description": "few clouds",
+ "icon": "02n"
+ }
+ ],
+ "clouds": {
+ "all": 13
+ },
+ "wind": {
+ "speed": 1.48,
+ "deg": 268,
+ "gust": 1.69
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-14 21:00:00"
+ },
+ {
+ "dt": 1705276800,
+ "main": {
+ "temp": 20.87,
+ "feels_like": 20.97,
+ "temp_min": 20.87,
+ "temp_max": 20.87,
+ "pressure": 1011,
+ "sea_level": 1011,
+ "grnd_level": 1008,
+ "humidity": 75,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01n"
+ }
+ ],
+ "clouds": {
+ "all": 7
+ },
+ "wind": {
+ "speed": 1.77,
+ "deg": 294,
+ "gust": 1.92
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-15 00:00:00"
+ },
+ {
+ "dt": 1705287600,
+ "main": {
+ "temp": 25.75,
+ "feels_like": 25.9,
+ "temp_min": 25.75,
+ "temp_max": 25.75,
+ "pressure": 1013,
+ "sea_level": 1013,
+ "grnd_level": 1010,
+ "humidity": 58,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "clouds": {
+ "all": 63
+ },
+ "wind": {
+ "speed": 1.45,
+ "deg": 310,
+ "gust": 1.31
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-15 03:00:00"
+ },
+ {
+ "dt": 1705298400,
+ "main": {
+ "temp": 31.52,
+ "feels_like": 31.45,
+ "temp_min": 31.52,
+ "temp_max": 31.52,
+ "pressure": 1010,
+ "sea_level": 1010,
+ "grnd_level": 1007,
+ "humidity": 39,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 803,
+ "main": "Clouds",
+ "description": "broken clouds",
+ "icon": "04d"
+ }
+ ],
+ "clouds": {
+ "all": 60
+ },
+ "wind": {
+ "speed": 0.72,
+ "deg": 220,
+ "gust": 1.45
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-15 06:00:00"
+ },
+ {
+ "dt": 1705309200,
+ "main": {
+ "temp": 32.94,
+ "feels_like": 32.9,
+ "temp_min": 32.94,
+ "temp_max": 32.94,
+ "pressure": 1007,
+ "sea_level": 1007,
+ "grnd_level": 1004,
+ "humidity": 36,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 800,
+ "main": "Clear",
+ "description": "clear sky",
+ "icon": "01d"
+ }
+ ],
+ "clouds": {
+ "all": 10
+ },
+ "wind": {
+ "speed": 2.38,
+ "deg": 193,
+ "gust": 2.99
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "d"
+ },
+ "dt_txt": "2024-01-15 09:00:00"
+ },
+ {
+ "dt": 1705320000,
+ "main": {
+ "temp": 27.63,
+ "feels_like": 28.21,
+ "temp_min": 27.63,
+ "temp_max": 27.63,
+ "pressure": 1009,
+ "sea_level": 1009,
+ "grnd_level": 1005,
+ "humidity": 52,
+ "temp_kf": 0
+ },
+ "weather": [
+ {
+ "id": 802,
+ "main": "Clouds",
+ "description": "scattered clouds",
+ "icon": "03n"
+ }
+ ],
+ "clouds": {
+ "all": 35
+ },
+ "wind": {
+ "speed": 2.79,
+ "deg": 176,
+ "gust": 4.39
+ },
+ "visibility": 10000,
+ "pop": 0,
+ "sys": {
+ "pod": "n"
+ },
+ "dt_txt": "2024-01-15 12:00:00"
+ }
+ ],
+ "city": {
+ "id": 1298824,
+ "name": "Yangon",
+ "coord": {
+ "lat": 16.8053,
+ "lon": 96.1561
+ },
+ "country": "MM",
+ "population": 4477638,
+ "timezone": 23400,
+ "sunrise": 1704845201,
+ "sunset": 1704885476
+ }
+}
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..0a6cd9b
--- /dev/null
+++ b/index.html
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+ WeatherSeeker
+
+
+
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..788620f
--- /dev/null
+++ b/package.json
@@ -0,0 +1,39 @@
+{
+ "name": "weather-seeker",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite --host",
+ "build": "tsc && vite build",
+ "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "@fortawesome/fontawesome-svg-core": "^6.5.1",
+ "@fortawesome/free-brands-svg-icons": "^6.5.1",
+ "@fortawesome/free-regular-svg-icons": "^6.5.1",
+ "@fortawesome/free-solid-svg-icons": "^6.5.1",
+ "@fortawesome/react-fontawesome": "^0.2.0",
+ "axios": "^1.6.3",
+ "framer-motion": "^10.16.16",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "react-router-dom": "^6.21.1",
+ "react-use-measure": "^2.1.1"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.15",
+ "@types/react-dom": "^18.2.7",
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
+ "@typescript-eslint/parser": "^6.0.0",
+ "@vitejs/plugin-react": "^4.0.3",
+ "eslint": "^8.45.0",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-react-refresh": "^0.4.3",
+ "sass": "^1.69.6",
+ "typescript": "^5.0.2",
+ "vite": "^4.4.5",
+ "vite-plugin-babel-macros": "^1.0.6"
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..8c453c9
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,2229 @@
+lockfileVersion: '6.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+dependencies:
+ '@fortawesome/fontawesome-svg-core':
+ specifier: ^6.5.1
+ version: 6.5.1
+ '@fortawesome/free-brands-svg-icons':
+ specifier: ^6.5.1
+ version: 6.5.1
+ '@fortawesome/free-regular-svg-icons':
+ specifier: ^6.5.1
+ version: 6.5.1
+ '@fortawesome/free-solid-svg-icons':
+ specifier: ^6.5.1
+ version: 6.5.1
+ '@fortawesome/react-fontawesome':
+ specifier: ^0.2.0
+ version: 0.2.0(@fortawesome/fontawesome-svg-core@6.5.1)(react@18.2.0)
+ axios:
+ specifier: ^1.6.3
+ version: 1.6.3
+ framer-motion:
+ specifier: ^10.16.16
+ version: 10.16.16(react-dom@18.2.0)(react@18.2.0)
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+ react-dom:
+ specifier: ^18.2.0
+ version: 18.2.0(react@18.2.0)
+ react-router-dom:
+ specifier: ^6.21.1
+ version: 6.21.1(react-dom@18.2.0)(react@18.2.0)
+ react-use-measure:
+ specifier: ^2.1.1
+ version: 2.1.1(react-dom@18.2.0)(react@18.2.0)
+
+devDependencies:
+ '@types/react':
+ specifier: ^18.2.15
+ version: 18.2.46
+ '@types/react-dom':
+ specifier: ^18.2.7
+ version: 18.2.18
+ '@typescript-eslint/eslint-plugin':
+ specifier: ^6.0.0
+ version: 6.16.0(@typescript-eslint/parser@6.16.0)(eslint@8.56.0)(typescript@5.3.3)
+ '@typescript-eslint/parser':
+ specifier: ^6.0.0
+ version: 6.16.0(eslint@8.56.0)(typescript@5.3.3)
+ '@vitejs/plugin-react':
+ specifier: ^4.0.3
+ version: 4.2.1(vite@4.5.1)
+ eslint:
+ specifier: ^8.45.0
+ version: 8.56.0
+ eslint-plugin-react-hooks:
+ specifier: ^4.6.0
+ version: 4.6.0(eslint@8.56.0)
+ eslint-plugin-react-refresh:
+ specifier: ^0.4.3
+ version: 0.4.5(eslint@8.56.0)
+ sass:
+ specifier: ^1.69.6
+ version: 1.69.6
+ typescript:
+ specifier: ^5.0.2
+ version: 5.3.3
+ vite:
+ specifier: ^4.4.5
+ version: 4.5.1(sass@1.69.6)
+ vite-plugin-babel-macros:
+ specifier: ^1.0.6
+ version: 1.0.6(vite@4.5.1)
+
+packages:
+
+ /@aashutoshrathi/word-wrap@1.2.6:
+ resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /@ampproject/remapping@2.2.1:
+ resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.20
+ dev: true
+
+ /@babel/code-frame@7.23.5:
+ resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/highlight': 7.23.4
+ chalk: 2.4.2
+ dev: true
+
+ /@babel/compat-data@7.23.5:
+ resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/core@7.23.7:
+ resolution: {integrity: sha512-+UpDgowcmqe36d4NwqvKsyPMlOLNGMsfMmQ5WGCu+siCe3t3dfe9njrzGfdN4qq+bcNUt0+Vw6haRxBOycs4dw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@ampproject/remapping': 2.2.1
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-compilation-targets': 7.23.6
+ '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.7)
+ '@babel/helpers': 7.23.7
+ '@babel/parser': 7.23.6
+ '@babel/template': 7.22.15
+ '@babel/traverse': 7.23.7
+ '@babel/types': 7.23.6
+ convert-source-map: 2.0.0
+ debug: 4.3.4
+ gensync: 1.0.0-beta.2
+ json5: 2.2.3
+ semver: 6.3.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/generator@7.23.6:
+ resolution: {integrity: sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.6
+ '@jridgewell/gen-mapping': 0.3.3
+ '@jridgewell/trace-mapping': 0.3.20
+ jsesc: 2.5.2
+ dev: true
+
+ /@babel/helper-compilation-targets@7.23.6:
+ resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/compat-data': 7.23.5
+ '@babel/helper-validator-option': 7.23.5
+ browserslist: 4.22.2
+ lru-cache: 5.1.1
+ semver: 6.3.1
+ dev: true
+
+ /@babel/helper-environment-visitor@7.22.20:
+ resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-function-name@7.23.0:
+ resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.22.15
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/helper-hoist-variables@7.22.5:
+ resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/helper-module-imports@7.22.15:
+ resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.7):
+ resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-module-imports': 7.22.15
+ '@babel/helper-simple-access': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/helper-validator-identifier': 7.22.20
+ dev: true
+
+ /@babel/helper-plugin-utils@7.22.5:
+ resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-simple-access@7.22.5:
+ resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/helper-split-export-declaration@7.22.6:
+ resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/helper-string-parser@7.23.4:
+ resolution: {integrity: sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-identifier@7.22.20:
+ resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helper-validator-option@7.23.5:
+ resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /@babel/helpers@7.23.7:
+ resolution: {integrity: sha512-6AMnjCoC8wjqBzDHkuqpa7jAKwvMo4dC+lr/TFBz+ucfulO1XMpDnwWPGBNwClOKZ8h6xn5N81W/R5OrcKtCbQ==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/template': 7.22.15
+ '@babel/traverse': 7.23.7
+ '@babel/types': 7.23.6
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/highlight@7.23.4:
+ resolution: {integrity: sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-validator-identifier': 7.22.20
+ chalk: 2.4.2
+ js-tokens: 4.0.0
+ dev: true
+
+ /@babel/parser@7.23.6:
+ resolution: {integrity: sha512-Z2uID7YJ7oNvAI20O9X0bblw7Qqs8Q2hFy0R9tAfnfLkp5MW0UH9eUvnDSnFwKZ0AvgS1ucqR4KzvVHgnke1VQ==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.7):
+ resolution: {integrity: sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: true
+
+ /@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.7):
+ resolution: {integrity: sha512-9EiNjVJOMwCO+43TqoTrgQ8jMwcAd0sWyXi9RPfIsLTj4R2MADDDQXELhffaUx/uJv2AYcxBgPwH6j4TIA4ytQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: true
+
+ /@babel/plugin-transform-react-jsx-self@7.23.3(@babel/core@7.23.7):
+ resolution: {integrity: sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: true
+
+ /@babel/plugin-transform-react-jsx-source@7.23.3(@babel/core@7.23.7):
+ resolution: {integrity: sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==}
+ engines: {node: '>=6.9.0'}
+ peerDependencies:
+ '@babel/core': ^7.0.0-0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/helper-plugin-utils': 7.22.5
+ dev: true
+
+ /@babel/runtime@7.23.7:
+ resolution: {integrity: sha512-w06OXVOFso7LcbzMiDGt+3X7Rh7Ho8MmgPoWU3rarH+8upf+wSU/grlGbWzQyr3DkdN6ZeuMFjpdwW0Q+HxobA==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ regenerator-runtime: 0.14.1
+ dev: true
+
+ /@babel/template@7.22.15:
+ resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/parser': 7.23.6
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@babel/traverse@7.23.7:
+ resolution: {integrity: sha512-tY3mM8rH9jM0YHFGyfC0/xf+SB5eKUu7HPj7/k3fpi9dAlsMc5YbQvDi0Sh2QTPXqMhyaAtzAr807TIyfQrmyg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ '@babel/generator': 7.23.6
+ '@babel/helper-environment-visitor': 7.22.20
+ '@babel/helper-function-name': 7.23.0
+ '@babel/helper-hoist-variables': 7.22.5
+ '@babel/helper-split-export-declaration': 7.22.6
+ '@babel/parser': 7.23.6
+ '@babel/types': 7.23.6
+ debug: 4.3.4
+ globals: 11.12.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@babel/types@7.23.6:
+ resolution: {integrity: sha512-+uarb83brBzPKN38NX1MkB6vb6+mwvR6amUulqAE7ccQw1pEl+bCia9TbdG1lsnFP7lZySvUn37CHyXQdfTwzg==}
+ engines: {node: '>=6.9.0'}
+ dependencies:
+ '@babel/helper-string-parser': 7.23.4
+ '@babel/helper-validator-identifier': 7.22.20
+ to-fast-properties: 2.0.0
+ dev: true
+
+ /@emotion/is-prop-valid@0.8.8:
+ resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
+ requiresBuild: true
+ dependencies:
+ '@emotion/memoize': 0.7.4
+ dev: false
+ optional: true
+
+ /@emotion/memoize@0.7.4:
+ resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
+ requiresBuild: true
+ dev: false
+ optional: true
+
+ /@esbuild/android-arm64@0.18.20:
+ resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-arm@0.18.20:
+ resolution: {integrity: sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/android-x64@0.18.20:
+ resolution: {integrity: sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [android]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-arm64@0.18.20:
+ resolution: {integrity: sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/darwin-x64@0.18.20:
+ resolution: {integrity: sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-arm64@0.18.20:
+ resolution: {integrity: sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/freebsd-x64@0.18.20:
+ resolution: {integrity: sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [freebsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm64@0.18.20:
+ resolution: {integrity: sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-arm@0.18.20:
+ resolution: {integrity: sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==}
+ engines: {node: '>=12'}
+ cpu: [arm]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ia32@0.18.20:
+ resolution: {integrity: sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-loong64@0.18.20:
+ resolution: {integrity: sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==}
+ engines: {node: '>=12'}
+ cpu: [loong64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-mips64el@0.18.20:
+ resolution: {integrity: sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==}
+ engines: {node: '>=12'}
+ cpu: [mips64el]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-ppc64@0.18.20:
+ resolution: {integrity: sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==}
+ engines: {node: '>=12'}
+ cpu: [ppc64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-riscv64@0.18.20:
+ resolution: {integrity: sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==}
+ engines: {node: '>=12'}
+ cpu: [riscv64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-s390x@0.18.20:
+ resolution: {integrity: sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==}
+ engines: {node: '>=12'}
+ cpu: [s390x]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/linux-x64@0.18.20:
+ resolution: {integrity: sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [linux]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/netbsd-x64@0.18.20:
+ resolution: {integrity: sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [netbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/openbsd-x64@0.18.20:
+ resolution: {integrity: sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [openbsd]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/sunos-x64@0.18.20:
+ resolution: {integrity: sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [sunos]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-arm64@0.18.20:
+ resolution: {integrity: sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==}
+ engines: {node: '>=12'}
+ cpu: [arm64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-ia32@0.18.20:
+ resolution: {integrity: sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==}
+ engines: {node: '>=12'}
+ cpu: [ia32]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@esbuild/win32-x64@0.18.20:
+ resolution: {integrity: sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==}
+ engines: {node: '>=12'}
+ cpu: [x64]
+ os: [win32]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /@eslint-community/eslint-utils@4.4.0(eslint@8.56.0):
+ resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ peerDependencies:
+ eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
+ dependencies:
+ eslint: 8.56.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@eslint-community/regexpp@4.10.0:
+ resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==}
+ engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
+ dev: true
+
+ /@eslint/eslintrc@2.1.4:
+ resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ ajv: 6.12.6
+ debug: 4.3.4
+ espree: 9.6.1
+ globals: 13.24.0
+ ignore: 5.3.0
+ import-fresh: 3.3.0
+ js-yaml: 4.1.0
+ minimatch: 3.1.2
+ strip-json-comments: 3.1.1
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@eslint/js@8.56.0:
+ resolution: {integrity: sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: true
+
+ /@fortawesome/fontawesome-common-types@6.5.1:
+ resolution: {integrity: sha512-GkWzv+L6d2bI5f/Vk6ikJ9xtl7dfXtoRu3YGE6nq0p/FFqA1ebMOAWg3XgRyb0I6LYyYkiAo+3/KrwuBp8xG7A==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dev: false
+
+ /@fortawesome/fontawesome-svg-core@6.5.1:
+ resolution: {integrity: sha512-MfRCYlQPXoLlpem+egxjfkEuP9UQswTrlCOsknus/NcMoblTH2g0jPrapbcIb04KGA7E2GZxbAccGZfWoYgsrQ==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.5.1
+ dev: false
+
+ /@fortawesome/free-brands-svg-icons@6.5.1:
+ resolution: {integrity: sha512-093l7DAkx0aEtBq66Sf19MgoZewv1zeY9/4C7vSKPO4qMwEsW/2VYTUTpBtLwfb9T2R73tXaRDPmE4UqLCYHfg==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.5.1
+ dev: false
+
+ /@fortawesome/free-regular-svg-icons@6.5.1:
+ resolution: {integrity: sha512-m6ShXn+wvqEU69wSP84coxLbNl7sGVZb+Ca+XZq6k30SzuP3X4TfPqtycgUh9ASwlNh5OfQCd8pDIWxl+O+LlQ==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.5.1
+ dev: false
+
+ /@fortawesome/free-solid-svg-icons@6.5.1:
+ resolution: {integrity: sha512-S1PPfU3mIJa59biTtXJz1oI0+KAXW6bkAb31XKhxdxtuXDiUIFsih4JR1v5BbxY7hVHsD1RKq+jRkVRaf773NQ==}
+ engines: {node: '>=6'}
+ requiresBuild: true
+ dependencies:
+ '@fortawesome/fontawesome-common-types': 6.5.1
+ dev: false
+
+ /@fortawesome/react-fontawesome@0.2.0(@fortawesome/fontawesome-svg-core@6.5.1)(react@18.2.0):
+ resolution: {integrity: sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==}
+ peerDependencies:
+ '@fortawesome/fontawesome-svg-core': ~1 || ~6
+ react: '>=16.3'
+ dependencies:
+ '@fortawesome/fontawesome-svg-core': 6.5.1
+ prop-types: 15.8.1
+ react: 18.2.0
+ dev: false
+
+ /@humanwhocodes/config-array@0.11.13:
+ resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==}
+ engines: {node: '>=10.10.0'}
+ dependencies:
+ '@humanwhocodes/object-schema': 2.0.1
+ debug: 4.3.4
+ minimatch: 3.1.2
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@humanwhocodes/module-importer@1.0.1:
+ resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
+ engines: {node: '>=12.22'}
+ dev: true
+
+ /@humanwhocodes/object-schema@2.0.1:
+ resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==}
+ dev: true
+
+ /@jridgewell/gen-mapping@0.3.3:
+ resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ '@jridgewell/set-array': 1.1.2
+ '@jridgewell/sourcemap-codec': 1.4.15
+ '@jridgewell/trace-mapping': 0.3.20
+ dev: true
+
+ /@jridgewell/resolve-uri@3.1.1:
+ resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/set-array@1.1.2:
+ resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==}
+ engines: {node: '>=6.0.0'}
+ dev: true
+
+ /@jridgewell/sourcemap-codec@1.4.15:
+ resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==}
+ dev: true
+
+ /@jridgewell/trace-mapping@0.3.20:
+ resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==}
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.1
+ '@jridgewell/sourcemap-codec': 1.4.15
+ dev: true
+
+ /@nodelib/fs.scandir@2.1.5:
+ resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ run-parallel: 1.2.0
+ dev: true
+
+ /@nodelib/fs.stat@2.0.5:
+ resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /@nodelib/fs.walk@1.2.8:
+ resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
+ engines: {node: '>= 8'}
+ dependencies:
+ '@nodelib/fs.scandir': 2.1.5
+ fastq: 1.16.0
+ dev: true
+
+ /@remix-run/router@1.14.1:
+ resolution: {integrity: sha512-Qg4DMQsfPNAs88rb2xkdk03N3bjK4jgX5fR24eHCTR9q6PrhZQZ4UJBPzCHJkIpTRN1UKxx2DzjZmnC+7Lj0Ow==}
+ engines: {node: '>=14.0.0'}
+ dev: false
+
+ /@types/babel__core@7.20.5:
+ resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
+ dependencies:
+ '@babel/parser': 7.23.6
+ '@babel/types': 7.23.6
+ '@types/babel__generator': 7.6.8
+ '@types/babel__template': 7.4.4
+ '@types/babel__traverse': 7.20.5
+ dev: true
+
+ /@types/babel__generator@7.6.8:
+ resolution: {integrity: sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@types/babel__template@7.4.4:
+ resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==}
+ dependencies:
+ '@babel/parser': 7.23.6
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@types/babel__traverse@7.20.5:
+ resolution: {integrity: sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==}
+ dependencies:
+ '@babel/types': 7.23.6
+ dev: true
+
+ /@types/json-schema@7.0.15:
+ resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
+ dev: true
+
+ /@types/parse-json@4.0.2:
+ resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==}
+ dev: true
+
+ /@types/prop-types@15.7.11:
+ resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==}
+ dev: true
+
+ /@types/react-dom@18.2.18:
+ resolution: {integrity: sha512-TJxDm6OfAX2KJWJdMEVTwWke5Sc/E/RlnPGvGfS0W7+6ocy2xhDVQVh/KvC2Uf7kACs+gDytdusDSdWfWkaNzw==}
+ dependencies:
+ '@types/react': 18.2.46
+ dev: true
+
+ /@types/react@18.2.46:
+ resolution: {integrity: sha512-nNCvVBcZlvX4NU1nRRNV/mFl1nNRuTuslAJglQsq+8ldXe5Xv0Wd2f7WTE3jOxhLH2BFfiZGC6GCp+kHQbgG+w==}
+ dependencies:
+ '@types/prop-types': 15.7.11
+ '@types/scheduler': 0.16.8
+ csstype: 3.1.3
+ dev: true
+
+ /@types/scheduler@0.16.8:
+ resolution: {integrity: sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==}
+ dev: true
+
+ /@types/semver@7.5.6:
+ resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==}
+ dev: true
+
+ /@typescript-eslint/eslint-plugin@6.16.0(@typescript-eslint/parser@6.16.0)(eslint@8.56.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-O5f7Kv5o4dLWQtPX4ywPPa+v9G+1q1x8mz0Kr0pXUtKsevo+gIJHLkGc8RxaZWtP8RrhwhSNIWThnW42K9/0rQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@eslint-community/regexpp': 4.10.0
+ '@typescript-eslint/parser': 6.16.0(eslint@8.56.0)(typescript@5.3.3)
+ '@typescript-eslint/scope-manager': 6.16.0
+ '@typescript-eslint/type-utils': 6.16.0(eslint@8.56.0)(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.16.0(eslint@8.56.0)(typescript@5.3.3)
+ '@typescript-eslint/visitor-keys': 6.16.0
+ debug: 4.3.4
+ eslint: 8.56.0
+ graphemer: 1.4.0
+ ignore: 5.3.0
+ natural-compare: 1.4.0
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/parser@6.16.0(eslint@8.56.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-H2GM3eUo12HpKZU9njig3DF5zJ58ja6ahj1GoHEHOgQvYxzoFJJEvC1MQ7T2l9Ha+69ZSOn7RTxOdpC/y3ikMw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/scope-manager': 6.16.0
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/typescript-estree': 6.16.0(typescript@5.3.3)
+ '@typescript-eslint/visitor-keys': 6.16.0
+ debug: 4.3.4
+ eslint: 8.56.0
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/scope-manager@6.16.0:
+ resolution: {integrity: sha512-0N7Y9DSPdaBQ3sqSCwlrm9zJwkpOuc6HYm7LpzLAPqBL7dmzAUimr4M29dMkOP/tEwvOCC/Cxo//yOfJD3HUiw==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/visitor-keys': 6.16.0
+ dev: true
+
+ /@typescript-eslint/type-utils@6.16.0(eslint@8.56.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-ThmrEOcARmOnoyQfYkHw/DX2SEYBalVECmoldVuH6qagKROp/jMnfXpAU/pAIWub9c4YTxga+XwgAkoA0pxfmg==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/typescript-estree': 6.16.0(typescript@5.3.3)
+ '@typescript-eslint/utils': 6.16.0(eslint@8.56.0)(typescript@5.3.3)
+ debug: 4.3.4
+ eslint: 8.56.0
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/types@6.16.0:
+ resolution: {integrity: sha512-hvDFpLEvTJoHutVl87+MG/c5C8I6LOgEx05zExTSJDEVU7hhR3jhV8M5zuggbdFCw98+HhZWPHZeKS97kS3JoQ==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dev: true
+
+ /@typescript-eslint/typescript-estree@6.16.0(typescript@5.3.3):
+ resolution: {integrity: sha512-VTWZuixh/vr7nih6CfrdpmFNLEnoVBF1skfjdyGnNwXOH1SLeHItGdZDHhhAIzd3ACazyY2Fg76zuzOVTaknGA==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ typescript: '*'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/visitor-keys': 6.16.0
+ debug: 4.3.4
+ globby: 11.1.0
+ is-glob: 4.0.3
+ minimatch: 9.0.3
+ semver: 7.5.4
+ ts-api-utils: 1.0.3(typescript@5.3.3)
+ typescript: 5.3.3
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /@typescript-eslint/utils@6.16.0(eslint@8.56.0)(typescript@5.3.3):
+ resolution: {integrity: sha512-T83QPKrBm6n//q9mv7oiSvy/Xq/7Hyw9SzSEhMHJwznEmQayfBM87+oAlkNAMEO7/MjIwKyOHgBJbxB0s7gx2A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ peerDependencies:
+ eslint: ^7.0.0 || ^8.0.0
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+ '@types/json-schema': 7.0.15
+ '@types/semver': 7.5.6
+ '@typescript-eslint/scope-manager': 6.16.0
+ '@typescript-eslint/types': 6.16.0
+ '@typescript-eslint/typescript-estree': 6.16.0(typescript@5.3.3)
+ eslint: 8.56.0
+ semver: 7.5.4
+ transitivePeerDependencies:
+ - supports-color
+ - typescript
+ dev: true
+
+ /@typescript-eslint/visitor-keys@6.16.0:
+ resolution: {integrity: sha512-QSFQLruk7fhs91a/Ep/LqRdbJCZ1Rq03rqBdKT5Ky17Sz8zRLUksqIe9DW0pKtg/Z35/ztbLQ6qpOCN6rOC11A==}
+ engines: {node: ^16.0.0 || >=18.0.0}
+ dependencies:
+ '@typescript-eslint/types': 6.16.0
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /@ungap/structured-clone@1.2.0:
+ resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==}
+ dev: true
+
+ /@vitejs/plugin-react@4.2.1(vite@4.5.1):
+ resolution: {integrity: sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ peerDependencies:
+ vite: ^4.2.0 || ^5.0.0
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/plugin-transform-react-jsx-self': 7.23.3(@babel/core@7.23.7)
+ '@babel/plugin-transform-react-jsx-source': 7.23.3(@babel/core@7.23.7)
+ '@types/babel__core': 7.20.5
+ react-refresh: 0.14.0
+ vite: 4.5.1(sass@1.69.6)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /acorn-jsx@5.3.2(acorn@8.11.3):
+ resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
+ peerDependencies:
+ acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
+ dependencies:
+ acorn: 8.11.3
+ dev: true
+
+ /acorn@8.11.3:
+ resolution: {integrity: sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==}
+ engines: {node: '>=0.4.0'}
+ hasBin: true
+ dev: true
+
+ /ajv@6.12.6:
+ resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-json-stable-stringify: 2.1.0
+ json-schema-traverse: 0.4.1
+ uri-js: 4.4.1
+ dev: true
+
+ /ansi-regex@5.0.1:
+ resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /ansi-styles@3.2.1:
+ resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
+ engines: {node: '>=4'}
+ dependencies:
+ color-convert: 1.9.3
+ dev: true
+
+ /ansi-styles@4.3.0:
+ resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
+ engines: {node: '>=8'}
+ dependencies:
+ color-convert: 2.0.1
+ dev: true
+
+ /anymatch@3.1.3:
+ resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
+ engines: {node: '>= 8'}
+ dependencies:
+ normalize-path: 3.0.0
+ picomatch: 2.3.1
+ dev: true
+
+ /argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+ dev: true
+
+ /array-union@2.1.0:
+ resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /asynckit@0.4.0:
+ resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
+ dev: false
+
+ /axios@1.6.3:
+ resolution: {integrity: sha512-fWyNdeawGam70jXSVlKl+SUNVcL6j6W79CuSIPfi6HnDUmSCH6gyUys/HrqHeA/wU0Az41rRgean494d0Jb+ww==}
+ dependencies:
+ follow-redirects: 1.15.4
+ form-data: 4.0.0
+ proxy-from-env: 1.1.0
+ transitivePeerDependencies:
+ - debug
+ dev: false
+
+ /babel-plugin-macros@3.1.0:
+ resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==}
+ engines: {node: '>=10', npm: '>=6'}
+ dependencies:
+ '@babel/runtime': 7.23.7
+ cosmiconfig: 7.1.0
+ resolve: 1.22.8
+ dev: true
+
+ /balanced-match@1.0.2:
+ resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
+ dev: true
+
+ /binary-extensions@2.2.0:
+ resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /brace-expansion@1.1.11:
+ resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
+ dependencies:
+ balanced-match: 1.0.2
+ concat-map: 0.0.1
+ dev: true
+
+ /brace-expansion@2.0.1:
+ resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
+ dependencies:
+ balanced-match: 1.0.2
+ dev: true
+
+ /braces@3.0.2:
+ resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==}
+ engines: {node: '>=8'}
+ dependencies:
+ fill-range: 7.0.1
+ dev: true
+
+ /browserslist@4.22.2:
+ resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==}
+ engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
+ hasBin: true
+ dependencies:
+ caniuse-lite: 1.0.30001572
+ electron-to-chromium: 1.4.616
+ node-releases: 2.0.14
+ update-browserslist-db: 1.0.13(browserslist@4.22.2)
+ dev: true
+
+ /callsites@3.1.0:
+ resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /caniuse-lite@1.0.30001572:
+ resolution: {integrity: sha512-1Pbh5FLmn5y4+QhNyJE9j3/7dK44dGB83/ZMjv/qJk86TvDbjk0LosiZo0i0WB0Vx607qMX9jYrn1VLHCkN4rw==}
+ dev: true
+
+ /chalk@2.4.2:
+ resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==}
+ engines: {node: '>=4'}
+ dependencies:
+ ansi-styles: 3.2.1
+ escape-string-regexp: 1.0.5
+ supports-color: 5.5.0
+ dev: true
+
+ /chalk@4.1.2:
+ resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
+ engines: {node: '>=10'}
+ dependencies:
+ ansi-styles: 4.3.0
+ supports-color: 7.2.0
+ dev: true
+
+ /chokidar@3.5.3:
+ resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==}
+ engines: {node: '>= 8.10.0'}
+ dependencies:
+ anymatch: 3.1.3
+ braces: 3.0.2
+ glob-parent: 5.1.2
+ is-binary-path: 2.1.0
+ is-glob: 4.0.3
+ normalize-path: 3.0.0
+ readdirp: 3.6.0
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /color-convert@1.9.3:
+ resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==}
+ dependencies:
+ color-name: 1.1.3
+ dev: true
+
+ /color-convert@2.0.1:
+ resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
+ engines: {node: '>=7.0.0'}
+ dependencies:
+ color-name: 1.1.4
+ dev: true
+
+ /color-name@1.1.3:
+ resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==}
+ dev: true
+
+ /color-name@1.1.4:
+ resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
+ dev: true
+
+ /combined-stream@1.0.8:
+ resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
+ engines: {node: '>= 0.8'}
+ dependencies:
+ delayed-stream: 1.0.0
+ dev: false
+
+ /concat-map@0.0.1:
+ resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ dev: true
+
+ /convert-source-map@2.0.0:
+ resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
+ dev: true
+
+ /cosmiconfig@7.1.0:
+ resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==}
+ engines: {node: '>=10'}
+ dependencies:
+ '@types/parse-json': 4.0.2
+ import-fresh: 3.3.0
+ parse-json: 5.2.0
+ path-type: 4.0.0
+ yaml: 1.10.2
+ dev: true
+
+ /cross-spawn@7.0.3:
+ resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
+ engines: {node: '>= 8'}
+ dependencies:
+ path-key: 3.1.1
+ shebang-command: 2.0.0
+ which: 2.0.2
+ dev: true
+
+ /csstype@3.1.3:
+ resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
+ dev: true
+
+ /debounce@1.2.1:
+ resolution: {integrity: sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==}
+ dev: false
+
+ /debug@4.3.4:
+ resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+ dependencies:
+ ms: 2.1.2
+ dev: true
+
+ /deep-is@0.1.4:
+ resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
+ dev: true
+
+ /delayed-stream@1.0.0:
+ resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
+ engines: {node: '>=0.4.0'}
+ dev: false
+
+ /dir-glob@3.0.1:
+ resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
+ engines: {node: '>=8'}
+ dependencies:
+ path-type: 4.0.0
+ dev: true
+
+ /doctrine@3.0.0:
+ resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==}
+ engines: {node: '>=6.0.0'}
+ dependencies:
+ esutils: 2.0.3
+ dev: true
+
+ /electron-to-chromium@1.4.616:
+ resolution: {integrity: sha512-1n7zWYh8eS0L9Uy+GskE0lkBUNK83cXTVJI0pU3mGprFsbfSdAc15VTFbo+A+Bq4pwstmL30AVcEU3Fo463lNg==}
+ dev: true
+
+ /error-ex@1.3.2:
+ resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
+ dependencies:
+ is-arrayish: 0.2.1
+ dev: true
+
+ /esbuild@0.18.20:
+ resolution: {integrity: sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==}
+ engines: {node: '>=12'}
+ hasBin: true
+ requiresBuild: true
+ optionalDependencies:
+ '@esbuild/android-arm': 0.18.20
+ '@esbuild/android-arm64': 0.18.20
+ '@esbuild/android-x64': 0.18.20
+ '@esbuild/darwin-arm64': 0.18.20
+ '@esbuild/darwin-x64': 0.18.20
+ '@esbuild/freebsd-arm64': 0.18.20
+ '@esbuild/freebsd-x64': 0.18.20
+ '@esbuild/linux-arm': 0.18.20
+ '@esbuild/linux-arm64': 0.18.20
+ '@esbuild/linux-ia32': 0.18.20
+ '@esbuild/linux-loong64': 0.18.20
+ '@esbuild/linux-mips64el': 0.18.20
+ '@esbuild/linux-ppc64': 0.18.20
+ '@esbuild/linux-riscv64': 0.18.20
+ '@esbuild/linux-s390x': 0.18.20
+ '@esbuild/linux-x64': 0.18.20
+ '@esbuild/netbsd-x64': 0.18.20
+ '@esbuild/openbsd-x64': 0.18.20
+ '@esbuild/sunos-x64': 0.18.20
+ '@esbuild/win32-arm64': 0.18.20
+ '@esbuild/win32-ia32': 0.18.20
+ '@esbuild/win32-x64': 0.18.20
+ dev: true
+
+ /escalade@3.1.1:
+ resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /escape-string-regexp@1.0.5:
+ resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==}
+ engines: {node: '>=0.8.0'}
+ dev: true
+
+ /escape-string-regexp@4.0.0:
+ resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /eslint-plugin-react-hooks@4.6.0(eslint@8.56.0):
+ resolution: {integrity: sha512-oFc7Itz9Qxh2x4gNHStv3BqJq54ExXmfC+a1NjAta66IAN87Wu0R/QArgIS9qKzX3dXKPI9H5crl9QchNMY9+g==}
+ engines: {node: '>=10'}
+ peerDependencies:
+ eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0
+ dependencies:
+ eslint: 8.56.0
+ dev: true
+
+ /eslint-plugin-react-refresh@0.4.5(eslint@8.56.0):
+ resolution: {integrity: sha512-D53FYKJa+fDmZMtriODxvhwrO+IOqrxoEo21gMA0sjHdU6dPVH4OhyFip9ypl8HOF5RV5KdTo+rBQLvnY2cO8w==}
+ peerDependencies:
+ eslint: '>=7'
+ dependencies:
+ eslint: 8.56.0
+ dev: true
+
+ /eslint-scope@7.2.2:
+ resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ esrecurse: 4.3.0
+ estraverse: 5.3.0
+ dev: true
+
+ /eslint-visitor-keys@3.4.3:
+ resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dev: true
+
+ /eslint@8.56.0:
+ resolution: {integrity: sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ hasBin: true
+ dependencies:
+ '@eslint-community/eslint-utils': 4.4.0(eslint@8.56.0)
+ '@eslint-community/regexpp': 4.10.0
+ '@eslint/eslintrc': 2.1.4
+ '@eslint/js': 8.56.0
+ '@humanwhocodes/config-array': 0.11.13
+ '@humanwhocodes/module-importer': 1.0.1
+ '@nodelib/fs.walk': 1.2.8
+ '@ungap/structured-clone': 1.2.0
+ ajv: 6.12.6
+ chalk: 4.1.2
+ cross-spawn: 7.0.3
+ debug: 4.3.4
+ doctrine: 3.0.0
+ escape-string-regexp: 4.0.0
+ eslint-scope: 7.2.2
+ eslint-visitor-keys: 3.4.3
+ espree: 9.6.1
+ esquery: 1.5.0
+ esutils: 2.0.3
+ fast-deep-equal: 3.1.3
+ file-entry-cache: 6.0.1
+ find-up: 5.0.0
+ glob-parent: 6.0.2
+ globals: 13.24.0
+ graphemer: 1.4.0
+ ignore: 5.3.0
+ imurmurhash: 0.1.4
+ is-glob: 4.0.3
+ is-path-inside: 3.0.3
+ js-yaml: 4.1.0
+ json-stable-stringify-without-jsonify: 1.0.1
+ levn: 0.4.1
+ lodash.merge: 4.6.2
+ minimatch: 3.1.2
+ natural-compare: 1.4.0
+ optionator: 0.9.3
+ strip-ansi: 6.0.1
+ text-table: 0.2.0
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /espree@9.6.1:
+ resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==}
+ engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
+ dependencies:
+ acorn: 8.11.3
+ acorn-jsx: 5.3.2(acorn@8.11.3)
+ eslint-visitor-keys: 3.4.3
+ dev: true
+
+ /esquery@1.5.0:
+ resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==}
+ engines: {node: '>=0.10'}
+ dependencies:
+ estraverse: 5.3.0
+ dev: true
+
+ /esrecurse@4.3.0:
+ resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
+ engines: {node: '>=4.0'}
+ dependencies:
+ estraverse: 5.3.0
+ dev: true
+
+ /estraverse@5.3.0:
+ resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
+ engines: {node: '>=4.0'}
+ dev: true
+
+ /esutils@2.0.3:
+ resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+ dev: true
+
+ /fast-glob@3.3.2:
+ resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
+ engines: {node: '>=8.6.0'}
+ dependencies:
+ '@nodelib/fs.stat': 2.0.5
+ '@nodelib/fs.walk': 1.2.8
+ glob-parent: 5.1.2
+ merge2: 1.4.1
+ micromatch: 4.0.5
+ dev: true
+
+ /fast-json-stable-stringify@2.1.0:
+ resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
+ dev: true
+
+ /fast-levenshtein@2.0.6:
+ resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ dev: true
+
+ /fastq@1.16.0:
+ resolution: {integrity: sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==}
+ dependencies:
+ reusify: 1.0.4
+ dev: true
+
+ /file-entry-cache@6.0.1:
+ resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flat-cache: 3.2.0
+ dev: true
+
+ /fill-range@7.0.1:
+ resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ to-regex-range: 5.0.1
+ dev: true
+
+ /find-up@5.0.0:
+ resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
+ engines: {node: '>=10'}
+ dependencies:
+ locate-path: 6.0.0
+ path-exists: 4.0.0
+ dev: true
+
+ /flat-cache@3.2.0:
+ resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
+ engines: {node: ^10.12.0 || >=12.0.0}
+ dependencies:
+ flatted: 3.2.9
+ keyv: 4.5.4
+ rimraf: 3.0.2
+ dev: true
+
+ /flatted@3.2.9:
+ resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
+ dev: true
+
+ /follow-redirects@1.15.4:
+ resolution: {integrity: sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==}
+ engines: {node: '>=4.0'}
+ peerDependencies:
+ debug: '*'
+ peerDependenciesMeta:
+ debug:
+ optional: true
+ dev: false
+
+ /form-data@4.0.0:
+ resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==}
+ engines: {node: '>= 6'}
+ dependencies:
+ asynckit: 0.4.0
+ combined-stream: 1.0.8
+ mime-types: 2.1.35
+ dev: false
+
+ /framer-motion@10.16.16(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==}
+ peerDependencies:
+ react: ^18.0.0
+ react-dom: ^18.0.0
+ peerDependenciesMeta:
+ react:
+ optional: true
+ react-dom:
+ optional: true
+ dependencies:
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ tslib: 2.6.2
+ optionalDependencies:
+ '@emotion/is-prop-valid': 0.8.8
+ dev: false
+
+ /fs.realpath@1.0.0:
+ resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
+ dev: true
+
+ /fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+ requiresBuild: true
+ dev: true
+ optional: true
+
+ /function-bind@1.1.2:
+ resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
+ dev: true
+
+ /gensync@1.0.0-beta.2:
+ resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
+ engines: {node: '>=6.9.0'}
+ dev: true
+
+ /glob-parent@5.1.2:
+ resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
+ engines: {node: '>= 6'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob-parent@6.0.2:
+ resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
+ engines: {node: '>=10.13.0'}
+ dependencies:
+ is-glob: 4.0.3
+ dev: true
+
+ /glob@7.2.3:
+ resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
+ dependencies:
+ fs.realpath: 1.0.0
+ inflight: 1.0.6
+ inherits: 2.0.4
+ minimatch: 3.1.2
+ once: 1.4.0
+ path-is-absolute: 1.0.1
+ dev: true
+
+ /globals@11.12.0:
+ resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /globals@13.24.0:
+ resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
+ engines: {node: '>=8'}
+ dependencies:
+ type-fest: 0.20.2
+ dev: true
+
+ /globby@11.1.0:
+ resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
+ engines: {node: '>=10'}
+ dependencies:
+ array-union: 2.1.0
+ dir-glob: 3.0.1
+ fast-glob: 3.3.2
+ ignore: 5.3.0
+ merge2: 1.4.1
+ slash: 3.0.0
+ dev: true
+
+ /graphemer@1.4.0:
+ resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
+ dev: true
+
+ /has-flag@3.0.0:
+ resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /has-flag@4.0.0:
+ resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /hasown@2.0.0:
+ resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==}
+ engines: {node: '>= 0.4'}
+ dependencies:
+ function-bind: 1.1.2
+ dev: true
+
+ /ignore@5.3.0:
+ resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==}
+ engines: {node: '>= 4'}
+ dev: true
+
+ /immutable@4.3.4:
+ resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
+ dev: true
+
+ /import-fresh@3.3.0:
+ resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
+ engines: {node: '>=6'}
+ dependencies:
+ parent-module: 1.0.1
+ resolve-from: 4.0.0
+ dev: true
+
+ /imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+ dev: true
+
+ /inflight@1.0.6:
+ resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
+ dependencies:
+ once: 1.4.0
+ wrappy: 1.0.2
+ dev: true
+
+ /inherits@2.0.4:
+ resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
+ dev: true
+
+ /is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+ dev: true
+
+ /is-binary-path@2.1.0:
+ resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==}
+ engines: {node: '>=8'}
+ dependencies:
+ binary-extensions: 2.2.0
+ dev: true
+
+ /is-core-module@2.13.1:
+ resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
+ dependencies:
+ hasown: 2.0.0
+ dev: true
+
+ /is-extglob@2.1.1:
+ resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /is-glob@4.0.3:
+ resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ is-extglob: 2.1.1
+ dev: true
+
+ /is-number@7.0.0:
+ resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
+ engines: {node: '>=0.12.0'}
+ dev: true
+
+ /is-path-inside@3.0.3:
+ resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /isexe@2.0.0:
+ resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
+ dev: true
+
+ /js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ /js-yaml@4.1.0:
+ resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
+ hasBin: true
+ dependencies:
+ argparse: 2.0.1
+ dev: true
+
+ /jsesc@2.5.2:
+ resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
+ engines: {node: '>=4'}
+ hasBin: true
+ dev: true
+
+ /json-buffer@3.0.1:
+ resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
+ dev: true
+
+ /json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+ dev: true
+
+ /json-schema-traverse@0.4.1:
+ resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ dev: true
+
+ /json-stable-stringify-without-jsonify@1.0.1:
+ resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ dev: true
+
+ /json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+ dev: true
+
+ /keyv@4.5.4:
+ resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ dependencies:
+ json-buffer: 3.0.1
+ dev: true
+
+ /levn@0.4.1:
+ resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ dev: true
+
+ /lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+ dev: true
+
+ /locate-path@6.0.0:
+ resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-locate: 5.0.0
+ dev: true
+
+ /lodash.merge@4.6.2:
+ resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
+ dev: true
+
+ /loose-envify@1.4.0:
+ resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
+ hasBin: true
+ dependencies:
+ js-tokens: 4.0.0
+ dev: false
+
+ /lru-cache@5.1.1:
+ resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
+ dependencies:
+ yallist: 3.1.1
+ dev: true
+
+ /lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
+ dependencies:
+ yallist: 4.0.0
+ dev: true
+
+ /merge2@1.4.1:
+ resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
+ engines: {node: '>= 8'}
+ dev: true
+
+ /micromatch@4.0.5:
+ resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==}
+ engines: {node: '>=8.6'}
+ dependencies:
+ braces: 3.0.2
+ picomatch: 2.3.1
+ dev: true
+
+ /mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+ dev: false
+
+ /mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+ dependencies:
+ mime-db: 1.52.0
+ dev: false
+
+ /minimatch@3.1.2:
+ resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
+ dependencies:
+ brace-expansion: 1.1.11
+ dev: true
+
+ /minimatch@9.0.3:
+ resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
+ engines: {node: '>=16 || 14 >=14.17'}
+ dependencies:
+ brace-expansion: 2.0.1
+ dev: true
+
+ /ms@2.1.2:
+ resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
+ dev: true
+
+ /nanoid@3.3.7:
+ resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
+ engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
+ hasBin: true
+ dev: true
+
+ /natural-compare@1.4.0:
+ resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ dev: true
+
+ /node-releases@2.0.14:
+ resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
+ dev: true
+
+ /normalize-path@3.0.0:
+ resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /object-assign@4.1.1:
+ resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
+ engines: {node: '>=0.10.0'}
+ dev: false
+
+ /once@1.4.0:
+ resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
+ dependencies:
+ wrappy: 1.0.2
+ dev: true
+
+ /optionator@0.9.3:
+ resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ '@aashutoshrathi/word-wrap': 1.2.6
+ deep-is: 0.1.4
+ fast-levenshtein: 2.0.6
+ levn: 0.4.1
+ prelude-ls: 1.2.1
+ type-check: 0.4.0
+ dev: true
+
+ /p-limit@3.1.0:
+ resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
+ engines: {node: '>=10'}
+ dependencies:
+ yocto-queue: 0.1.0
+ dev: true
+
+ /p-locate@5.0.0:
+ resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
+ engines: {node: '>=10'}
+ dependencies:
+ p-limit: 3.1.0
+ dev: true
+
+ /parent-module@1.0.1:
+ resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
+ engines: {node: '>=6'}
+ dependencies:
+ callsites: 3.1.0
+ dev: true
+
+ /parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+ dependencies:
+ '@babel/code-frame': 7.23.5
+ error-ex: 1.3.2
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+ dev: true
+
+ /path-exists@4.0.0:
+ resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /path-is-absolute@1.0.1:
+ resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /path-key@3.1.1:
+ resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /path-parse@1.0.7:
+ resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
+ dev: true
+
+ /path-type@4.0.0:
+ resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /picocolors@1.0.0:
+ resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
+ dev: true
+
+ /picomatch@2.3.1:
+ resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
+ engines: {node: '>=8.6'}
+ dev: true
+
+ /postcss@8.4.32:
+ resolution: {integrity: sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==}
+ engines: {node: ^10 || ^12 || >=14}
+ dependencies:
+ nanoid: 3.3.7
+ picocolors: 1.0.0
+ source-map-js: 1.0.2
+ dev: true
+
+ /prelude-ls@1.2.1:
+ resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
+ engines: {node: '>= 0.8.0'}
+ dev: true
+
+ /prop-types@15.8.1:
+ resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
+ dependencies:
+ loose-envify: 1.4.0
+ object-assign: 4.1.1
+ react-is: 16.13.1
+ dev: false
+
+ /proxy-from-env@1.1.0:
+ resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
+ dev: false
+
+ /punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+ dev: true
+
+ /queue-microtask@1.2.3:
+ resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
+ dev: true
+
+ /react-dom@18.2.0(react@18.2.0):
+ resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==}
+ peerDependencies:
+ react: ^18.2.0
+ dependencies:
+ loose-envify: 1.4.0
+ react: 18.2.0
+ scheduler: 0.23.0
+ dev: false
+
+ /react-is@16.13.1:
+ resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ dev: false
+
+ /react-refresh@0.14.0:
+ resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /react-router-dom@6.21.1(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-QCNrtjtDPwHDO+AO21MJd7yIcr41UetYt5jzaB9Y1UYaPTCnVuJq6S748g1dE11OQlCFIQg+RtAA1SEZIyiBeA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ react-dom: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.14.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ react-router: 6.21.1(react@18.2.0)
+ dev: false
+
+ /react-router@6.21.1(react@18.2.0):
+ resolution: {integrity: sha512-W0l13YlMTm1YrpVIOpjCADJqEUpz1vm+CMo47RuFX4Ftegwm6KOYsL5G3eiE52jnJpKvzm6uB/vTKTPKM8dmkA==}
+ engines: {node: '>=14.0.0'}
+ peerDependencies:
+ react: '>=16.8'
+ dependencies:
+ '@remix-run/router': 1.14.1
+ react: 18.2.0
+ dev: false
+
+ /react-use-measure@2.1.1(react-dom@18.2.0)(react@18.2.0):
+ resolution: {integrity: sha512-nocZhN26cproIiIduswYpV5y5lQpSQS1y/4KuvUCjSKmw7ZWIS/+g3aFnX3WdBkyuGUtTLif3UTqnLLhbDoQig==}
+ peerDependencies:
+ react: '>=16.13'
+ react-dom: '>=16.13'
+ dependencies:
+ debounce: 1.2.1
+ react: 18.2.0
+ react-dom: 18.2.0(react@18.2.0)
+ dev: false
+
+ /react@18.2.0:
+ resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==}
+ engines: {node: '>=0.10.0'}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /readdirp@3.6.0:
+ resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==}
+ engines: {node: '>=8.10.0'}
+ dependencies:
+ picomatch: 2.3.1
+ dev: true
+
+ /regenerator-runtime@0.14.1:
+ resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==}
+ dev: true
+
+ /resolve-from@4.0.0:
+ resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /resolve@1.22.8:
+ resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
+ hasBin: true
+ dependencies:
+ is-core-module: 2.13.1
+ path-parse: 1.0.7
+ supports-preserve-symlinks-flag: 1.0.0
+ dev: true
+
+ /reusify@1.0.4:
+ resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
+ engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
+ dev: true
+
+ /rimraf@3.0.2:
+ resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==}
+ hasBin: true
+ dependencies:
+ glob: 7.2.3
+ dev: true
+
+ /rollup@3.29.4:
+ resolution: {integrity: sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==}
+ engines: {node: '>=14.18.0', npm: '>=8.0.0'}
+ hasBin: true
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /run-parallel@1.2.0:
+ resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
+ dependencies:
+ queue-microtask: 1.2.3
+ dev: true
+
+ /sass@1.69.6:
+ resolution: {integrity: sha512-qbRr3k9JGHWXCvZU77SD2OTwUlC+gNT+61JOLcmLm+XqH4h/5D+p4IIsxvpkB89S9AwJOyb5+rWNpIucaFxSFQ==}
+ engines: {node: '>=14.0.0'}
+ hasBin: true
+ dependencies:
+ chokidar: 3.5.3
+ immutable: 4.3.4
+ source-map-js: 1.0.2
+ dev: true
+
+ /scheduler@0.23.0:
+ resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==}
+ dependencies:
+ loose-envify: 1.4.0
+ dev: false
+
+ /semver@6.3.1:
+ resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
+ hasBin: true
+ dev: true
+
+ /semver@7.5.4:
+ resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==}
+ engines: {node: '>=10'}
+ hasBin: true
+ dependencies:
+ lru-cache: 6.0.0
+ dev: true
+
+ /shebang-command@2.0.0:
+ resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
+ engines: {node: '>=8'}
+ dependencies:
+ shebang-regex: 3.0.0
+ dev: true
+
+ /shebang-regex@3.0.0:
+ resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /slash@3.0.0:
+ resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /source-map-js@1.0.2:
+ resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==}
+ engines: {node: '>=0.10.0'}
+ dev: true
+
+ /strip-ansi@6.0.1:
+ resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
+ engines: {node: '>=8'}
+ dependencies:
+ ansi-regex: 5.0.1
+ dev: true
+
+ /strip-json-comments@3.1.1:
+ resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
+ engines: {node: '>=8'}
+ dev: true
+
+ /supports-color@5.5.0:
+ resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
+ engines: {node: '>=4'}
+ dependencies:
+ has-flag: 3.0.0
+ dev: true
+
+ /supports-color@7.2.0:
+ resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
+ engines: {node: '>=8'}
+ dependencies:
+ has-flag: 4.0.0
+ dev: true
+
+ /supports-preserve-symlinks-flag@1.0.0:
+ resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
+ engines: {node: '>= 0.4'}
+ dev: true
+
+ /text-table@0.2.0:
+ resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+ dev: true
+
+ /to-fast-properties@2.0.0:
+ resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==}
+ engines: {node: '>=4'}
+ dev: true
+
+ /to-regex-range@5.0.1:
+ resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
+ engines: {node: '>=8.0'}
+ dependencies:
+ is-number: 7.0.0
+ dev: true
+
+ /ts-api-utils@1.0.3(typescript@5.3.3):
+ resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==}
+ engines: {node: '>=16.13.0'}
+ peerDependencies:
+ typescript: '>=4.2.0'
+ dependencies:
+ typescript: 5.3.3
+ dev: true
+
+ /tslib@2.6.2:
+ resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
+ dev: false
+
+ /type-check@0.4.0:
+ resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
+ engines: {node: '>= 0.8.0'}
+ dependencies:
+ prelude-ls: 1.2.1
+ dev: true
+
+ /type-fest@0.20.2:
+ resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==}
+ engines: {node: '>=10'}
+ dev: true
+
+ /typescript@5.3.3:
+ resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+ dev: true
+
+ /update-browserslist-db@1.0.13(browserslist@4.22.2):
+ resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==}
+ hasBin: true
+ peerDependencies:
+ browserslist: '>= 4.21.0'
+ dependencies:
+ browserslist: 4.22.2
+ escalade: 3.1.1
+ picocolors: 1.0.0
+ dev: true
+
+ /uri-js@4.4.1:
+ resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
+ dependencies:
+ punycode: 2.3.1
+ dev: true
+
+ /vite-plugin-babel-macros@1.0.6(vite@4.5.1):
+ resolution: {integrity: sha512-7cCT8jtu5UjpE46pH7RyVltWw5FbhDAtQliZ6QGqRNR5RUZKdAsB0CDjuF+VBoDpnl0KuESPu22SoNqXRYYWyQ==}
+ engines: {node: '>=16'}
+ peerDependencies:
+ vite: '>=2'
+ dependencies:
+ '@babel/core': 7.23.7
+ '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.7)
+ '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.7)
+ '@types/babel__core': 7.20.5
+ babel-plugin-macros: 3.1.0
+ vite: 4.5.1(sass@1.69.6)
+ transitivePeerDependencies:
+ - supports-color
+ dev: true
+
+ /vite@4.5.1(sass@1.69.6):
+ resolution: {integrity: sha512-AXXFaAJ8yebyqzoNB9fu2pHoo/nWX+xZlaRwoeYUxEqBO+Zj4msE5G+BhGBll9lYEKv9Hfks52PAF2X7qDYXQA==}
+ engines: {node: ^14.18.0 || >=16.0.0}
+ hasBin: true
+ peerDependencies:
+ '@types/node': '>= 14'
+ less: '*'
+ lightningcss: ^1.21.0
+ sass: '*'
+ stylus: '*'
+ sugarss: '*'
+ terser: ^5.4.0
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+ less:
+ optional: true
+ lightningcss:
+ optional: true
+ sass:
+ optional: true
+ stylus:
+ optional: true
+ sugarss:
+ optional: true
+ terser:
+ optional: true
+ dependencies:
+ esbuild: 0.18.20
+ postcss: 8.4.32
+ rollup: 3.29.4
+ sass: 1.69.6
+ optionalDependencies:
+ fsevents: 2.3.3
+ dev: true
+
+ /which@2.0.2:
+ resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
+ engines: {node: '>= 8'}
+ hasBin: true
+ dependencies:
+ isexe: 2.0.0
+ dev: true
+
+ /wrappy@1.0.2:
+ resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ dev: true
+
+ /yallist@3.1.1:
+ resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ dev: true
+
+ /yallist@4.0.0:
+ resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
+ dev: true
+
+ /yaml@1.10.2:
+ resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
+ engines: {node: '>= 6'}
+ dev: true
+
+ /yocto-queue@0.1.0:
+ resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
+ engines: {node: '>=10'}
+ dev: true
diff --git a/public/vite.svg b/public/vite.svg
new file mode 100644
index 0000000..e7b8dfb
--- /dev/null
+++ b/public/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000..a6b8d64
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,36 @@
+import { useEffect } from 'react';
+import { About, ErrorInfo, Header, Particles } from './components';
+import './sass/base.scss';
+import './weather-icons/weather-icons.min.css';
+import { Route, Routes } from 'react-router-dom';
+import { Home, Weather } from './pages';
+import { useAppContext } from './hooks';
+
+function App() {
+ const { dispatch } = useAppContext();
+
+ useEffect(() => {
+ dispatch({ type: 'RESTORE_UNIT' });
+ }, []);
+
+ return (
+ <>
+
+
+
+ }
+ />
+ }
+ />
+
+
+
+ >
+ );
+}
+
+export default App;
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/sass.png b/src/assets/sass.png
new file mode 100644
index 0000000..9713000
Binary files /dev/null and b/src/assets/sass.png differ
diff --git a/src/assets/typescript.svg b/src/assets/typescript.svg
new file mode 100644
index 0000000..b65a93a
--- /dev/null
+++ b/src/assets/typescript.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..e7b8dfb
--- /dev/null
+++ b/src/assets/vite.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/components/About/About.tsx b/src/components/About/About.tsx
new file mode 100644
index 0000000..11eb48a
--- /dev/null
+++ b/src/components/About/About.tsx
@@ -0,0 +1,158 @@
+import reactLogo from '../../assets/react.svg';
+import viteLogo from '../../assets/vite.svg';
+import typescriptLogo from '../../assets/typescript.svg';
+import sassLogo from '../../assets/sass.png';
+import './about.scss';
+import { AnimatePresence, motion } from 'framer-motion';
+import { useState } from 'react';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import {
+ aboutControllerVariants,
+ aboutModalContainerVariants,
+ aboutModalVariants,
+} from './About.variants';
+
+const About = () => {
+ const [showAbout, setShowAbout] = useState(false);
+
+ const handleDismiss = () => setShowAbout(false);
+
+ return (
+ <>
+ setShowAbout(true)}
+ variants={aboutControllerVariants}
+ initial='hidden'
+ animate='visible'
+ >
+
+
+
+ {showAbout && (
+
+ e.stopPropagation()}
+ >
+
+ About
+
+ Developed by{' '}
+
+ Thu Htoo San{' '}
+
+
+
+
+ View source code at{' '}
+
+ GitHub{' '}
+
+
+
+
+
+
+
+
Tech Stack
+
+
+ React{' '}
+
+
+ with
+
+ Vite
+
+ template
+
+
+ TypeScript{' '}
+
+
+
+ Sass
+
+
+
+ Framer Motion
+
+
+
+
+
+ )}
+
+ >
+ );
+};
+
+export default About;
diff --git a/src/components/About/About.variants.ts b/src/components/About/About.variants.ts
new file mode 100644
index 0000000..351faa7
--- /dev/null
+++ b/src/components/About/About.variants.ts
@@ -0,0 +1,43 @@
+import { Variants } from 'framer-motion';
+
+export const aboutControllerVariants: Variants = {
+ hidden: {
+ y: -200,
+ },
+ visible: {
+ y: 0,
+ transition: {
+ type: 'tween',
+ ease: 'easeInOut',
+ delay: 5,
+ duration: 1,
+ },
+ },
+};
+
+export const aboutModalContainerVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 0.3,
+ type: 'tween',
+ ease: 'easeInOut',
+ },
+ },
+};
+
+export const aboutModalVariants: Variants = {
+ hidden: {
+ y: '100vh',
+ },
+ visible: {
+ y: 0,
+ transition: {
+ type: 'spring',
+ velocity: 2,
+ },
+ },
+};
diff --git a/src/components/About/about.scss b/src/components/About/about.scss
new file mode 100644
index 0000000..79e1104
--- /dev/null
+++ b/src/components/About/about.scss
@@ -0,0 +1,83 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.about-controller {
+ position: fixed;
+ top: 0;
+ right: 10px;
+ background: transparentize($white, 0.1);
+ backdrop-filter: $blur;
+ box-shadow: $box-shadow;
+ border-radius: 0 0 50% 50%;
+ width: 35px;
+ height: 35px;
+ cursor: pointer;
+ @include flex-row-center;
+ color: $primary;
+ z-index: 2;
+}
+
+.about-container {
+ @include modal-container();
+ z-index: 99;
+
+ .about {
+ @include modal-center($white, 8px);
+ max-width: 350px;
+ color: $black;
+ padding: 20px !important;
+
+ .title {
+ font-size: 1.2rem;
+ margin-bottom: 10px;
+ }
+
+ .link-opener-blank {
+ font-size: 0.8rem;
+ }
+
+ .dismiss-about {
+ @include modal-dismiss-btn(right, $secondary, $navy);
+ padding: 0;
+ top: 10px;
+ right: 15px;
+ color: $primary;
+ }
+
+ .separator {
+ width: 50px;
+ height: 2px;
+ border-radius: 20px;
+ box-shadow: $box-shadow;
+ background: $dark-grey;
+ margin: 10px auto;
+ }
+
+ .tech-stack {
+ @include flex-col(5px);
+
+ .title {
+ font-size: 1rem;
+ }
+
+ .technologies {
+ @include flex-row-center(10px);
+ flex-wrap: wrap;
+
+ li {
+ @include flex-row(5px);
+ padding: 5px 10px;
+ background: #fff;
+ border-radius: $border-radius;
+ box-shadow: $box-shadow;
+ cursor: grab;
+
+ img {
+ width: 18px;
+ pointer-events: none;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/About/index.tsx b/src/components/About/index.tsx
new file mode 100644
index 0000000..19a87da
--- /dev/null
+++ b/src/components/About/index.tsx
@@ -0,0 +1,3 @@
+import About from './About';
+
+export default About;
diff --git a/src/components/AdvancedWeatherDetails/AdvancedWeatherDetails.tsx b/src/components/AdvancedWeatherDetails/AdvancedWeatherDetails.tsx
new file mode 100644
index 0000000..4525145
--- /dev/null
+++ b/src/components/AdvancedWeatherDetails/AdvancedWeatherDetails.tsx
@@ -0,0 +1,18 @@
+import { motion } from 'framer-motion';
+import { VisibilityDetail, WindDetails } from '..';
+import './advanced-weather-details.scss';
+import { CurrentWeather } from '../../types';
+
+const AdvancedWeatherDetails = ({
+ visibility,
+ wind,
+}: Pick) => {
+ return (
+
+
+
+
+ );
+};
+
+export default AdvancedWeatherDetails;
diff --git a/src/components/AdvancedWeatherDetails/advanced-weather-details.scss b/src/components/AdvancedWeatherDetails/advanced-weather-details.scss
new file mode 100644
index 0000000..954cb72
--- /dev/null
+++ b/src/components/AdvancedWeatherDetails/advanced-weather-details.scss
@@ -0,0 +1,6 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.advanced-details-container {
+ @include flex-col(30px);
+}
diff --git a/src/components/AdvancedWeatherDetails/index.tsx b/src/components/AdvancedWeatherDetails/index.tsx
new file mode 100644
index 0000000..01b316a
--- /dev/null
+++ b/src/components/AdvancedWeatherDetails/index.tsx
@@ -0,0 +1,3 @@
+import AdvancedWeatherDetails from './AdvancedWeatherDetails';
+
+export default AdvancedWeatherDetails;
diff --git a/src/components/BasicWeatherDetails/BasicWeatherDetails.tsx b/src/components/BasicWeatherDetails/BasicWeatherDetails.tsx
new file mode 100644
index 0000000..d2cd071
--- /dev/null
+++ b/src/components/BasicWeatherDetails/BasicWeatherDetails.tsx
@@ -0,0 +1,67 @@
+import { MainDetails, WeatherIcon } from '..';
+import { motion } from 'framer-motion';
+import './basic-weather-details.scss';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { useUnit } from '../../hooks';
+import { cardVariants } from '../../variants';
+import { CurrentWeather } from '../../types';
+
+const BasicWeatherDetails = ({
+ weather,
+ main,
+ sys,
+ name: city,
+}: Pick) => {
+ const { getUnit } = useUnit();
+ const { icon: iconName, description: condition } = weather[0];
+ const { feels_like, humidity, temp } = main;
+
+ return (
+
+
+
+
+
+ {city}, {sys.country}
+
+
+
+
+
+
+ {feels_like} {getUnit('temperature')}
+
+
+
+
+ {temp} {getUnit('temperature')}
+
+
+ {condition.replace(/^.|(?<=\s)./g, m => m.toUpperCase())}
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default BasicWeatherDetails;
diff --git a/src/components/BasicWeatherDetails/basic-weather-details.scss b/src/components/BasicWeatherDetails/basic-weather-details.scss
new file mode 100644
index 0000000..01e756e
--- /dev/null
+++ b/src/components/BasicWeatherDetails/basic-weather-details.scss
@@ -0,0 +1,70 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.basic-details-container {
+ @include flex-col(30px);
+
+ .basic-details {
+ @include card();
+ gap: 10px;
+
+ .location {
+ @include flex-row-center(5px);
+ text-align: center;
+ font-size: 1rem;
+ }
+
+ .weather {
+ @include flex-row(5px);
+ align-items: flex-end;
+ .section-left {
+ @include flex-row();
+ justify-content: flex-start;
+ font-size: 0.9rem;
+ flex: 1;
+
+ .feels-like {
+ @include flex-row(5px);
+ }
+ }
+
+ .section-center {
+ flex: 2.5;
+ text-align: center;
+ @include flex-col-center(10px);
+
+ .location {
+ @include flex-row-center(5px);
+ font-weight: 600;
+ letter-spacing: 0.4px;
+ }
+
+ .condition {
+ font-size: 1.1rem;
+ }
+
+ .icon-wrapper {
+ .icon {
+ width: 100px;
+ font-size: 4.5rem;
+ margin-block: -15px -25px;
+ }
+ }
+
+ .temp {
+ @include flex-row-center(5px);
+ font-size: 1.5rem;
+ font-weight: 700;
+ letter-spacing: 1.1px;
+ }
+ }
+
+ .section-right {
+ flex: 1;
+ @include flex-row();
+ justify-content: flex-end;
+ font-size: 0.9rem;
+ }
+ }
+ }
+}
diff --git a/src/components/BasicWeatherDetails/index.tsx b/src/components/BasicWeatherDetails/index.tsx
new file mode 100644
index 0000000..eff6571
--- /dev/null
+++ b/src/components/BasicWeatherDetails/index.tsx
@@ -0,0 +1,3 @@
+import BasicWeatherDetails from './BasicWeatherDetails';
+
+export default BasicWeatherDetails;
diff --git a/src/components/BeaufortScale/BeaufortScale.tsx b/src/components/BeaufortScale/BeaufortScale.tsx
new file mode 100644
index 0000000..eb967c0
--- /dev/null
+++ b/src/components/BeaufortScale/BeaufortScale.tsx
@@ -0,0 +1,83 @@
+import { motion } from 'framer-motion';
+import './beaufort-scale.scss';
+import { BeaufortScaleProps, Conditions } from './BeaufortScale.types';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { beaufortScaleChildrenVariants } from './BeaufortScale.variants';
+
+const BeaufortScale = ({ detail }: BeaufortScaleProps) => {
+ const conditions: Conditions = {
+ sea: detail.conditions.sea.split('\n'),
+ land: detail.conditions.land.split('\n'),
+ };
+
+ return (
+
+
+
+ Beaufort Scale {detail.number}
+
+
+ - {detail.description} -
+
+
+
+
+ Land Conditions
+
+ {conditions.land.map((condition, index) => (
+
+ {condition}
+
+ ))}
+
+
+
+
+ Sea Conditions
+
+ {conditions.sea.map((condition, index) => (
+
+ {condition}
+
+ ))}
+
+
+ );
+};
+
+export default BeaufortScale;
diff --git a/src/components/BeaufortScale/BeaufortScale.types.ts b/src/components/BeaufortScale/BeaufortScale.types.ts
new file mode 100644
index 0000000..dcb7601
--- /dev/null
+++ b/src/components/BeaufortScale/BeaufortScale.types.ts
@@ -0,0 +1,10 @@
+import { BeaufortScale } from '../../hooks/useBeaufort';
+
+export type BeaufortScaleProps = {
+ detail: BeaufortScale;
+};
+
+export type Conditions = {
+ sea: string[];
+ land: string[];
+};
diff --git a/src/components/BeaufortScale/BeaufortScale.variants.ts b/src/components/BeaufortScale/BeaufortScale.variants.ts
new file mode 100644
index 0000000..1473395
--- /dev/null
+++ b/src/components/BeaufortScale/BeaufortScale.variants.ts
@@ -0,0 +1,17 @@
+import { Variants } from 'framer-motion';
+
+export const beaufortScaleChildrenVariants: Variants = {
+ hidden: {
+ y: 500,
+ opacity: 0,
+ },
+ visible: {
+ y: 0,
+ scale: 1,
+ opacity: 1,
+ transition: {
+ type: 'spring',
+ duration: 1,
+ },
+ },
+};
diff --git a/src/components/BeaufortScale/beaufort-scale.scss b/src/components/BeaufortScale/beaufort-scale.scss
new file mode 100644
index 0000000..8e087c5
--- /dev/null
+++ b/src/components/BeaufortScale/beaufort-scale.scss
@@ -0,0 +1,85 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.beaufort-scale-details {
+ max-width: 300px;
+ padding-top: 20px;
+ @include flex-col(20px);
+
+ .separator {
+ width: 100%;
+ height: 1px;
+ background: transparentize($white, 0.8);
+ border-radius: 12px;
+ }
+
+ .beaufort-scale-title {
+ @include flex-row-center(5px);
+ text-align: center;
+ font-size: 20px;
+
+ .beaufort-number {
+ @include flex-row-center();
+ width: 20px;
+ height: 20px;
+ background: $white;
+ border-radius: 50%;
+ font-size: 0.9rem;
+ color: $primary;
+ }
+ }
+
+ .beaufort-scale-subtitle {
+ font-size: 0.9rem;
+ text-align: center;
+ margin-top: -15px;
+ }
+
+ .land-conditions {
+ @include flex-col(10px);
+
+ .land-conditions-title {
+ font-size: 0.9rem;
+ @include flex-row(5px);
+
+ .icon {
+ font-size: 0.7rem;
+ }
+ }
+
+ .description {
+ border-left: 2px solid $white;
+ padding-left: 10px;
+ font-size: 0.9rem;
+
+ &:not(:last-of-type) {
+ margin-bottom: -10px;
+ padding-bottom: 10px;
+ }
+ }
+ }
+
+ .sea-conditions {
+ @include flex-col(10px);
+
+ .sea-condition-title {
+ font-size: 0.9rem;
+ @include flex-row(5px);
+
+ .icon {
+ font-size: 0.8rem;
+ }
+ }
+
+ .description {
+ border-left: 2px solid $white;
+ padding-left: 10px;
+ font-size: 0.9rem;
+
+ &:not(:last-of-type) {
+ margin-bottom: -10px;
+ padding-bottom: 10px;
+ }
+ }
+ }
+}
diff --git a/src/components/BeaufortScale/index.tsx b/src/components/BeaufortScale/index.tsx
new file mode 100644
index 0000000..72b922d
--- /dev/null
+++ b/src/components/BeaufortScale/index.tsx
@@ -0,0 +1,3 @@
+import BeaufortScale from './BeaufortScale';
+
+export default BeaufortScale;
diff --git a/src/components/ErrorInfo/ErrorInfo.tsx b/src/components/ErrorInfo/ErrorInfo.tsx
new file mode 100644
index 0000000..ec23e59
--- /dev/null
+++ b/src/components/ErrorInfo/ErrorInfo.tsx
@@ -0,0 +1,56 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { useAppContext } from '../../hooks';
+import './error-info.scss';
+import { Action } from '../../state/reducer.types';
+import { AnimatePresence, motion } from 'framer-motion';
+import { errorInfoContainerVariants } from './ErrorInfo.variants';
+
+const ErrorInfo = () => {
+ const { state, dispatch } = useAppContext();
+
+ const dismissErrorAction = (): Action => ({
+ type: 'DISMISS_ERROR',
+ });
+
+ const dismissError = () => dispatch(dismissErrorAction());
+
+ return (
+ <>
+
+ {state.error && (
+
+ e.stopPropagation()}
+ >
+
Oops!
+
+
+ {state.error.replace(/^./, m => m.toUpperCase())}
+
+
+
+
+ )}
+
+ >
+ );
+};
+
+export default ErrorInfo;
diff --git a/src/components/ErrorInfo/ErrorInfo.variants.ts b/src/components/ErrorInfo/ErrorInfo.variants.ts
new file mode 100644
index 0000000..763ebdf
--- /dev/null
+++ b/src/components/ErrorInfo/ErrorInfo.variants.ts
@@ -0,0 +1,10 @@
+import { Variants } from 'framer-motion';
+
+export const errorInfoContainerVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ },
+};
diff --git a/src/components/ErrorInfo/error-info.scss b/src/components/ErrorInfo/error-info.scss
new file mode 100644
index 0000000..0a6227a
--- /dev/null
+++ b/src/components/ErrorInfo/error-info.scss
@@ -0,0 +1,28 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.error-container {
+ @include modal-container();
+ z-index: 99;
+
+ .error-modal {
+ @include modal-center($black, 15px);
+ max-width: 300px;
+ padding: 20px !important;
+
+ .title {
+ font-size: 1.1rem;
+ }
+
+ .icon {
+ font-size: 4rem;
+ }
+
+ .dismiss {
+ color: $black;
+ padding: 5px 8px;
+ font-size: 0.8rem;
+ background: $white;
+ }
+ }
+}
diff --git a/src/components/ErrorInfo/index.tsx b/src/components/ErrorInfo/index.tsx
new file mode 100644
index 0000000..2661be3
--- /dev/null
+++ b/src/components/ErrorInfo/index.tsx
@@ -0,0 +1,3 @@
+import ErrorInfo from './ErrorInfo';
+
+export default ErrorInfo;
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
new file mode 100644
index 0000000..ff96252
--- /dev/null
+++ b/src/components/Header/Header.tsx
@@ -0,0 +1,25 @@
+import { Link } from 'react-router-dom';
+import { SearchBar, Settings, WeatherIcon } from '..';
+import './header.scss';
+
+const Header = () => {
+ return (
+
+
+
+
WeatherSeeker
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Header;
diff --git a/src/components/Header/header.scss b/src/components/Header/header.scss
new file mode 100644
index 0000000..9dde9ca
--- /dev/null
+++ b/src/components/Header/header.scss
@@ -0,0 +1,27 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.header {
+ @include flex-col();
+ align-items: center;
+ margin-bottom: 20px;
+
+ .title-link {
+ text-decoration: none;
+ color: $white;
+
+ .title {
+ @include flex-row(10px);
+ padding: 30px 0;
+ text-decoration: none;
+
+ .icon {
+ font-size: 1.7rem;
+ }
+ }
+ }
+
+ .search-bar-container {
+ @include flex-row-center(10px);
+ }
+}
diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx
new file mode 100644
index 0000000..a9ce105
--- /dev/null
+++ b/src/components/Header/index.tsx
@@ -0,0 +1,3 @@
+import Header from './Header';
+
+export default Header;
diff --git a/src/components/MainDetails/MainDetails.tsx b/src/components/MainDetails/MainDetails.tsx
new file mode 100644
index 0000000..74cd5e9
--- /dev/null
+++ b/src/components/MainDetails/MainDetails.tsx
@@ -0,0 +1,82 @@
+import { motion } from 'framer-motion';
+import './main-details.scss';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { useUnit } from '../../hooks';
+import { cardVariants } from '../../variants';
+import { WeatherIcon } from '..';
+import { CurrentWeather } from '../../types';
+
+const MainDetails = ({
+ temp_min,
+ temp_max,
+ pressure,
+ sea_level,
+ grnd_level: ground_level,
+}: CurrentWeather['main']) => {
+ const { getUnit } = useUnit();
+
+ return (
+
+
+
+ {' '}
+ Maximum Temperature
+
+
+ {temp_max} {getUnit('temperature')}
+
+
+
+
+ {' '}
+ Minimum Temperature
+
+
+ {temp_min} {getUnit('temperature')}
+
+
+ {sea_level && ground_level ? (
+ <>
+ {sea_level && (
+
+
+ Pressure at
+ the sea level
+
+
+ {sea_level} {getUnit('pressure')}
+
+
+ )}
+ {ground_level && (
+
+
+ Pressure
+ at the ground level
+
+
+ {ground_level} {getUnit('pressure')}
+
+
+ )}
+ >
+ ) : (
+
+
+
+ Pressure
+
+
+ {pressure} {getUnit('pressure')}
+
+
+ )}
+
+ );
+};
+
+export default MainDetails;
diff --git a/src/components/MainDetails/index.tsx b/src/components/MainDetails/index.tsx
new file mode 100644
index 0000000..9ee86ec
--- /dev/null
+++ b/src/components/MainDetails/index.tsx
@@ -0,0 +1,3 @@
+import MainDetails from './MainDetails';
+
+export default MainDetails;
diff --git a/src/components/MainDetails/main-details.scss b/src/components/MainDetails/main-details.scss
new file mode 100644
index 0000000..2bafd37
--- /dev/null
+++ b/src/components/MainDetails/main-details.scss
@@ -0,0 +1,23 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.main-details-container {
+ @include card();
+
+ & > div {
+ @include flex-row();
+ position: relative;
+ justify-content: space-between;
+
+ &:not(:last-of-type):after {
+ content: '';
+ position: absolute;
+ bottom: -10px;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 1px;
+ background: transparentize($white, 0.8);
+ }
+ }
+}
diff --git a/src/components/Particles/Particles.tsx b/src/components/Particles/Particles.tsx
new file mode 100644
index 0000000..dcbca27
--- /dev/null
+++ b/src/components/Particles/Particles.tsx
@@ -0,0 +1,55 @@
+import { motion } from 'framer-motion';
+import './particles.scss';
+import { particleVariants } from './Particles.variants';
+import { WeatherIcon } from '..';
+import { useAppContext } from '../../hooks';
+
+const Particles = () => {
+ const { state } = useAppContext();
+ const iconId = state.weather.current?.weather[0].id || 800;
+ const measures = document.body.getBoundingClientRect();
+ const { height, width } = measures;
+ const keys = Array.from(
+ { length: width < 500 ? 20 : 40 },
+ (_, index) => index + 1
+ );
+
+ const getRandomXY = () => {
+ return {
+ x: [
+ Math.floor(Math.random() * width),
+ Math.floor(Math.random() * (width + 200)),
+ ],
+ y: [
+ Math.floor(Math.random() * height),
+ Math.floor(Math.random() * (height + 200)),
+ ],
+ };
+ };
+
+ return (
+
+ {keys.map(key => (
+
+
+
+ ))}
+
+ );
+};
+
+export default Particles;
diff --git a/src/components/Particles/Particles.types.ts b/src/components/Particles/Particles.types.ts
new file mode 100644
index 0000000..fef74b1
--- /dev/null
+++ b/src/components/Particles/Particles.types.ts
@@ -0,0 +1,4 @@
+export type xyObj = {
+ x: number[];
+ y: number[];
+};
diff --git a/src/components/Particles/Particles.variants.tsx b/src/components/Particles/Particles.variants.tsx
new file mode 100644
index 0000000..7227ea1
--- /dev/null
+++ b/src/components/Particles/Particles.variants.tsx
@@ -0,0 +1,29 @@
+import { Variants } from 'framer-motion';
+
+const measures = document.body.getBoundingClientRect();
+export const { height, width } = measures;
+
+const getRandomXY = () => {
+ return {
+ x: [
+ Math.floor(Math.random() * width),
+ Math.floor(Math.random() * (width + 200)),
+ ],
+ y: [
+ Math.floor(Math.random() * height),
+ Math.floor(Math.random() * (height + 200)),
+ ],
+ };
+};
+
+export const particleVariants: Variants = {
+ float: {
+ ...getRandomXY(),
+ transition: {
+ ease: 'easeInOut',
+ duration: 10,
+ repeat: Infinity,
+ repeatType: 'mirror',
+ },
+ },
+};
diff --git a/src/components/Particles/index.tsx b/src/components/Particles/index.tsx
new file mode 100644
index 0000000..425983a
--- /dev/null
+++ b/src/components/Particles/index.tsx
@@ -0,0 +1,3 @@
+import Particles from './Particles';
+
+export default Particles;
diff --git a/src/components/Particles/particles.scss b/src/components/Particles/particles.scss
new file mode 100644
index 0000000..0a9a21e
--- /dev/null
+++ b/src/components/Particles/particles.scss
@@ -0,0 +1,11 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.particle {
+ font-size: 1.5rem;
+ color: $white;
+ position: fixed;
+ top: 0;
+ left: 0;
+ pointer-events: none;
+}
diff --git a/src/components/SearchBar/SearchBar.tsx b/src/components/SearchBar/SearchBar.tsx
new file mode 100644
index 0000000..5771828
--- /dev/null
+++ b/src/components/SearchBar/SearchBar.tsx
@@ -0,0 +1,71 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { useEffect, useRef, useState } from 'react';
+import './search-bar.scss';
+import { useNavigate } from 'react-router-dom';
+
+const SearchBar = () => {
+ const navigate = useNavigate();
+ const [input, setInput] = useState('');
+ const inputRef = useRef(null!);
+
+ const documentWidth = document.body.getBoundingClientRect().width;
+
+ const handleInput = (e: React.ChangeEvent) => {
+ const value = e.target.value.replace(/^.|(?<=\s)\w/g, m => m.toUpperCase());
+ setInput(value);
+ };
+
+ const handleEnter = (e: React.KeyboardEvent) => {
+ if (e.key === 'Enter') {
+ navigate(`/weather?city=${input.trim()}`);
+ setInput('');
+ }
+ };
+
+ const handleShortcut = (e: KeyboardEvent) => {
+ if (e.ctrlKey && e.key === 'k') {
+ e.preventDefault();
+ inputRef.current.focus();
+ } else if (e.key === 'Escape') {
+ e.preventDefault();
+ inputRef.current.blur();
+ }
+ };
+
+ useEffect(() => {
+ document.addEventListener('keydown', handleShortcut);
+
+ return () => {
+ document.removeEventListener('keydown', handleShortcut);
+ };
+ }, []);
+
+ return (
+
+
+
+
+ {documentWidth > 1000 && (
+
+ Ctrl + K
+
+ )}
+
+
+
+ );
+};
+
+export default SearchBar;
diff --git a/src/components/SearchBar/SearchBar.types.ts b/src/components/SearchBar/SearchBar.types.ts
new file mode 100644
index 0000000..d496503
--- /dev/null
+++ b/src/components/SearchBar/SearchBar.types.ts
@@ -0,0 +1,3 @@
+export type SearchBarProps = {
+ city: string;
+};
diff --git a/src/components/SearchBar/index.tsx b/src/components/SearchBar/index.tsx
new file mode 100644
index 0000000..7a508f9
--- /dev/null
+++ b/src/components/SearchBar/index.tsx
@@ -0,0 +1,3 @@
+import SearchBar from './SearchBar';
+
+export default SearchBar;
diff --git a/src/components/SearchBar/search-bar.scss b/src/components/SearchBar/search-bar.scss
new file mode 100644
index 0000000..b6f1fd2
--- /dev/null
+++ b/src/components/SearchBar/search-bar.scss
@@ -0,0 +1,78 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.search-bar {
+ position: relative;
+ width: 260px;
+ height: 40px;
+ z-index: 1;
+ border: 2px solid transparentize($white, 0.1);
+ border-radius: $pill-border;
+ box-shadow: $box-shadow;
+ overflow: hidden;
+ background: transparentize($primary, 0.9);
+ backdrop-filter: $blur;
+
+ .input-container {
+ @include flex-row(10px);
+ padding: 0 15px;
+ width: 100%;
+ height: 100%;
+
+ .icon {
+ font-size: 1.1rem;
+ }
+
+ .search {
+ background: transparent;
+ width: 100%;
+ border: none;
+ color: $white;
+ font-size: 0.95rem;
+
+ &:focus {
+ outline: none;
+ }
+
+ &::placeholder {
+ color: $white;
+ opacity: 0.8;
+ }
+ }
+
+ .shortcut-info {
+ @include flex-row-center(1px);
+ width: 65px;
+ height: 65%;
+ background: $white;
+ border-radius: 6px;
+ box-shadow: $box-shadow;
+ color: $dark-grey;
+ padding: 5px;
+ font-size: 0.6rem;
+ letter-spacing: 0.7px;
+
+ .key {
+ background: transparentize($grey, 0.7);
+ padding: 0 2px;
+ border-radius: 4px;
+ }
+ }
+
+ .ripple-effect {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background: transparentize($navy, 0.9);
+ width: 100%;
+ height: 100%;
+ z-index: -1;
+ clip-path: circle(0% at 20% 50%);
+ transition: 0.3s;
+ }
+
+ .search:focus ~ .ripple-effect {
+ clip-path: circle(100% at 50% 50%);
+ }
+ }
+}
diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx
new file mode 100644
index 0000000..c1991a5
--- /dev/null
+++ b/src/components/Settings/Settings.tsx
@@ -0,0 +1,57 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import './settings.scss';
+import { useState } from 'react';
+import { AnimatePresence, motion } from 'framer-motion';
+import {
+ settingsModalContainerVariants,
+ settingsModalVariants,
+} from './Settings.variants';
+import { useAppContext } from '../../hooks';
+
+const Settings = () => {
+ const [showSettings, setShowSettings] = useState(false);
+ const { state, dispatch } = useAppContext();
+
+ return (
+ <>
+ setShowSettings(true)}
+ >
+
+
+
+ {showSettings && (
+ setShowSettings(false)}
+ >
+ e.stopPropagation()}
+ >
+ Settings
+
+
Metric Unit
+
+
+
+
+ )}
+
+ >
+ );
+};
+
+export default Settings;
diff --git a/src/components/Settings/Settings.variants.ts b/src/components/Settings/Settings.variants.ts
new file mode 100644
index 0000000..2f960c2
--- /dev/null
+++ b/src/components/Settings/Settings.variants.ts
@@ -0,0 +1,28 @@
+import { Variants } from 'framer-motion';
+
+export const settingsModalContainerVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 0.3,
+ type: 'tween',
+ ease: 'easeInOut',
+ },
+ },
+};
+
+export const settingsModalVariants: Variants = {
+ hidden: {
+ y: '100vh',
+ },
+ visible: {
+ y: 0,
+ transition: {
+ type: 'spring',
+ velocity: 2,
+ },
+ },
+};
diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx
new file mode 100644
index 0000000..6a02e4e
--- /dev/null
+++ b/src/components/Settings/index.tsx
@@ -0,0 +1,3 @@
+import Settings from './Settings';
+
+export default Settings;
diff --git a/src/components/Settings/settings.scss b/src/components/Settings/settings.scss
new file mode 100644
index 0000000..fb44b82
--- /dev/null
+++ b/src/components/Settings/settings.scss
@@ -0,0 +1,82 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.menu {
+ @include flex-row-center();
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ border: 2px solid $white;
+ transition: $transition-duration;
+ cursor: pointer;
+ font-size: 0.8rem;
+ box-shadow: $box-shadow;
+ background: transparentize($primary, 0.9);
+ backdrop-filter: $blur;
+
+ &:hover {
+ background: $white;
+ color: $primary;
+ }
+}
+
+.settings-modal-container {
+ @include modal-container(rgba(256, 256, 256, 0.5));
+ z-index: 99;
+ transition: none;
+
+ .settings-modal {
+ @include flex-col(10px);
+ padding: 20px;
+ background: $primary;
+ border-radius: $border-radius;
+ width: 250px;
+ box-shadow: $box-shadow;
+ touch-action: none;
+
+ .title {
+ text-align: center;
+ @include flex-row-center;
+ padding: 0 0 15px;
+ font-size: 1.1rem;
+ }
+
+ .option {
+ @include flex-row;
+ justify-content: space-between;
+ align-items: center;
+
+ &:not(:last-of-type) {
+ margin-bottom: 10px;
+ }
+
+ .button {
+ border-radius: $pill-border;
+ position: relative;
+ box-shadow: $box-shadow;
+ border: none;
+ background: $white;
+
+ &:after {
+ content: '';
+ position: absolute;
+ top: 1.4px;
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+ transition: $transition-duration;
+ }
+
+ &.on:after {
+ left: calc(100% - 16.5px);
+ background: $primary;
+ }
+
+ &.off:after {
+ left: 2px;
+ background: $muted;
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/Spinner/Spinner.tsx b/src/components/Spinner/Spinner.tsx
new file mode 100644
index 0000000..e7e8ce1
--- /dev/null
+++ b/src/components/Spinner/Spinner.tsx
@@ -0,0 +1,45 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { SpinnerProps } from './Spinner.types';
+import './spinner.scss';
+import { AnimatePresence, motion } from 'framer-motion';
+import { useAppContext } from '../../hooks';
+import { SpinnerContainerVariants } from './Spinner.variants';
+
+const Spinner = ({ title }: SpinnerProps) => {
+ const { state } = useAppContext();
+ title = title || 'Loading';
+
+ return (
+
+ {state.loading && (
+
+
+
+
+
+
+
{title}
+
+ .
+ .
+ .
+
+
+
+
+ )}
+
+ );
+};
+
+export default Spinner;
diff --git a/src/components/Spinner/Spinner.types.ts b/src/components/Spinner/Spinner.types.ts
new file mode 100644
index 0000000..1b549d2
--- /dev/null
+++ b/src/components/Spinner/Spinner.types.ts
@@ -0,0 +1,3 @@
+export type SpinnerProps = {
+ title?: string;
+};
diff --git a/src/components/Spinner/Spinner.variants.ts b/src/components/Spinner/Spinner.variants.ts
new file mode 100644
index 0000000..e565337
--- /dev/null
+++ b/src/components/Spinner/Spinner.variants.ts
@@ -0,0 +1,10 @@
+import { Variants } from 'framer-motion';
+
+export const SpinnerContainerVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ },
+};
diff --git a/src/components/Spinner/index.tsx b/src/components/Spinner/index.tsx
new file mode 100644
index 0000000..0484da0
--- /dev/null
+++ b/src/components/Spinner/index.tsx
@@ -0,0 +1,3 @@
+import Spinner from './Spinner';
+
+export default Spinner;
diff --git a/src/components/Spinner/spinner.scss b/src/components/Spinner/spinner.scss
new file mode 100644
index 0000000..21edbf2
--- /dev/null
+++ b/src/components/Spinner/spinner.scss
@@ -0,0 +1,42 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+@use '../../sass/animations.scss' as *;
+
+.spinner-container {
+ @include modal-container(rgba(0, 0, 0, 0.2));
+ z-index: 99;
+ transition: none;
+ cursor: wait;
+
+ .spinner {
+ @include flex-col(20px);
+ align-items: center;
+ text-align: center;
+ width: 100%;
+ border-radius: $border-radius;
+ padding: 20px;
+ color: $white;
+
+ .icon-wrapper .icon {
+ font-size: 4rem;
+ color: $white;
+ animation: rotate 4s linear infinite;
+ }
+
+ .title {
+ @include flex-row;
+ font-size: 1.1rem;
+ letter-spacing: 0.6px;
+
+ .dot {
+ animation: loading 1.5s linear infinite;
+
+ @for $delay from 1 through 3 {
+ &:nth-of-type(#{$delay}) {
+ animation-delay: 0.3s * $delay;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/VisibilityDetail/VisibilityDetail.tsx b/src/components/VisibilityDetail/VisibilityDetail.tsx
new file mode 100644
index 0000000..ad54b19
--- /dev/null
+++ b/src/components/VisibilityDetail/VisibilityDetail.tsx
@@ -0,0 +1,57 @@
+import { motion } from 'framer-motion';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import './visibility-detail.scss';
+import { indicatorVariants } from './VisibilityDetail.variants';
+import { useUnit } from '../../hooks';
+import { cardVariants } from '../../variants';
+import { CurrentWeather } from '../../types';
+
+const VisibilityDetail = ({
+ visibility,
+}: Pick) => {
+ const { getUnit } = useUnit();
+ const visibilityInPercent = (100 / 10000) * visibility;
+ let visibilityInKm = Number((visibility / 1000).toFixed(2));
+ const cleanedVisibility =
+ visibility >= 1000 ? `${visibilityInKm} k` : `${visibility} `;
+
+ return (
+
+
+ Visibility
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default VisibilityDetail;
diff --git a/src/components/VisibilityDetail/VisibilityDetail.types.ts b/src/components/VisibilityDetail/VisibilityDetail.types.ts
new file mode 100644
index 0000000..acff993
--- /dev/null
+++ b/src/components/VisibilityDetail/VisibilityDetail.types.ts
@@ -0,0 +1,3 @@
+export type VisibilityDetailProps = {
+ visibility: number;
+};
diff --git a/src/components/VisibilityDetail/VisibilityDetail.variants.ts b/src/components/VisibilityDetail/VisibilityDetail.variants.ts
new file mode 100644
index 0000000..55ed849
--- /dev/null
+++ b/src/components/VisibilityDetail/VisibilityDetail.variants.ts
@@ -0,0 +1,19 @@
+import { Variants } from 'framer-motion';
+
+export const indicatorVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ x: '-100vw',
+ },
+ enter: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ type: 'spring',
+ stiffness: 200,
+ },
+ },
+ tap: {
+ scale: 1.1,
+ },
+};
diff --git a/src/components/VisibilityDetail/index.tsx b/src/components/VisibilityDetail/index.tsx
new file mode 100644
index 0000000..03eb7a4
--- /dev/null
+++ b/src/components/VisibilityDetail/index.tsx
@@ -0,0 +1,3 @@
+import VisibilityDetail from './VisibilityDetail';
+
+export default VisibilityDetail;
diff --git a/src/components/VisibilityDetail/visibility-detail.scss b/src/components/VisibilityDetail/visibility-detail.scss
new file mode 100644
index 0000000..8fef5be
--- /dev/null
+++ b/src/components/VisibilityDetail/visibility-detail.scss
@@ -0,0 +1,86 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.visibility-container {
+ @include card();
+
+ .visibility-bar {
+ position: relative;
+ width: 100%;
+ height: 4px;
+ border-radius: $border-radius;
+ box-shadow: $box-shadow;
+ background: $white;
+ margin-block: 40px;
+
+ .indicator-wrapper {
+ position: absolute;
+ top: -5px;
+ left: 5px;
+ width: calc(100% - 25px);
+ z-index: 1;
+
+ .indicator {
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 15px;
+ height: 15px;
+ background: $white;
+ border-radius: 50%;
+ cursor: grab;
+
+ &:before {
+ content: attr(data-visibility);
+ position: relative;
+ top: -30px;
+ left: -10px;
+ right: 0;
+ color: $white;
+ @include flex-row();
+ justify-content: flex-start;
+ text-align: center;
+ width: 100px;
+ }
+ }
+ }
+
+ // start point for visibility bar
+ &:before {
+ content: '';
+ position: absolute;
+ top: -5px;
+ left: 10px;
+ width: 5px;
+ height: 15px;
+ background: inherit;
+ border-radius: 12px;
+ }
+
+ // end point for visibility bar
+ &:after {
+ content: '';
+ position: absolute;
+ top: -5px;
+ right: 10px;
+ width: 5px;
+ height: 15px;
+ background: inherit;
+ border-radius: 12px;
+ }
+
+ .invisible-icon {
+ position: absolute;
+ top: 14px;
+ left: 7px;
+ font-size: 0.7rem;
+ }
+
+ .visible-icon {
+ position: absolute;
+ top: 14px;
+ right: 6px;
+ font-size: 0.7rem;
+ }
+ }
+}
diff --git a/src/components/WeatherDetailsContainer/WeatherDetailsContainer.tsx b/src/components/WeatherDetailsContainer/WeatherDetailsContainer.tsx
new file mode 100644
index 0000000..22fd9e3
--- /dev/null
+++ b/src/components/WeatherDetailsContainer/WeatherDetailsContainer.tsx
@@ -0,0 +1,37 @@
+import { motion } from 'framer-motion';
+import { WeatherDetailsContainerProps } from './WeatherDetailsContainer.types';
+import {
+ BasicWeatherDetails,
+ AdvancedWeatherDetails,
+ WeatherForecast,
+ WeatherSource,
+} from '..';
+import './weather-details-container.scss';
+
+const WeatherDetailsContainer = ({
+ currentWeather,
+ forecastData,
+}: WeatherDetailsContainerProps) => {
+ const { weather, main, clouds, visibility, wind, dt, sys, name } =
+ currentWeather;
+ return (
+
+
+
+
+
+
+
+ );
+};
+
+export default WeatherDetailsContainer;
diff --git a/src/components/WeatherDetailsContainer/WeatherDetailsContainer.types.ts b/src/components/WeatherDetailsContainer/WeatherDetailsContainer.types.ts
new file mode 100644
index 0000000..20569ad
--- /dev/null
+++ b/src/components/WeatherDetailsContainer/WeatherDetailsContainer.types.ts
@@ -0,0 +1,7 @@
+import { CurrentWeather } from '../../types';
+import { RefactoredForecastData } from '../../types/Forecasts';
+
+export type WeatherDetailsContainerProps = {
+ currentWeather: CurrentWeather;
+ forecastData: RefactoredForecastData;
+};
diff --git a/src/components/WeatherDetailsContainer/index.tsx b/src/components/WeatherDetailsContainer/index.tsx
new file mode 100644
index 0000000..f1411c8
--- /dev/null
+++ b/src/components/WeatherDetailsContainer/index.tsx
@@ -0,0 +1,3 @@
+import WeatherDetailsContainer from './WeatherDetailsContainer';
+
+export default WeatherDetailsContainer;
diff --git a/src/components/WeatherDetailsContainer/weather-details-container.scss b/src/components/WeatherDetailsContainer/weather-details-container.scss
new file mode 100644
index 0000000..8ebc617
--- /dev/null
+++ b/src/components/WeatherDetailsContainer/weather-details-container.scss
@@ -0,0 +1,14 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.weather-details-container {
+ width: 100%;
+ @include flex-col(30px);
+ padding: 20px;
+
+ .current {
+ @include flex-row-center(30px);
+ align-items: flex-start;
+ flex-wrap: wrap;
+ }
+}
diff --git a/src/components/WeatherForecast/WeatherForecast.tsx b/src/components/WeatherForecast/WeatherForecast.tsx
new file mode 100644
index 0000000..b96af80
--- /dev/null
+++ b/src/components/WeatherForecast/WeatherForecast.tsx
@@ -0,0 +1,88 @@
+import { WeatherIcon } from '..';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { useUnit } from '../../hooks';
+import './weather-forecast.scss';
+import { motion } from 'framer-motion';
+import { cardVariants } from '../../variants';
+import { WeatherForecastProps } from './WeatherForecast.types';
+import {
+ forecastTitleVariants,
+ forecastVariants,
+} from './WeatherForecast.variants';
+
+const WeatherForecast = ({ list }: WeatherForecastProps) => {
+ const { getUnit } = useUnit();
+
+ return (
+
+
+ 5-day Forecast
+
+
+
+ {list.map(forecast => {
+ const { date, weather, temp } = forecast;
+ return (
+
+
+
+ {date.month.slice(0, 3).replace(/^./, m => m.toUpperCase())}{' '}
+ {date.date < 10 ? `0${date.date}` : `${date.date}`}
+
+
+ ({date.day.slice(0, 3).replace(/^./, m => m.toUpperCase())})
+
+
+
+ {weather.description.replace(/^.|(?<=\s)\w/g, m =>
+ m.toUpperCase()
+ )}
+
+
+
+
+
+
+
+ {temp.max} {getUnit('temperature')}
+
+
+
+ {temp.min} {getUnit('temperature')}
+
+
+
+ );
+ })}
+
+
+ );
+};
+
+export default WeatherForecast;
diff --git a/src/components/WeatherForecast/WeatherForecast.types.ts b/src/components/WeatherForecast/WeatherForecast.types.ts
new file mode 100644
index 0000000..a4d32ee
--- /dev/null
+++ b/src/components/WeatherForecast/WeatherForecast.types.ts
@@ -0,0 +1,38 @@
+import { RefactoredForecast } from '../../types/Forecasts';
+
+export type WeatherForecastProps = {
+ list: RefactoredForecast[];
+};
+
+export type DateInNum =
+ | 1
+ | 2
+ | 3
+ | 4
+ | 5
+ | 6
+ | 7
+ | 8
+ | 9
+ | 10
+ | 11
+ | 12
+ | 13
+ | 14
+ | 15
+ | 16
+ | 17
+ | 18
+ | 19
+ | 20
+ | 21
+ | 22
+ | 23
+ | 24
+ | 25
+ | 26
+ | 27
+ | 28
+ | 29
+ | 30
+ | 31;
diff --git a/src/components/WeatherForecast/WeatherForecast.variants.ts b/src/components/WeatherForecast/WeatherForecast.variants.ts
new file mode 100644
index 0000000..ecb94b0
--- /dev/null
+++ b/src/components/WeatherForecast/WeatherForecast.variants.ts
@@ -0,0 +1,29 @@
+import { Variants } from 'framer-motion';
+
+export const forecastTitleVariants: Variants = {
+ hidden: {
+ y: 200,
+ opacity: 0,
+ },
+ enter: {
+ y: 0,
+ opacity: 1,
+ transition: {
+ type: 'spring',
+ },
+ },
+};
+
+export const forecastVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ x: '400px',
+ },
+ enter: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ type: 'spring',
+ },
+ },
+};
diff --git a/src/components/WeatherForecast/index.tsx b/src/components/WeatherForecast/index.tsx
new file mode 100644
index 0000000..e674760
--- /dev/null
+++ b/src/components/WeatherForecast/index.tsx
@@ -0,0 +1,3 @@
+import WeatherForecast from './WeatherForecast';
+
+export default WeatherForecast;
diff --git a/src/components/WeatherForecast/weather-forecast.scss b/src/components/WeatherForecast/weather-forecast.scss
new file mode 100644
index 0000000..3538952
--- /dev/null
+++ b/src/components/WeatherForecast/weather-forecast.scss
@@ -0,0 +1,96 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.weather-forecast-container {
+ @include flex-col(20px);
+ max-width: 800px;
+ margin: 0 auto;
+
+ .weather-forecast-title {
+ font-size: 1rem;
+ text-align: center;
+ position: relative;
+ padding-block: 20px 5px;
+ margin-inline: 10px;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 32px;
+ left: 0;
+ width: calc(50% - 70px);
+ height: 0.8px;
+ background: rgba(256, 256, 256, 0.8);
+ border-radius: 12px;
+ }
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: 32px;
+ right: 0;
+ width: calc(50% - 70px);
+ height: 0.8px;
+ background: rgba(256, 256, 256, 0.8);
+ border-radius: 12px;
+ }
+ }
+
+ .weather-forecasts {
+ @include flex-row(20px);
+ width: 100%;
+ padding: 10px;
+ border-radius: $border-radius;
+ overflow-x: scroll;
+ scrollbar-color: $white transparent;
+
+ // hide scrollbar on mobile
+ @media (hover: none) {
+ & {
+ scrollbar-width: none;
+ }
+ &::-webkit-scrollbar {
+ display: none;
+ }
+ }
+
+ .forecast {
+ @include card(auto);
+ gap: 15px;
+ text-align: center;
+
+ .date {
+ @include flex-row-center(5px);
+ text-align: center;
+ }
+
+ .description {
+ font-size: 0.85rem;
+ max-height: 20px;
+ width: 100px;
+ }
+
+ .icon-wrapper {
+ height: 80px;
+ pointer-events: none;
+
+ .icon {
+ object-fit: contain;
+ width: 100%;
+ height: 100%;
+ }
+ }
+
+ .temps {
+ .temp {
+ @include flex-row-center(5px);
+ font-size: 0.8rem;
+ }
+
+ .icon {
+ font-size: 0.7rem;
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/WeatherIcon/WeatherIcon.tsx b/src/components/WeatherIcon/WeatherIcon.tsx
new file mode 100644
index 0000000..d4fe2af
--- /dev/null
+++ b/src/components/WeatherIcon/WeatherIcon.tsx
@@ -0,0 +1,19 @@
+import { WeatherIconProps } from './WeatherIcon.types';
+
+const WeatherIcon = (props: WeatherIconProps) => {
+ if ('name' in props) {
+ return (
+
+ );
+ } else if ('id' in props) {
+ const { id, theme = '' } = props;
+ return ;
+ } else if ('class' in props) {
+ return ;
+ }
+};
+
+export default WeatherIcon;
diff --git a/src/components/WeatherIcon/WeatherIcon.types.ts b/src/components/WeatherIcon/WeatherIcon.types.ts
new file mode 100644
index 0000000..d90c305
--- /dev/null
+++ b/src/components/WeatherIcon/WeatherIcon.types.ts
@@ -0,0 +1,18 @@
+type OpenWeatherMap = {
+ name: string;
+};
+
+type WeatherIconsRepo = OWMCompatible | UtilClass;
+
+type OWMCompatible = {
+ id: number;
+ theme?: Theme;
+};
+
+type UtilClass = {
+ class: string;
+};
+
+export type WeatherIconProps = OpenWeatherMap | WeatherIconsRepo;
+
+export type Theme = 'day' | 'night';
diff --git a/src/components/WeatherSource/WeatherSource.tsx b/src/components/WeatherSource/WeatherSource.tsx
new file mode 100644
index 0000000..ef8a467
--- /dev/null
+++ b/src/components/WeatherSource/WeatherSource.tsx
@@ -0,0 +1,50 @@
+import { cardVariants } from '../../variants';
+import { WeatherSourceProps } from './WeatherSource.types';
+import './weather-source.scss';
+import { motion } from 'framer-motion';
+
+const WeatherSource = ({ dt }: WeatherSourceProps) => {
+ const currentDate = new Date();
+ const sourceDate = new Date(dt * 1000);
+ let dateString: string = '';
+
+ if (sourceDate.getDate() === currentDate.getDate()) {
+ if (sourceDate.getHours() === currentDate.getHours()) {
+ const minuteOffset = currentDate.getMinutes() - sourceDate.getMinutes();
+
+ dateString =
+ minuteOffset === 0
+ ? 'just now'
+ : `${minuteOffset} ${minuteOffset === 1 ? 'min' : 'mins'} ago`;
+ } else {
+ const hourOffset = currentDate.getHours() - sourceDate.getHours();
+ dateString = `${hourOffset} ${hourOffset === 1 ? 'hour' : 'hours'} ago`;
+ }
+ } else {
+ dateString = `${sourceDate.toLocaleDateString()} ${sourceDate.toLocaleTimeString()}`;
+ }
+
+ return (
+
+
+ Source:{' '}
+
+ OpenWeatherMap
+
+
+
+ Last updated:
+ {dateString}
+
+
+ );
+};
+
+export default WeatherSource;
diff --git a/src/components/WeatherSource/WeatherSource.types.ts b/src/components/WeatherSource/WeatherSource.types.ts
new file mode 100644
index 0000000..8003acc
--- /dev/null
+++ b/src/components/WeatherSource/WeatherSource.types.ts
@@ -0,0 +1,3 @@
+export type WeatherSourceProps = {
+ dt: number;
+};
diff --git a/src/components/WeatherSource/WeatherSource.variants.ts b/src/components/WeatherSource/WeatherSource.variants.ts
new file mode 100644
index 0000000..5c25600
--- /dev/null
+++ b/src/components/WeatherSource/WeatherSource.variants.ts
@@ -0,0 +1,18 @@
+import { Variants } from 'framer-motion';
+
+export const sourceVariants: Variants = {
+ hidden: {
+ y: 400,
+ opacity: 0,
+ maxHeight: 0,
+ },
+ visible: {
+ y: 0,
+ opacity: 1,
+ maxHeight: 9999,
+ transition: {
+ type: 'spring',
+ duration: 1,
+ },
+ },
+};
diff --git a/src/components/WeatherSource/index.tsx b/src/components/WeatherSource/index.tsx
new file mode 100644
index 0000000..7b8e245
--- /dev/null
+++ b/src/components/WeatherSource/index.tsx
@@ -0,0 +1,3 @@
+import WeatherSource from './WeatherSource';
+
+export default WeatherSource;
diff --git a/src/components/WeatherSource/weather-source.scss b/src/components/WeatherSource/weather-source.scss
new file mode 100644
index 0000000..a226e9b
--- /dev/null
+++ b/src/components/WeatherSource/weather-source.scss
@@ -0,0 +1,16 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.source-info {
+ @include flex-row;
+ justify-content: space-between;
+ padding: 20px 10px;
+ font-size: 0.8rem;
+ max-width: 800px;
+ margin: 0 auto;
+
+ .source a {
+ color: inherit;
+ text-decoration: underline;
+ }
+}
diff --git a/src/components/WindDetails/WindDetails.tsx b/src/components/WindDetails/WindDetails.tsx
new file mode 100644
index 0000000..ba33531
--- /dev/null
+++ b/src/components/WindDetails/WindDetails.tsx
@@ -0,0 +1,114 @@
+import { motion } from 'framer-motion';
+import './wind-details-container.scss';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+import { BeaufortScale, WeatherIcon, WindMillIcon } from '..';
+import { useBeaufort, useUnit } from '../../hooks';
+import {
+ windDirectionVariants,
+ windDetailVariants,
+ beaufortDetailExpandVariants,
+} from './WindDetails.variants';
+import { useState } from 'react';
+import { cardVariants } from '../../variants';
+import { CurrentWeather } from '../../types';
+
+const WindDetails = ({ speed, deg, gust }: CurrentWeather['wind']) => {
+ const [showBeaufort, setShowBeaufort] = useState(false);
+ const { getUnit, currentUnit } = useUnit();
+ const beaufortScale = useBeaufort(speed);
+ const speedInMetric = currentUnit === 'imperial' ? speed / 2.24 : speed;
+ const windmillSpeed = beaufortScale.number < 10 ? 1 * speedInMetric : 0;
+
+ return (
+
+
+ Wind Details
+
+
+
+
+
+
+
+ {' '}
+
+ {beaufortScale.description}{' '}
+ setShowBeaufort(!showBeaufort)}
+ >
+
+
+
+
+
+
+
+ {speed} {getUnit('wind_speed')}
+
+ {gust && (
+
+
+
+
+ {gust} {getUnit('wind_speed')}
+
+ )}
+ {deg && (
+
+
+
+
+ {deg} °
+
+ )}
+
+
+ {showBeaufort && }
+
+ );
+};
+
+export default WindDetails;
diff --git a/src/components/WindDetails/WindDetails.variants.ts b/src/components/WindDetails/WindDetails.variants.ts
new file mode 100644
index 0000000..2110924
--- /dev/null
+++ b/src/components/WindDetails/WindDetails.variants.ts
@@ -0,0 +1,49 @@
+import { Variants } from 'framer-motion';
+
+export const windDetailVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ x: '100vw',
+ },
+ visible: {
+ opacity: 1,
+ x: 0,
+ transition: {
+ type: 'tween',
+ duration: 1.5,
+ ease: 'easeInOut',
+ },
+ },
+};
+
+export const beaufortDetailExpandVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ scale: 0,
+ },
+ visible: {
+ opacity: 1,
+ scale: 1,
+ transition: {
+ type: 'spring',
+ stiffness: 300,
+ delay: 4,
+ },
+ },
+};
+
+export const windDirectionVariants: Variants = {
+ hidden: {
+ rotate: -360,
+ opacity: 0,
+ },
+ visible: {
+ rotate: 0,
+ opacity: 1,
+ transition: {
+ type: 'spring',
+ stiffness: 300,
+ delay: 3,
+ },
+ },
+};
diff --git a/src/components/WindDetails/index.tsx b/src/components/WindDetails/index.tsx
new file mode 100644
index 0000000..939277e
--- /dev/null
+++ b/src/components/WindDetails/index.tsx
@@ -0,0 +1,3 @@
+import WindDetails from './WindDetails';
+
+export default WindDetails;
diff --git a/src/components/WindDetails/wind-details-container.scss b/src/components/WindDetails/wind-details-container.scss
new file mode 100644
index 0000000..39e9fdc
--- /dev/null
+++ b/src/components/WindDetails/wind-details-container.scss
@@ -0,0 +1,44 @@
+@use '../../sass/variables.scss' as *;
+@use '../../sass/mixins.scss' as *;
+
+.wind-details-container {
+ @include card();
+
+ .wind-details {
+ @include flex-row-center(50px);
+
+ .wind-info {
+ .icon {
+ font-size: 1rem;
+ }
+
+ .beaufort-scale {
+ @include flex-row(6px);
+
+ .icon-wrapper {
+ cursor: pointer;
+
+ .detail-expand {
+ font-size: 0.9rem;
+ }
+ }
+ }
+
+ .wind-speed {
+ @include flex-row(5px);
+ }
+
+ .wind-gust {
+ @include flex-row(9px);
+
+ .icon {
+ font-size: 0.7rem;
+ }
+ }
+
+ .wind-direction {
+ @include flex-row(10px);
+ }
+ }
+ }
+}
diff --git a/src/components/WindMillIcon/WindMillIcon.tsx b/src/components/WindMillIcon/WindMillIcon.tsx
new file mode 100644
index 0000000..445c861
--- /dev/null
+++ b/src/components/WindMillIcon/WindMillIcon.tsx
@@ -0,0 +1,126 @@
+import { motion } from 'framer-motion';
+import './wind-mill-icon.scss';
+import {
+ windMillAlertVariants,
+ windMillIconVariants,
+} from './WindMillIcon.variants';
+import { WindMillIconProps } from './WindMillIcon.types';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { icon } from '@fortawesome/fontawesome-svg-core/import.macro';
+
+const WindMillIcon = ({ animationSpeed = 1 }: WindMillIconProps) => {
+ const duration = 10 / animationSpeed;
+
+ return (
+
+ {animationSpeed <= 0 && (
+
+ {' '}
+ Turbines shut down
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default WindMillIcon;
diff --git a/src/components/WindMillIcon/WindMillIcon.types.ts b/src/components/WindMillIcon/WindMillIcon.types.ts
new file mode 100644
index 0000000..b3060d4
--- /dev/null
+++ b/src/components/WindMillIcon/WindMillIcon.types.ts
@@ -0,0 +1,3 @@
+export type WindMillIconProps = {
+ animationSpeed?: number;
+};
diff --git a/src/components/WindMillIcon/WindMillIcon.variants.ts b/src/components/WindMillIcon/WindMillIcon.variants.ts
new file mode 100644
index 0000000..0220c57
--- /dev/null
+++ b/src/components/WindMillIcon/WindMillIcon.variants.ts
@@ -0,0 +1,30 @@
+import { Variants } from 'framer-motion';
+
+export const windMillIconVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ },
+ visible: {
+ opacity: 1,
+ transition: {
+ duration: 2,
+ },
+ },
+};
+
+export const windMillAlertVariants: Variants = {
+ hidden: {
+ opacity: 0,
+ scale: 0,
+ y: 90,
+ },
+ visible: {
+ opacity: 1,
+ scale: 1,
+ y: 0,
+ transition: {
+ type: 'spring',
+ delay: 4,
+ },
+ },
+};
diff --git a/src/components/WindMillIcon/index.tsx b/src/components/WindMillIcon/index.tsx
new file mode 100644
index 0000000..7ea92e1
--- /dev/null
+++ b/src/components/WindMillIcon/index.tsx
@@ -0,0 +1,3 @@
+import WindMillIcon from './WindMillIcon';
+
+export default WindMillIcon;
diff --git a/src/components/WindMillIcon/wind-mill-icon.scss b/src/components/WindMillIcon/wind-mill-icon.scss
new file mode 100644
index 0000000..bc2d5f2
--- /dev/null
+++ b/src/components/WindMillIcon/wind-mill-icon.scss
@@ -0,0 +1,30 @@
+@use '../../sass/variables.scss' as *;
+
+.windmill-icon {
+ position: relative;
+ width: 100px;
+ height: 100px;
+
+ .shut-down {
+ position: absolute;
+ top: 5px;
+ left: -25px;
+ font-size: 0.5rem;
+ color: $warning;
+ text-transform: uppercase;
+ width: 70px;
+ letter-spacing: 0.7px;
+ text-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
+ z-index: -1;
+
+ .icon {
+ font-size: 0.5rem;
+ }
+ }
+
+ .windmillSvg {
+ width: 100%;
+ height: 100%;
+ cursor: grab;
+ }
+}
diff --git a/src/components/index.tsx b/src/components/index.tsx
new file mode 100644
index 0000000..0a7b90f
--- /dev/null
+++ b/src/components/index.tsx
@@ -0,0 +1,39 @@
+import Header from './Header';
+import SearchBar from './SearchBar';
+import Settings from './Settings';
+import About from './About';
+import WeatherDetailsContainer from './WeatherDetailsContainer';
+import BasicWeatherDetails from './BasicWeatherDetails';
+import AdvancedWeatherDetails from './AdvancedWeatherDetails';
+import MainDetails from './MainDetails';
+import VisibilityDetail from './VisibilityDetail';
+import WindDetails from './WindDetails';
+import WindMillIcon from './WindMillIcon';
+import BeaufortScale from './BeaufortScale';
+import WeatherForecast from './WeatherForecast';
+import WeatherIcon from './WeatherIcon/WeatherIcon';
+import WeatherSource from './WeatherSource';
+import Particles from './Particles';
+import Spinner from './Spinner';
+import ErrorInfo from './ErrorInfo';
+
+export {
+ Header,
+ SearchBar,
+ Settings,
+ About,
+ WeatherDetailsContainer,
+ BasicWeatherDetails,
+ AdvancedWeatherDetails,
+ MainDetails,
+ VisibilityDetail,
+ WindDetails,
+ WindMillIcon,
+ BeaufortScale,
+ WeatherForecast,
+ WeatherIcon,
+ WeatherSource,
+ Particles,
+ Spinner,
+ ErrorInfo,
+};
diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx
new file mode 100644
index 0000000..aa98707
--- /dev/null
+++ b/src/context/AppContext.tsx
@@ -0,0 +1,17 @@
+import { createContext, useReducer } from 'react';
+import { AppContextType, AppContextProviderProps } from './AppContext.types';
+import { initialState, reducer } from '../state/reducer';
+
+const AppContext = createContext({} as AppContextType);
+
+const AppContextProvider = ({ children }: AppContextProviderProps) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export { AppContext, AppContextProvider };
diff --git a/src/context/AppContext.types.ts b/src/context/AppContext.types.ts
new file mode 100644
index 0000000..857c31c
--- /dev/null
+++ b/src/context/AppContext.types.ts
@@ -0,0 +1,10 @@
+import { Action, State } from '../state/reducer.types';
+
+export type AppContextProviderProps = {
+ children: React.ReactNode;
+};
+
+export type AppContextType = {
+ state: State;
+ dispatch: React.Dispatch;
+};
diff --git a/src/functions/getDay.ts b/src/functions/getDay.ts
new file mode 100644
index 0000000..09d754f
--- /dev/null
+++ b/src/functions/getDay.ts
@@ -0,0 +1,9 @@
+export type Day = 'monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday';
+
+const days: Day[] = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
+
+export type DayInNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6;
+
+export const getDay = (dayInNum: DayInNumber): Day => {
+ return days[dayInNum];
+}
\ No newline at end of file
diff --git a/src/functions/getMonth.ts b/src/functions/getMonth.ts
new file mode 100644
index 0000000..551b57b
--- /dev/null
+++ b/src/functions/getMonth.ts
@@ -0,0 +1,35 @@
+export type Month =
+ | 'january'
+ | 'february'
+ | 'march'
+ | 'april'
+ | 'may'
+ | 'june'
+ | 'july'
+ | 'august'
+ | 'september'
+ | 'october'
+ | 'november'
+ | 'december';
+
+export const months: Month[] = [
+ 'january',
+ 'february',
+ 'march',
+ 'april',
+ 'may',
+ 'june',
+ 'july',
+ 'august',
+ 'august',
+ 'september',
+ 'october',
+ 'november',
+ 'december',
+];
+
+export type MonthInNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11;
+
+export const getMonth = (monthInNum: MonthInNumber): Month => {
+ return months[monthInNum];
+};
diff --git a/src/functions/index.ts b/src/functions/index.ts
new file mode 100644
index 0000000..2be319c
--- /dev/null
+++ b/src/functions/index.ts
@@ -0,0 +1,7 @@
+import { getDay } from "./getDay";
+import { getMonth } from "./getMonth";
+
+export {
+ getDay,
+ getMonth
+}
\ No newline at end of file
diff --git a/src/hooks/index.tsx b/src/hooks/index.tsx
new file mode 100644
index 0000000..4d56701
--- /dev/null
+++ b/src/hooks/index.tsx
@@ -0,0 +1,5 @@
+import { useAppContext } from './useAppContext';
+import { useUnit } from './useUnit';
+import { useBeaufort } from './useBeaufort';
+
+export { useAppContext, useUnit, useBeaufort };
diff --git a/src/hooks/useAppContext.tsx b/src/hooks/useAppContext.tsx
new file mode 100644
index 0000000..392ede5
--- /dev/null
+++ b/src/hooks/useAppContext.tsx
@@ -0,0 +1,4 @@
+import { useContext } from 'react';
+import { AppContext } from '../context/AppContext';
+
+export const useAppContext = () => useContext(AppContext);
diff --git a/src/hooks/useBeaufort.tsx b/src/hooks/useBeaufort.tsx
new file mode 100644
index 0000000..5f51101
--- /dev/null
+++ b/src/hooks/useBeaufort.tsx
@@ -0,0 +1,194 @@
+import { useAppContext } from '.';
+
+const beaufortScales: BeaufortScale[] = [
+ {
+ number: 0,
+ minSpeed: {
+ metric: 0,
+ imperial: 0,
+ },
+ description: 'Calm',
+ conditions: {
+ sea: 'Sea is like a mirror. Smoke rises vertically.',
+ land: 'Calm wind. Smoke rises vertically with little if any drift.',
+ },
+ },
+ {
+ number: 1,
+ minSpeed: {
+ metric: 0.3,
+ imperial: 1,
+ },
+ description: 'Light Air',
+ conditions: {
+ sea: 'Ripples with the appearance of scales are formed, but without foam crests. Smoke drifts from funnel.',
+ land: 'Direction of wind shown by smoke drift, not by wind vanes. Little if any movement with flags. Wind barely moves tree leaves.',
+ },
+ },
+ {
+ number: 2,
+ minSpeed: {
+ metric: 1.6,
+ imperial: 4,
+ },
+ description: 'Light Breeze',
+ conditions: {
+ sea: 'Small wavelets, still short but more pronounced, crests have glassy appearance and do not break. Wind felt on face. Smoke rises at about 80 degrees.',
+ land: 'Wind felt on face. Leaves rustle and small twigs move. Ordinary wind vanes move.',
+ },
+ },
+ {
+ number: 3,
+ minSpeed: {
+ metric: 3.4,
+ imperial: 8,
+ },
+ description: 'Gentle Breeze',
+ conditions: {
+ sea: 'Large wavelets, crests begin to break. Foam of glassy appearance. Perhaps scattered white horses (white caps). Wind extends light flag and pennants. Smoke rises at about 70 deg.',
+ land: 'Leaves and small twigs in constant motion. Wind blows up dry leaves from the ground. Flags are extended out.',
+ },
+ },
+ {
+ number: 4,
+ minSpeed: {
+ metric: 5.5,
+ imperial: 13,
+ },
+ description: 'Moderate Breeze',
+ conditions: {
+ sea: 'Small waves, becoming longer. Fairly frequent white horses (white caps). Wind raises dust and loose paper on deck. Smoke rises at about 50 deg. No noticeable sound in the rigging. Slack halyards curve and sway. Heavy flag flaps limply.',
+ land: 'Wind moves small branches. Wind raises dust and loose paper from the ground and drives them along.',
+ },
+ },
+ {
+ number: 5,
+ minSpeed: {
+ metric: 8,
+ imperial: 19,
+ },
+ description: 'Fresh Breeze',
+ conditions: {
+ sea: "Moderate waves, taking more pronounced long form. Many white horses (white caps) are formed (chance of some spray).\nWind felt strongly on face. Smoke rises at about 30 deg. Slack halyards whip while bending continuously to leeward. Taut halyards maintain slightly bent position. Low whistle in the rigging. Heavy flag doesn't extended but flaps over entire length.",
+ land: 'Large branches and small trees in leaf begin to sway. Crested wavelets form on inland lakes and large rivers.',
+ },
+ },
+ {
+ number: 6,
+ minSpeed: {
+ metric: 10.8,
+ imperial: 25,
+ },
+ description: 'Strong Breeze',
+ conditions: {
+ sea: 'Large waves begin to form. White foam crests are more extensive everywhere (probably some spray).\nWind stings face in temperatures below 35 deg F (2C). Slight effort in maintaining balance against wind. Smoke rises at about 15 deg. Both slack and taut halyards whip slightly in bent position. Low moaning, rather than whistle, in the rigging. Heavy flag extends and flaps more vigorously.',
+ land: 'Large branches in continuous motion. Whistling sounds heard in overhead or nearby power and telephone lines. Umbrellas used with difficulty.',
+ },
+ },
+ {
+ number: 7,
+ minSpeed: {
+ metric: 13.9,
+ imperial: 32,
+ },
+ description: 'Near Gale',
+ conditions: {
+ sea: 'Sea heaps up and white foam from breaking waves begins to be blown in streaks along the direction of wind. Necessary to lean slightly into the wind to maintain balance. Smoke rises at about 5 to 10 deg. Higher pitched moaning and whistling heard from rigging. Halyards still whip slightly. Heavy flag extends fully and flaps only at the end. Oilskins and loose clothing inflate and pull against the body.',
+ land: 'Whole trees in motion. Inconvenience felt when walking against the wind.',
+ },
+ },
+ {
+ number: 8,
+ minSpeed: {
+ metric: 17.2,
+ imperial: 39,
+ },
+ description: 'Gale',
+ conditions: {
+ sea: 'Moderately high waves of greater length. Edges of crests begin to break into the spindrift. The foam is blown in well-marked streaks along the direction of the wind. Head pushed back by the force of the wind if allowed to relax. Oilskins and loose clothing inflate and pull strongly. Halyards rigidly bent. Loud whistle from rigging. Heavy flag straight out and whipping.',
+ land: 'Wind breaks twigs and small branches. Wind generally impedes walking.',
+ },
+ },
+ {
+ number: 9,
+ minSpeed: {
+ metric: 20.8,
+ imperial: 47,
+ },
+ description: 'Strong Gale',
+ conditions: {
+ sea: 'High waves. Dense streaks of foam along direction of wind. Crests of waves begin to topple, tumble and roll over. Spray may affect visibility.',
+ land: 'Structural damage occurs, such as chimney covers, roofing tiles blown off, and television antennas damaged. Ground is littered with many small twigs and broken branches.',
+ },
+ },
+ {
+ number: 10,
+ minSpeed: {
+ metric: 24.5,
+ imperial: 55,
+ },
+ description: 'Whole Gale',
+ conditions: {
+ sea: 'Very high waves with long overhanging crests. The resulting foam, in great patches is blown in dense streaks along the direction of the wind. On the whole, the sea takes on a whitish appearance. Tumbling of the sea becomes heavy and shock-like. Visibility affected.',
+ land: 'Considerable structural damage occurs, especially on roofs. Small trees may be blown over and uprooted.',
+ },
+ },
+ {
+ number: 11,
+ minSpeed: {
+ metric: 28.5,
+ imperial: 64,
+ },
+ description: 'Storm Force',
+ conditions: {
+ sea: 'Exceptionally high waves (small and medium-sized ships might be for time lost to view behind the waves). The sea is completely covered with long white patches of foam lying along the direction of the wind. Everywhere, the edges of the wave crests are blown into froth. Visibility greatly affected.',
+ land: 'Widespread damage occurs. Larger trees blown over and uprooted.',
+ },
+ },
+ {
+ number: 12,
+ minSpeed: {
+ metric: 32.7,
+ imperial: 73,
+ },
+ description: 'Hurricane Force',
+ conditions: {
+ sea: 'The air is filled with foam and spray. The sea is completely white with driving spray. Visibility is seriously affected.',
+ land: 'Severe and extensive damage. Roofs can be peeled off. Windows broken. Trees uprooted. RVs and small mobile homes overturned. Moving automobiles can be pushed off the roadways.',
+ },
+ },
+];
+
+export type BeaufortScale = {
+ number: 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12;
+ minSpeed: {
+ metric: number;
+ imperial: number;
+ };
+ description: string;
+ conditions: {
+ sea: string;
+ land: string;
+ };
+};
+
+const reversedbeaufortScales = [...beaufortScales].reverse();
+
+const useBeaufort = (windSpeed: number) => {
+ const { state } = useAppContext();
+
+ return reversedbeaufortScales.reduce(
+ (result: BeaufortScale, detail: BeaufortScale) => {
+ if (
+ Object.keys(result).length === 0 &&
+ Number(detail.minSpeed[state.configs.unit]) <= Number(windSpeed)
+ ) {
+ return detail;
+ }
+ return result;
+ },
+ {} as BeaufortScale
+ );
+};
+
+export { useBeaufort };
diff --git a/src/hooks/useUnit.tsx b/src/hooks/useUnit.tsx
new file mode 100644
index 0000000..f3270fd
--- /dev/null
+++ b/src/hooks/useUnit.tsx
@@ -0,0 +1,69 @@
+import { useAppContext } from './useAppContext';
+
+const units: Units = {
+ temperature: {
+ metric: '°C',
+ imperial: '°F',
+ },
+ pressure: {
+ metric: 'hPa',
+ imperial: 'hPa',
+ },
+ wind_speed: {
+ metric: 'm/s',
+ imperial: 'mph',
+ },
+ rain: {
+ metric: 'mm',
+ imperial: 'mm',
+ },
+ snow: {
+ metric: 'mm',
+ imperial: 'mm',
+ },
+ visibility: {
+ metric: 'm',
+ imperial: 'm',
+ },
+};
+
+type Units = {
+ temperature: {
+ metric: '°C';
+ imperial: '°F';
+ };
+ pressure: {
+ metric: 'hPa';
+ imperial: 'hPa';
+ };
+ wind_speed: {
+ metric: 'm/s';
+ imperial: 'mph';
+ };
+ rain: {
+ metric: 'mm';
+ imperial: 'mm';
+ };
+ snow: {
+ metric: 'mm';
+ imperial: 'mm';
+ };
+ visibility: {
+ metric: 'm';
+ imperial: 'm';
+ };
+};
+
+const useUnit = () => {
+ const { state } = useAppContext();
+
+ const getUnit = (measurement: T) => {
+ return units[measurement][
+ state.configs.unit
+ ] as Units[T][typeof state.configs.unit];
+ };
+
+ return { getUnit, currentUnit: state.configs.unit };
+};
+
+export { useUnit };
diff --git a/src/main.tsx b/src/main.tsx
new file mode 100644
index 0000000..1a9676e
--- /dev/null
+++ b/src/main.tsx
@@ -0,0 +1,12 @@
+import ReactDOM from 'react-dom/client';
+import App from './App.tsx';
+import { AppContextProvider } from './context/AppContext.tsx';
+import { BrowserRouter as Router } from 'react-router-dom';
+
+ReactDOM.createRoot(document.getElementById('root')!).render(
+
+
+
+
+
+);
diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx
new file mode 100644
index 0000000..01d4f94
--- /dev/null
+++ b/src/pages/Home/Home.tsx
@@ -0,0 +1,62 @@
+import { useEffect } from 'react';
+import { useAppContext } from '../../hooks';
+import { Spinner } from '../../components';
+import { IpInfo } from '../../types/IpInfo';
+import { useNavigate } from 'react-router-dom';
+import axios, { AxiosError, isAxiosError } from 'axios';
+import { AbstractErrorResponse } from '../../types/ErrorResponses';
+
+const Home = () => {
+ const { dispatch } = useAppContext();
+ const navigate = useNavigate();
+
+ const fetchData = async () => {
+ try {
+ dispatch({
+ type: 'FETCH_START',
+ });
+
+ const res = await axios.get(
+ `https://ipgeolocation.abstractapi.com/v1/?api_key=${
+ import.meta.env.VITE_ABSTRACT_KEY
+ }`
+ );
+
+ dispatch({
+ type: 'FETCH_IP_SUCCESS',
+ payload: res.data,
+ });
+
+ const { city, country_code } = res.data;
+ navigate(`/weather?city=${city}&country=${country_code}`);
+ } catch (err) {
+ console.error(err);
+
+ if (isAxiosError(err)) {
+ if (err.response?.data) {
+ dispatch({
+ type: 'FETCH_FAIL',
+ payload: err.response.data,
+ });
+ } else {
+ dispatch({
+ type: 'FETCH_FAIL',
+ payload: err as AxiosError,
+ });
+ }
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchData();
+ }, []);
+
+ return (
+ <>
+
+ >
+ );
+};
+
+export default Home;
diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx
new file mode 100644
index 0000000..fbe3fed
--- /dev/null
+++ b/src/pages/Home/index.tsx
@@ -0,0 +1,3 @@
+import Home from './Home';
+
+export default Home;
diff --git a/src/pages/Weather/Weather.tsx b/src/pages/Weather/Weather.tsx
new file mode 100644
index 0000000..37e9c81
--- /dev/null
+++ b/src/pages/Weather/Weather.tsx
@@ -0,0 +1,89 @@
+import { useSearchParams } from 'react-router-dom';
+import { Spinner, WeatherDetailsContainer } from '../../components';
+import { useEffect } from 'react';
+import { useAppContext } from '../../hooks';
+import axios, { AxiosError, isAxiosError } from 'axios';
+import { CurrentWeather, ForecastData } from '../../types';
+import { OpenWeatherMapErrorResponse } from '../../types/ErrorResponses';
+
+const Weather = () => {
+ const { state, dispatch } = useAppContext();
+ const [searchParams, _] = useSearchParams();
+ let { city, country, lat, lon } = Object.fromEntries(searchParams.entries());
+ lat = Number(lat).toFixed(2);
+ lon = Number(lon).toFixed(2);
+
+ const fetchWeatherByCoord = async () => {
+ return Promise.all([
+ axios.get(
+ `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lat}&appid=${
+ import.meta.env.VITE_OWM_KEY
+ }&units=${state.configs.unit}`
+ ),
+ axios.get(
+ `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${
+ import.meta.env.VITE_OWM_KEY
+ }&units=${state.configs.unit}`
+ ),
+ ]);
+ };
+
+ const fetchWeatherByCity = async () => {
+ return Promise.all([
+ axios.get(
+ `https://api.openweathermap.org/data/2.5/weather?q=${city}${
+ country ? `,${country}` : ''
+ }&appid=${import.meta.env.VITE_OWM_KEY}&units=${state.configs.unit}`
+ ),
+ axios.get(
+ `https://api.openweathermap.org/data/2.5/forecast?q=${city}${
+ country ? `,${country}` : ''
+ }&appid=${import.meta.env.VITE_OWM_KEY}&units=${state.configs.unit}`
+ ),
+ ]);
+ };
+
+ const fetchWeather = async () => {
+ dispatch({ type: 'FETCH_START' });
+ try {
+ const res = city
+ ? await fetchWeatherByCity()
+ : await fetchWeatherByCoord();
+ const data = res.map(res => res.data) as [CurrentWeather, ForecastData];
+ dispatch({ type: 'FETCH_WEATHER_DATA_SUCCESS', payload: data });
+ } catch (err) {
+ console.error(err);
+
+ if (isAxiosError(err)) {
+ if (err.response?.data) {
+ dispatch({
+ type: 'FETCH_FAIL',
+ payload: err.response.data,
+ });
+ } else {
+ dispatch({
+ type: 'FETCH_FAIL',
+ payload: err as AxiosError,
+ });
+ }
+ }
+ }
+ };
+
+ useEffect(() => {
+ fetchWeather();
+ }, [state.configs.unit, lat, lon, city]);
+ return (
+ <>
+
+ {!!state.weather.current && !!state.weather.forecast && (
+
+ )}
+ >
+ );
+};
+
+export default Weather;
diff --git a/src/pages/Weather/index.tsx b/src/pages/Weather/index.tsx
new file mode 100644
index 0000000..e69de29
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
new file mode 100644
index 0000000..bca4ea0
--- /dev/null
+++ b/src/pages/index.tsx
@@ -0,0 +1,4 @@
+import Home from './Home';
+import Weather from './Weather/Weather';
+
+export { Home, Weather };
diff --git a/src/sass/animations.scss b/src/sass/animations.scss
new file mode 100644
index 0000000..3e795ab
--- /dev/null
+++ b/src/sass/animations.scss
@@ -0,0 +1,87 @@
+@use 'variables' as *;
+
+@keyframes rotate {
+ 0% {
+ transform: rotate(0);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
+
+@keyframes fadeIn {
+ 0% {
+ opacity: 0;
+ visibility: hidden;
+ }
+ 100% {
+ opacity: 1;
+ visibility: visible;
+ }
+}
+
+@keyframes fadeOut {
+ 100% {
+ opacity: 0;
+ visibility: hidden;
+ }
+}
+
+@keyframes slideIn {
+ 0% {
+ transform: translateY(-400px);
+ visibility: hidden;
+ }
+ 100% {
+ transform: translateY(0);
+ visibility: visible;
+ }
+}
+
+@keyframes shine {
+ 0% {
+ transform: translate(-300px);
+ background: linear-gradient(
+ to right,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(255, 255, 255, 0.13) 77%,
+ rgba(255, 255, 255, 0.7) 92%,
+ rgba(255, 255, 255, 0) 100%
+ );
+ }
+ 90% {
+ transform: translate(300px);
+ }
+ 100% {
+ transform: translate(500px);
+ opacity: 0;
+ }
+}
+
+@keyframes evolves {
+ 0% {
+ color: $secondary;
+ }
+ 100% {
+ color: $primary;
+ opacity: 1;
+ }
+}
+
+@keyframes float {
+ 50% {
+ transform: translateY(-5px);
+ }
+}
+
+@keyframes loading {
+ 0% {
+ opacity: 1;
+ }
+ 70% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 0;
+ }
+}
diff --git a/src/sass/base.scss b/src/sass/base.scss
new file mode 100644
index 0000000..478a16c
--- /dev/null
+++ b/src/sass/base.scss
@@ -0,0 +1,76 @@
+@use 'variables' as *;
+@use 'mixins' as *;
+@import url('https://fonts.googleapis.com/css2?family=Inter+Tight:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,100;1,200;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
+
+/* Global Styles */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+
+ &:not(i) {
+ font-family: 'Inter Tight', sans-serif;
+ }
+}
+
+body {
+ min-height: 100dvh;
+ background-color: #21d4fd;
+ background-image: linear-gradient(
+ 19deg,
+ darken(#21d4fd, 5%) 0%,
+ darken(#b721ff, 5%) 100%
+ );
+ color: $white;
+ font-weight: 500;
+ font-size: 0.9rem;
+}
+
+#root {
+ width: 100%;
+ min-height: inherit;
+ overflow-x: hidden;
+}
+
+button {
+ background: $primary;
+ color: white;
+ border: none;
+ border-radius: $border-radius;
+ padding: 10px 13px;
+ font-size: 1rem;
+}
+
+.white-space {
+ white-space: pre;
+}
+
+.bold {
+ font-weight: bold;
+}
+
+.italic {
+ font-style: italic;
+}
+
+/* Event Delegation Fix */
+button > * {
+ pointer-events: none;
+}
+
+@media (prefers-reduced-motion: no-preference) {
+ a {
+ color: inherit;
+ }
+
+ a,
+ button {
+ transition: $transition-duration;
+ cursor: pointer;
+ }
+}
+
+::selection {
+ background-color: $primary;
+ color: $white;
+}
diff --git a/src/sass/functions.scss b/src/sass/functions.scss
new file mode 100644
index 0000000..4ba1b8b
--- /dev/null
+++ b/src/sass/functions.scss
@@ -0,0 +1,13 @@
+@use 'variables' as *;
+
+@function adaptive-text($bg) {
+ $yiq-sum: color.red($bg) * 299 + color.green($bg) * 587 + color.blue($bg) *
+ 114;
+ $brightness: math.round(math.div($yiq-sum, 1000));
+
+ @if ($brightness > 125) {
+ @return $purple;
+ } @else {
+ @return white;
+ }
+}
diff --git a/src/sass/mixins.scss b/src/sass/mixins.scss
new file mode 100644
index 0000000..8377b38
--- /dev/null
+++ b/src/sass/mixins.scss
@@ -0,0 +1,169 @@
+@use 'variables' as *;
+
+// flex alignments
+@mixin flex-row($gap: 0px) {
+ display: flex;
+ align-items: center;
+ gap: $gap;
+}
+
+@mixin flex-row-center($gap: 0px) {
+ @include flex-row($gap);
+ justify-content: center;
+}
+
+@mixin flex-col($gap: 0px) {
+ display: flex;
+ flex-direction: column;
+ gap: $gap;
+}
+
+@mixin flex-col-center($gap: 0px) {
+ @include flex-col($gap);
+ justify-content: center;
+}
+
+@mixin flex-center($gap: 0px) {
+ @include flex-col-center($gap);
+ align-items: center;
+}
+
+@mixin fix-center() {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+}
+
+// modal
+
+@mixin modal-container($bg: none, $blurred: true) {
+ @include flex-row-center();
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: $bg;
+
+ @if ($blurred) {
+ backdrop-filter: $blur;
+ }
+}
+
+@mixin modal($bg: #fff, $gap: 25px, $color: inherit, $width: auto) {
+ @include flex-col($gap);
+ width: $width;
+ min-width: 250px;
+ min-height: 200px;
+ padding: 20px;
+ position: relative;
+ background: transparentize($bg, 0.4);
+ backdrop-filter: blur(8px);
+ color: $color;
+ animation: fadeIn $animation-duration 1;
+
+ @if ($width != 100% and $width != 100vw) {
+ max-width: 90%;
+ border-radius: $border-radius;
+ box-shadow: $box-shadow;
+ } @else {
+ height: 100%;
+ }
+
+ @media screen and (max-width: 500px) {
+ padding: 30px;
+ }
+}
+
+@mixin modal-center($bg: #fff, $gap: 25px, $color: inherit, $width: auto) {
+ @include modal($bg, $gap, $color, $width);
+ justify-content: center;
+ align-items: center;
+ text-align: center;
+}
+
+@mixin modal-dismiss-btn($side: right, $hover-color: null, $focus-color: null) {
+ position: absolute;
+ top: 20px;
+ #{$side}: 20px;
+ font-size: 1.8rem;
+ background: none;
+ border: none;
+
+ @if ($hover-color) {
+ &:hover {
+ color: $hover-color;
+ }
+ }
+
+ @if ($focus-color) {
+ &:focus {
+ color: $focus-color;
+ }
+ }
+
+ @if ($hover-color or $focus-color) {
+ transition: $transition-duration;
+ }
+}
+
+// navbar
+@mixin navbar($bg: none, $color: inherit) {
+ @include flex-row();
+ justify-content: space-between;
+ padding: 20px;
+ background: $bg;
+ color: $color;
+ margin-bottom: 15px;
+ max-width: $max-width;
+ margin-inline: auto;
+}
+
+// nav links
+@mixin navlinks($gap, $color: inherit, $hover-color: null) {
+ @include flex-row($gap);
+
+ a {
+ text-decoration: none;
+ }
+
+ & > li {
+ list-style-type: none;
+ margin-inline: none;
+ }
+
+ & > * {
+ color: $color;
+
+ @if ($hover-color) {
+ &:hover {
+ color: $hover-color;
+ }
+
+ &:active {
+ color: darken($hover-color, 20%);
+ }
+ }
+ }
+}
+
+// card with bg blur
+@mixin card($width: 350px) {
+ @include flex-col(20px);
+ background: transparentize($primary, 0.8);
+ backdrop-filter: $blur;
+ border-radius: $border-radius;
+ min-width: $width;
+ padding: 15px 20px;
+ box-shadow: $box-shadow;
+ z-index: 1;
+
+ // disable blur effect and
+ // adjust background color on mobile
+ // to prevent lagging
+ @media (max-width: 1000px) {
+ background: transparentize(darken(#6773fa, 2%), 0.1);
+ backdrop-filter: none;
+ }
+}
diff --git a/src/sass/variables.scss b/src/sass/variables.scss
new file mode 100644
index 0000000..67f2fcf
--- /dev/null
+++ b/src/sass/variables.scss
@@ -0,0 +1,42 @@
+$yellow: rgb(255, 209, 27);
+$pink: rgb(248, 189, 235);
+$blue: rgb(82, 114, 242);
+$navy: rgb(7, 37, 65);
+$light-navy: rgb(36, 20, 104);
+$teal: rgb(31, 138, 112);
+$green: rgb(3, 201, 136);
+$red: rgb(216, 0, 50);
+$grey: rgb(155, 164, 181);
+$dark-grey: darken($grey, 20%);
+$white: rgb(241, 246, 249);
+$dark-navy: rgb(33, 42, 62);
+$black: $dark-navy;
+
+// color palettes
+$blue: rgb(82, 92, 235);
+$black: rgb(61, 59, 64);
+$grey: rgb(191, 207, 231);
+$white: rgb(248, 237, 255);
+
+$primary: $blue;
+$secondary: $black;
+$tertiary: $grey;
+$muted: transparentize($dark-grey, 0.2);
+$success: $green;
+$warning: $yellow;
+$error: $red;
+$bg: $blue;
+
+$max-width: 1000px;
+
+$border-radius: 8px;
+$pill-border: 30px;
+
+$box-shadow: rgba(0, 0, 0, 0.09) 0px 3px 12px;
+$blur: blur(6px);
+
+$transition-duration: 0.3s;
+$animation-duration: 0.5s;
+
+$button-bg: $primary;
+$button-color: $white;
diff --git a/src/state/reducer.tsx b/src/state/reducer.tsx
new file mode 100644
index 0000000..d2d5baa
--- /dev/null
+++ b/src/state/reducer.tsx
@@ -0,0 +1,175 @@
+import { Action, State } from './reducer.types';
+import { getDay, getMonth } from '../functions';
+import { DayInNumber } from '../functions/getDay';
+import { MonthInNumber } from '../functions/getMonth';
+import { DateInNum } from '../components/WeatherForecast/WeatherForecast.types';
+import {
+ ForecastDataByDay,
+ RawWeather,
+ RefactoredForecast,
+} from '../types/Forecasts';
+
+const initialState: Readonly = {
+ configs: {
+ unit: 'metric',
+ },
+ loading: false,
+ error: null,
+ weather: {
+ current: null,
+ forecast: null,
+ },
+ ipInfo: null,
+};
+
+const reducer = (state: State, action: Action): State => {
+ if (import.meta.env.DEV) {
+ console.log(action.type);
+ }
+
+ switch (action.type) {
+ case 'RESTORE_UNIT': {
+ const configs = localStorage.getItem('configs') || '{}';
+ return {
+ ...state,
+ configs: {
+ ...state.configs,
+ unit: JSON.parse(configs)?.unit || 'metric',
+ },
+ };
+ }
+ case 'TOGGLE_UNIT':
+ const unit = state.configs.unit === 'metric' ? 'imperial' : 'metric';
+ localStorage.setItem('configs', JSON.stringify({ unit }));
+ return {
+ ...state,
+ configs: {
+ ...state.configs,
+ unit,
+ },
+ };
+ case 'FETCH_START':
+ return {
+ ...state,
+ loading: true,
+ };
+ case 'FETCH_IP_SUCCESS':
+ return {
+ ...state,
+ loading: false,
+ ipInfo: action.payload,
+ };
+ case 'FETCH_WEATHER_DATA_SUCCESS':
+ // round temperature digits
+ const currentWeather = action.payload[0];
+ const main = action.payload[0].main;
+ const [temp, temp_min, temp_max, feels_like] = [
+ main.temp,
+ main.temp_min,
+ main.temp_max,
+ main.feels_like,
+ ].map(temp => Math.round(Number(temp)));
+
+ // refactor weather forecast data
+ const forecastData = action.payload[1];
+ const date = new Date();
+ const currentDate = date.getDate();
+ const currentHours = date.getHours();
+
+ const list: RefactoredForecast[] = forecastData.list
+ // sort forecasts by day
+ .reduce((result: ForecastDataByDay, data) => {
+ const itemDate = new Date(data.dt * 1000).getDate();
+ const dateOffset = itemDate - currentDate;
+
+ if (dateOffset > 0) {
+ const index = dateOffset - 1;
+
+ if (!result[index]) {
+ result.push([data]);
+ } else {
+ result[index].push(data);
+ }
+ }
+ return result;
+ }, [])
+ .map(day => {
+ const dateObj = new Date(day[0].dt * 1000);
+
+ // combine hourly min/max temps in arrays
+ const rawWeather = day.reduce(
+ (result: RawWeather, hour, index, arr) => {
+ const hours = new Date(hour.dt * 1000).getHours();
+ result.minTemps.push(Math.round(Number(hour.main.temp_min)));
+ result.maxTemps.push(Math.round(Number(hour.main.temp_max)));
+
+ if (!result.weather.length) {
+ if (hours >= currentHours || index === arr.length - 1) {
+ result.weather.push(hour.weather[0]);
+ }
+ }
+ return result;
+ },
+ { minTemps: [], maxTemps: [], weather: [] }
+ );
+
+ return {
+ temp: {
+ min: rawWeather.minTemps.sort((a, b) => a - b)[0],
+ max: rawWeather.maxTemps.sort((a, b) => b - a)[0],
+ },
+ weather: rawWeather.weather[0],
+ // add date info as an extra info
+ // to be used in UI components easily
+ date: {
+ date: dateObj.getDate() as DateInNum,
+ day: getDay(dateObj.getDay() as DayInNumber),
+ month: getMonth(dateObj.getMonth() as MonthInNumber),
+ },
+ };
+ });
+
+ return {
+ ...state,
+ loading: false,
+ weather: {
+ current: {
+ ...currentWeather,
+ main: {
+ ...currentWeather.main,
+ temp,
+ temp_min,
+ temp_max,
+ feels_like,
+ },
+ },
+ forecast: {
+ ...forecastData,
+ list,
+ },
+ },
+ };
+ case 'FETCH_FAIL':
+ let error = 'Unknown error';
+ if ('message' in action.payload) {
+ error = action.payload.message;
+ } else if ('error' in action.payload) {
+ error = action.payload.error.message;
+ }
+ return {
+ ...state,
+ error,
+ loading: false,
+ };
+ case 'DISMISS_ERROR':
+ return {
+ ...state,
+ error: null,
+ };
+ default: {
+ return state;
+ }
+ }
+};
+
+export { initialState, reducer };
diff --git a/src/state/reducer.types.tsx b/src/state/reducer.types.tsx
new file mode 100644
index 0000000..42d3804
--- /dev/null
+++ b/src/state/reducer.types.tsx
@@ -0,0 +1,46 @@
+import { AxiosError } from 'axios';
+import { CurrentWeather, ForecastData } from '../types';
+import {
+ AbstractErrorResponse,
+ OpenWeatherMapErrorResponse,
+} from '../types/ErrorResponses';
+import { RefactoredForecastData } from '../types/Forecasts';
+import { IpInfo } from '../types/IpInfo';
+
+export type State = {
+ configs: {
+ unit: 'imperial' | 'metric';
+ };
+ loading: boolean;
+ error: string | null;
+ weather: {
+ current: CurrentWeather | null;
+ forecast: RefactoredForecastData | null;
+ };
+ ipInfo: IpInfo | null;
+};
+
+export type Action =
+ | NoPayloadActions
+ | FetchWeatherDataSuccessAction
+ | FetchIPSuccessAction
+ | FetchFailureAction;
+
+type NoPayloadActions = {
+ type: 'RESTORE_UNIT' | 'TOGGLE_UNIT' | 'FETCH_START' | 'DISMISS_ERROR';
+};
+
+type FetchWeatherDataSuccessAction = {
+ type: 'FETCH_WEATHER_DATA_SUCCESS';
+ payload: [CurrentWeather, ForecastData];
+};
+
+type FetchIPSuccessAction = {
+ type: 'FETCH_IP_SUCCESS';
+ payload: IpInfo;
+};
+
+type FetchFailureAction = {
+ type: 'FETCH_FAIL';
+ payload: AbstractErrorResponse | OpenWeatherMapErrorResponse | AxiosError;
+};
diff --git a/src/types/CurrentWeather.ts b/src/types/CurrentWeather.ts
new file mode 100644
index 0000000..2c6040f
--- /dev/null
+++ b/src/types/CurrentWeather.ts
@@ -0,0 +1,47 @@
+export type CurrentWeather = {
+ coord: {
+ lon: number;
+ lat: number;
+ };
+ weather: {
+ id: number;
+ main: string;
+ description: string;
+ icon: string;
+ }[];
+ base: string;
+ main: {
+ temp: number;
+ feels_like: number;
+ temp_min: number;
+ temp_max: number;
+ pressure: number;
+ humidity: number;
+ sea_level?: number;
+ grnd_level?: number;
+ };
+ visibility: number;
+ wind: {
+ speed: number;
+ deg: number;
+ gust?: number;
+ };
+ rain?: {
+ '1h': number;
+ };
+ clouds: {
+ all: number;
+ };
+ dt: number;
+ sys: {
+ type: number;
+ id: number;
+ country: string;
+ sunrise: number;
+ sunset: number;
+ };
+ timezone: number;
+ id: number;
+ name: string;
+ cod: number;
+};
diff --git a/src/types/ErrorResponses.ts b/src/types/ErrorResponses.ts
new file mode 100644
index 0000000..5c05676
--- /dev/null
+++ b/src/types/ErrorResponses.ts
@@ -0,0 +1,12 @@
+export type AbstractErrorResponse = {
+ error: {
+ message: string;
+ code: string;
+ details: null | string;
+ };
+};
+
+export type OpenWeatherMapErrorResponse = {
+ cod: string;
+ message: string;
+};
diff --git a/src/types/Forecasts.ts b/src/types/Forecasts.ts
new file mode 100644
index 0000000..b219c70
--- /dev/null
+++ b/src/types/Forecasts.ts
@@ -0,0 +1,88 @@
+import { DateInNum } from '../components/WeatherForecast/WeatherForecast.types';
+import { Day } from '../functions/getDay';
+import { Month } from '../functions/getMonth';
+
+export type ForecastData = {
+ cod: string;
+ message: number;
+ cnt: number;
+ list: Forecast[];
+ city: {
+ id: number;
+ name: string;
+ coord: {
+ lat: number;
+ lon: number;
+ };
+ country: string;
+ population: number;
+ timezone: number;
+ sunrise: number;
+ sunset: number;
+ };
+};
+
+export type Forecast = {
+ dt: number;
+ main: {
+ temp: number;
+ feels_like: number;
+ temp_min: number;
+ temp_max: number;
+ pressure: number;
+ sea_level?: number;
+ grnd_level?: number;
+ humidity: number;
+ temp_kf: number;
+ };
+ weather: WeatherInfo[];
+ clouds: {
+ all: number;
+ };
+ rain?: {
+ '3h': number;
+ };
+ wind: {
+ speed: number;
+ deg: number;
+ gust?: number;
+ };
+ visibility: number;
+ pop: number;
+ sys: {
+ pod: 'n' | 'd';
+ };
+ dt_txt: string;
+};
+
+type WeatherInfo = {
+ id: number;
+ main: string;
+ description: string;
+ icon: string;
+};
+
+export type ForecastDataByDay = Forecast[][];
+
+export type RawWeather = {
+ minTemps: number[];
+ maxTemps: number[];
+ weather: WeatherInfo[];
+};
+
+export type RefactoredForecast = {
+ temp: {
+ min: number;
+ max: number;
+ };
+ weather: WeatherInfo;
+ date: {
+ date: DateInNum;
+ day: Day;
+ month: Month;
+ };
+};
+
+export type RefactoredForecastData = {
+ list: RefactoredForecast[];
+} & Omit;
diff --git a/src/types/IpInfo.ts b/src/types/IpInfo.ts
new file mode 100644
index 0000000..83ede62
--- /dev/null
+++ b/src/types/IpInfo.ts
@@ -0,0 +1,45 @@
+export type IpInfo = {
+ ip_address: string;
+ city: string;
+ city_geoname_id: number;
+ region: string;
+ region_iso_code: string;
+ region_geoname_id: number;
+ postal_code: string;
+ country: string;
+ country_code: string;
+ country_geoname_id: number;
+ country_is_eu: boolean;
+ continent: string;
+ continent_code: string;
+ continent_geoname_id: number;
+ longitude: number;
+ latitude: number;
+ security: {
+ is_vpn: boolean;
+ };
+ timezone: {
+ name: string;
+ abbreviation: string;
+ gmt_offset: number;
+ current_time: string;
+ is_dst: boolean;
+ };
+ flag: {
+ emoji: string;
+ unicode: string;
+ png: string;
+ svg: string;
+ };
+ currency: {
+ currency_name: string;
+ currency_code: string;
+ };
+ connection: {
+ autonomous_system_number: number;
+ autonomous_system_organization: string;
+ connection_type: string;
+ isp_name: string;
+ organization_name: string;
+ };
+};
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..facb4c7
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,4 @@
+import { CurrentWeather } from './CurrentWeather';
+import { Forecast, ForecastData } from './Forecasts';
+
+export type { CurrentWeather, Forecast, ForecastData };
diff --git a/src/variants/cardVariants.ts b/src/variants/cardVariants.ts
new file mode 100644
index 0000000..5e709ab
--- /dev/null
+++ b/src/variants/cardVariants.ts
@@ -0,0 +1,28 @@
+import { Variants } from 'framer-motion';
+
+export const cardVariants: Variants = {
+ hidden: {
+ y: 400,
+ opacity: 0,
+ maxHeight: 0,
+ },
+ visible: {
+ y: 0,
+ opacity: 1,
+ maxHeight: 9999,
+ transition: {
+ type: 'spring',
+ duration: 1,
+ },
+ },
+ exit: {
+ y: '100vh',
+ opacity: 0,
+ transition: {
+ type: 'tween',
+ ease: 'easeInOut',
+ duration: 1,
+ staggerChildren: 0.5,
+ },
+ },
+};
diff --git a/src/variants/index.ts b/src/variants/index.ts
new file mode 100644
index 0000000..1e20b73
--- /dev/null
+++ b/src/variants/index.ts
@@ -0,0 +1,3 @@
+import { cardVariants } from './cardVariants';
+
+export { cardVariants };
diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts
new file mode 100644
index 0000000..11f02fe
--- /dev/null
+++ b/src/vite-env.d.ts
@@ -0,0 +1 @@
+///
diff --git a/src/weather-icons/fonts/weathericons-regular-webfont.eot b/src/weather-icons/fonts/weathericons-regular-webfont.eot
new file mode 100755
index 0000000..330b7ec
Binary files /dev/null and b/src/weather-icons/fonts/weathericons-regular-webfont.eot differ
diff --git a/src/weather-icons/fonts/weathericons-regular-webfont.svg b/src/weather-icons/fonts/weathericons-regular-webfont.svg
new file mode 100755
index 0000000..397d730
--- /dev/null
+++ b/src/weather-icons/fonts/weathericons-regular-webfont.svg
@@ -0,0 +1,257 @@
+
+
+
\ No newline at end of file
diff --git a/src/weather-icons/fonts/weathericons-regular-webfont.ttf b/src/weather-icons/fonts/weathericons-regular-webfont.ttf
new file mode 100755
index 0000000..948f0a5
Binary files /dev/null and b/src/weather-icons/fonts/weathericons-regular-webfont.ttf differ
diff --git a/src/weather-icons/fonts/weathericons-regular-webfont.woff b/src/weather-icons/fonts/weathericons-regular-webfont.woff
new file mode 100755
index 0000000..e0b2f94
Binary files /dev/null and b/src/weather-icons/fonts/weathericons-regular-webfont.woff differ
diff --git a/src/weather-icons/fonts/weathericons-regular-webfont.woff2 b/src/weather-icons/fonts/weathericons-regular-webfont.woff2
new file mode 100755
index 0000000..bb0c19d
Binary files /dev/null and b/src/weather-icons/fonts/weathericons-regular-webfont.woff2 differ
diff --git a/src/weather-icons/weather-icons.min.css b/src/weather-icons/weather-icons.min.css
new file mode 100644
index 0000000..9b364ec
--- /dev/null
+++ b/src/weather-icons/weather-icons.min.css
@@ -0,0 +1,21 @@
+/*!
+ * Weather Icons 2.0.10
+ * Updated November 1, 2020
+ * Weather themed icons for Bootstrap
+ * Author - Erik Flowers - erik@helloerik.com
+ * Email: erik@helloerik.com
+ * Twitter: http://twitter.com/Erik_UX
+ * ------------------------------------------------------------------------------
+ * Maintained at http://erikflowers.github.io/weather-icons
+ *
+ * License
+ * ------------------------------------------------------------------------------
+ * - Font licensed under SIL OFL 1.1 -
+ * http://scripts.sil.org/OFL
+ * - CSS, SCSS and LESS are licensed under MIT License -
+ * http://opensource.org/licenses/mit-license.html
+ * - Documentation licensed under CC BY 3.0 -
+ * http://creativecommons.org/licenses/by/3.0/
+ * - Inspired by and works great as a companion with Font Awesome
+ * "Font Awesome by Dave Gandy - http://fontawesome.io"
+ */@font-face{font-family:'weathericons';src:url('./fonts/weathericons-regular-webfont.eot');src:url('./fonts/weathericons-regular-webfont.eot?#iefix') format('embedded-opentype'),url('./fonts/weathericons-regular-webfont.woff2') format('woff2'),url('./fonts/weathericons-regular-webfont.woff') format('woff'),url('./fonts/weathericons-regular-webfont.ttf') format('truetype'),url('./fonts/weathericons-regular-webfont.svg#weather_iconsregular') format('svg');font-weight:normal;font-style:normal}.wi{display:inline-block;font-family:'weathericons';font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.wi-fw{text-align:center;width:1.4em}.wi-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.wi-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.wi-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.wi-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.wi-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}.wi-day-sunny:before{content:"\f00d"}.wi-day-cloudy:before{content:"\f002"}.wi-day-cloudy-gusts:before{content:"\f000"}.wi-day-cloudy-windy:before{content:"\f001"}.wi-day-fog:before{content:"\f003"}.wi-day-hail:before{content:"\f004"}.wi-day-haze:before{content:"\f0b6"}.wi-day-lightning:before{content:"\f005"}.wi-day-rain:before{content:"\f008"}.wi-day-rain-mix:before{content:"\f006"}.wi-day-rain-wind:before{content:"\f007"}.wi-day-showers:before{content:"\f009"}.wi-day-sleet:before{content:"\f0b2"}.wi-day-sleet-storm:before{content:"\f068"}.wi-day-snow:before{content:"\f00a"}.wi-day-snow-thunderstorm:before{content:"\f06b"}.wi-day-snow-wind:before{content:"\f065"}.wi-day-sprinkle:before{content:"\f00b"}.wi-day-storm-showers:before{content:"\f00e"}.wi-day-sunny-overcast:before{content:"\f00c"}.wi-day-thunderstorm:before{content:"\f010"}.wi-day-windy:before{content:"\f085"}.wi-solar-eclipse:before{content:"\f06e"}.wi-hot:before{content:"\f072"}.wi-day-cloudy-high:before{content:"\f07d"}.wi-day-light-wind:before{content:"\f0c4"}.wi-night-clear:before{content:"\f02e"}.wi-night-alt-cloudy:before{content:"\f086"}.wi-night-alt-cloudy-gusts:before{content:"\f022"}.wi-night-alt-cloudy-windy:before{content:"\f023"}.wi-night-alt-hail:before{content:"\f024"}.wi-night-alt-lightning:before{content:"\f025"}.wi-night-alt-rain:before{content:"\f028"}.wi-night-alt-rain-mix:before{content:"\f026"}.wi-night-alt-rain-wind:before{content:"\f027"}.wi-night-alt-showers:before{content:"\f029"}.wi-night-alt-sleet:before{content:"\f0b4"}.wi-night-alt-sleet-storm:before{content:"\f06a"}.wi-night-alt-snow:before{content:"\f02a"}.wi-night-alt-snow-thunderstorm:before{content:"\f06d"}.wi-night-alt-snow-wind:before{content:"\f067"}.wi-night-alt-sprinkle:before{content:"\f02b"}.wi-night-alt-storm-showers:before{content:"\f02c"}.wi-night-alt-thunderstorm:before{content:"\f02d"}.wi-night-cloudy:before{content:"\f031"}.wi-night-cloudy-gusts:before{content:"\f02f"}.wi-night-cloudy-windy:before{content:"\f030"}.wi-night-fog:before{content:"\f04a"}.wi-night-hail:before{content:"\f032"}.wi-night-lightning:before{content:"\f033"}.wi-night-partly-cloudy:before{content:"\f083"}.wi-night-rain:before{content:"\f036"}.wi-night-rain-mix:before{content:"\f034"}.wi-night-rain-wind:before{content:"\f035"}.wi-night-showers:before{content:"\f037"}.wi-night-sleet:before{content:"\f0b3"}.wi-night-sleet-storm:before{content:"\f069"}.wi-night-snow:before{content:"\f038"}.wi-night-snow-thunderstorm:before{content:"\f06c"}.wi-night-snow-wind:before{content:"\f066"}.wi-night-sprinkle:before{content:"\f039"}.wi-night-storm-showers:before{content:"\f03a"}.wi-night-thunderstorm:before{content:"\f03b"}.wi-lunar-eclipse:before{content:"\f070"}.wi-stars:before{content:"\f077"}.wi-storm-showers:before{content:"\f01d"}.wi-thunderstorm:before{content:"\f01e"}.wi-night-alt-cloudy-high:before{content:"\f07e"}.wi-night-cloudy-high:before{content:"\f080"}.wi-night-alt-partly-cloudy:before{content:"\f081"}.wi-cloud:before{content:"\f041"}.wi-cloudy:before{content:"\f013"}.wi-cloudy-gusts:before{content:"\f011"}.wi-cloudy-windy:before{content:"\f012"}.wi-fog:before{content:"\f014"}.wi-hail:before{content:"\f015"}.wi-rain:before{content:"\f019"}.wi-rain-mix:before{content:"\f017"}.wi-rain-wind:before{content:"\f018"}.wi-showers:before{content:"\f01a"}.wi-sleet:before{content:"\f0b5"}.wi-snow:before{content:"\f01b"}.wi-sprinkle:before{content:"\f01c"}.wi-storm-showers:before{content:"\f01d"}.wi-thunderstorm:before{content:"\f01e"}.wi-snow-wind:before{content:"\f064"}.wi-snow:before{content:"\f01b"}.wi-smog:before{content:"\f074"}.wi-smoke:before{content:"\f062"}.wi-lightning:before{content:"\f016"}.wi-raindrops:before{content:"\f04e"}.wi-raindrop:before{content:"\f078"}.wi-dust:before{content:"\f063"}.wi-snowflake-cold:before{content:"\f076"}.wi-windy:before{content:"\f021"}.wi-strong-wind:before{content:"\f050"}.wi-sandstorm:before{content:"\f082"}.wi-earthquake:before{content:"\f0c6"}.wi-fire:before{content:"\f0c7"}.wi-flood:before{content:"\f07c"}.wi-meteor:before{content:"\f071"}.wi-tsunami:before{content:"\f0c5"}.wi-volcano:before{content:"\f0c8"}.wi-hurricane:before{content:"\f073"}.wi-tornado:before{content:"\f056"}.wi-small-craft-advisory:before{content:"\f0cc"}.wi-gale-warning:before{content:"\f0cd"}.wi-storm-warning:before{content:"\f0ce"}.wi-hurricane-warning:before{content:"\f0cf"}.wi-wind-direction:before{content:"\f0b1"}.wi-alien:before{content:"\f075"}.wi-celsius:before{content:"\f03c"}.wi-fahrenheit:before{content:"\f045"}.wi-degrees:before{content:"\f042"}.wi-thermometer:before{content:"\f055"}.wi-thermometer-exterior:before{content:"\f053"}.wi-thermometer-internal:before{content:"\f054"}.wi-cloud-down:before{content:"\f03d"}.wi-cloud-up:before{content:"\f040"}.wi-cloud-refresh:before{content:"\f03e"}.wi-horizon:before{content:"\f047"}.wi-horizon-alt:before{content:"\f046"}.wi-sunrise:before{content:"\f051"}.wi-sunset:before{content:"\f052"}.wi-moonrise:before{content:"\f0c9"}.wi-moonset:before{content:"\f0ca"}.wi-refresh:before{content:"\f04c"}.wi-refresh-alt:before{content:"\f04b"}.wi-umbrella:before{content:"\f084"}.wi-barometer:before{content:"\f079"}.wi-humidity:before{content:"\f07a"}.wi-na:before{content:"\f07b"}.wi-train:before{content:"\f0cb"}.wi-moon-new:before{content:"\f095"}.wi-moon-waxing-crescent-1:before{content:"\f096"}.wi-moon-waxing-crescent-2:before{content:"\f097"}.wi-moon-waxing-crescent-3:before{content:"\f098"}.wi-moon-waxing-crescent-4:before{content:"\f099"}.wi-moon-waxing-crescent-5:before{content:"\f09a"}.wi-moon-waxing-crescent-6:before{content:"\f09b"}.wi-moon-first-quarter:before{content:"\f09c"}.wi-moon-waxing-gibbous-1:before{content:"\f09d"}.wi-moon-waxing-gibbous-2:before{content:"\f09e"}.wi-moon-waxing-gibbous-3:before{content:"\f09f"}.wi-moon-waxing-gibbous-4:before{content:"\f0a0"}.wi-moon-waxing-gibbous-5:before{content:"\f0a1"}.wi-moon-waxing-gibbous-6:before{content:"\f0a2"}.wi-moon-full:before{content:"\f0a3"}.wi-moon-waning-gibbous-1:before{content:"\f0a4"}.wi-moon-waning-gibbous-2:before{content:"\f0a5"}.wi-moon-waning-gibbous-3:before{content:"\f0a6"}.wi-moon-waning-gibbous-4:before{content:"\f0a7"}.wi-moon-waning-gibbous-5:before{content:"\f0a8"}.wi-moon-waning-gibbous-6:before{content:"\f0a9"}.wi-moon-third-quarter:before{content:"\f0aa"}.wi-moon-waning-crescent-1:before{content:"\f0ab"}.wi-moon-waning-crescent-2:before{content:"\f0ac"}.wi-moon-waning-crescent-3:before{content:"\f0ad"}.wi-moon-waning-crescent-4:before{content:"\f0ae"}.wi-moon-waning-crescent-5:before{content:"\f0af"}.wi-moon-waning-crescent-6:before{content:"\f0b0"}.wi-moon-alt-new:before{content:"\f0eb"}.wi-moon-alt-waxing-crescent-1:before{content:"\f0d0"}.wi-moon-alt-waxing-crescent-2:before{content:"\f0d1"}.wi-moon-alt-waxing-crescent-3:before{content:"\f0d2"}.wi-moon-alt-waxing-crescent-4:before{content:"\f0d3"}.wi-moon-alt-waxing-crescent-5:before{content:"\f0d4"}.wi-moon-alt-waxing-crescent-6:before{content:"\f0d5"}.wi-moon-alt-first-quarter:before{content:"\f0d6"}.wi-moon-alt-waxing-gibbous-1:before{content:"\f0d7"}.wi-moon-alt-waxing-gibbous-2:before{content:"\f0d8"}.wi-moon-alt-waxing-gibbous-3:before{content:"\f0d9"}.wi-moon-alt-waxing-gibbous-4:before{content:"\f0da"}.wi-moon-alt-waxing-gibbous-5:before{content:"\f0db"}.wi-moon-alt-waxing-gibbous-6:before{content:"\f0dc"}.wi-moon-alt-full:before{content:"\f0dd"}.wi-moon-alt-waning-gibbous-1:before{content:"\f0de"}.wi-moon-alt-waning-gibbous-2:before{content:"\f0df"}.wi-moon-alt-waning-gibbous-3:before{content:"\f0e0"}.wi-moon-alt-waning-gibbous-4:before{content:"\f0e1"}.wi-moon-alt-waning-gibbous-5:before{content:"\f0e2"}.wi-moon-alt-waning-gibbous-6:before{content:"\f0e3"}.wi-moon-alt-third-quarter:before{content:"\f0e4"}.wi-moon-alt-waning-crescent-1:before{content:"\f0e5"}.wi-moon-alt-waning-crescent-2:before{content:"\f0e6"}.wi-moon-alt-waning-crescent-3:before{content:"\f0e7"}.wi-moon-alt-waning-crescent-4:before{content:"\f0e8"}.wi-moon-alt-waning-crescent-5:before{content:"\f0e9"}.wi-moon-alt-waning-crescent-6:before{content:"\f0ea"}.wi-moon-0:before{content:"\f095"}.wi-moon-1:before{content:"\f096"}.wi-moon-2:before{content:"\f097"}.wi-moon-3:before{content:"\f098"}.wi-moon-4:before{content:"\f099"}.wi-moon-5:before{content:"\f09a"}.wi-moon-6:before{content:"\f09b"}.wi-moon-7:before{content:"\f09c"}.wi-moon-8:before{content:"\f09d"}.wi-moon-9:before{content:"\f09e"}.wi-moon-10:before{content:"\f09f"}.wi-moon-11:before{content:"\f0a0"}.wi-moon-12:before{content:"\f0a1"}.wi-moon-13:before{content:"\f0a2"}.wi-moon-14:before{content:"\f0a3"}.wi-moon-15:before{content:"\f0a4"}.wi-moon-16:before{content:"\f0a5"}.wi-moon-17:before{content:"\f0a6"}.wi-moon-18:before{content:"\f0a7"}.wi-moon-19:before{content:"\f0a8"}.wi-moon-20:before{content:"\f0a9"}.wi-moon-21:before{content:"\f0aa"}.wi-moon-22:before{content:"\f0ab"}.wi-moon-23:before{content:"\f0ac"}.wi-moon-24:before{content:"\f0ad"}.wi-moon-25:before{content:"\f0ae"}.wi-moon-26:before{content:"\f0af"}.wi-moon-27:before{content:"\f0b0"}.wi-time-1:before{content:"\f08a"}.wi-time-2:before{content:"\f08b"}.wi-time-3:before{content:"\f08c"}.wi-time-4:before{content:"\f08d"}.wi-time-5:before{content:"\f08e"}.wi-time-6:before{content:"\f08f"}.wi-time-7:before{content:"\f090"}.wi-time-8:before{content:"\f091"}.wi-time-9:before{content:"\f092"}.wi-time-10:before{content:"\f093"}.wi-time-11:before{content:"\f094"}.wi-time-12:before{content:"\f089"}.wi-direction-up:before{content:"\f058"}.wi-direction-up-right:before{content:"\f057"}.wi-direction-right:before{content:"\f04d"}.wi-direction-down-right:before{content:"\f088"}.wi-direction-down:before{content:"\f044"}.wi-direction-down-left:before{content:"\f043"}.wi-direction-left:before{content:"\f048"}.wi-direction-up-left:before{content:"\f087"}.wi-wind-beaufort-0:before{content:"\f0b7"}.wi-wind-beaufort-1:before{content:"\f0b8"}.wi-wind-beaufort-2:before{content:"\f0b9"}.wi-wind-beaufort-3:before{content:"\f0ba"}.wi-wind-beaufort-4:before{content:"\f0bb"}.wi-wind-beaufort-5:before{content:"\f0bc"}.wi-wind-beaufort-6:before{content:"\f0bd"}.wi-wind-beaufort-7:before{content:"\f0be"}.wi-wind-beaufort-8:before{content:"\f0bf"}.wi-wind-beaufort-9:before{content:"\f0c0"}.wi-wind-beaufort-10:before{content:"\f0c1"}.wi-wind-beaufort-11:before{content:"\f0c2"}.wi-wind-beaufort-12:before{content:"\f0c3"}.wi-yahoo-0:before{content:"\f056"}.wi-yahoo-1:before{content:"\f00e"}.wi-yahoo-2:before{content:"\f073"}.wi-yahoo-3:before{content:"\f01e"}.wi-yahoo-4:before{content:"\f01e"}.wi-yahoo-5:before{content:"\f017"}.wi-yahoo-6:before{content:"\f017"}.wi-yahoo-7:before{content:"\f017"}.wi-yahoo-8:before{content:"\f015"}.wi-yahoo-9:before{content:"\f01a"}.wi-yahoo-10:before{content:"\f015"}.wi-yahoo-11:before{content:"\f01a"}.wi-yahoo-12:before{content:"\f01a"}.wi-yahoo-13:before{content:"\f01b"}.wi-yahoo-14:before{content:"\f00a"}.wi-yahoo-15:before{content:"\f064"}.wi-yahoo-16:before{content:"\f01b"}.wi-yahoo-17:before{content:"\f015"}.wi-yahoo-18:before{content:"\f017"}.wi-yahoo-19:before{content:"\f063"}.wi-yahoo-20:before{content:"\f014"}.wi-yahoo-21:before{content:"\f021"}.wi-yahoo-22:before{content:"\f062"}.wi-yahoo-23:before{content:"\f050"}.wi-yahoo-24:before{content:"\f050"}.wi-yahoo-25:before{content:"\f076"}.wi-yahoo-26:before{content:"\f013"}.wi-yahoo-27:before{content:"\f031"}.wi-yahoo-28:before{content:"\f002"}.wi-yahoo-29:before{content:"\f031"}.wi-yahoo-30:before{content:"\f002"}.wi-yahoo-31:before{content:"\f02e"}.wi-yahoo-32:before{content:"\f00d"}.wi-yahoo-33:before{content:"\f083"}.wi-yahoo-34:before{content:"\f00c"}.wi-yahoo-35:before{content:"\f017"}.wi-yahoo-36:before{content:"\f072"}.wi-yahoo-37:before{content:"\f00e"}.wi-yahoo-38:before{content:"\f00e"}.wi-yahoo-39:before{content:"\f00e"}.wi-yahoo-40:before{content:"\f01a"}.wi-yahoo-41:before{content:"\f064"}.wi-yahoo-42:before{content:"\f01b"}.wi-yahoo-43:before{content:"\f064"}.wi-yahoo-44:before{content:"\f00c"}.wi-yahoo-45:before{content:"\f00e"}.wi-yahoo-46:before{content:"\f01b"}.wi-yahoo-47:before{content:"\f00e"}.wi-yahoo-3200:before{content:"\f077"}.wi-forecast-io-clear-day:before{content:"\f00d"}.wi-forecast-io-clear-night:before{content:"\f02e"}.wi-forecast-io-rain:before{content:"\f019"}.wi-forecast-io-snow:before{content:"\f01b"}.wi-forecast-io-sleet:before{content:"\f0b5"}.wi-forecast-io-wind:before{content:"\f050"}.wi-forecast-io-fog:before{content:"\f014"}.wi-forecast-io-cloudy:before{content:"\f013"}.wi-forecast-io-partly-cloudy-day:before{content:"\f002"}.wi-forecast-io-partly-cloudy-night:before{content:"\f031"}.wi-forecast-io-hail:before{content:"\f015"}.wi-forecast-io-thunderstorm:before{content:"\f01e"}.wi-forecast-io-tornado:before{content:"\f056"}.wi-wmo4680-0:before,.wi-wmo4680-00:before{content:"\f055"}.wi-wmo4680-1:before,.wi-wmo4680-01:before{content:"\f013"}.wi-wmo4680-2:before,.wi-wmo4680-02:before{content:"\f055"}.wi-wmo4680-3:before,.wi-wmo4680-03:before{content:"\f013"}.wi-wmo4680-4:before,.wi-wmo4680-04:before{content:"\f014"}.wi-wmo4680-5:before,.wi-wmo4680-05:before{content:"\f014"}.wi-wmo4680-10:before{content:"\f014"}.wi-wmo4680-11:before{content:"\f014"}.wi-wmo4680-12:before{content:"\f016"}.wi-wmo4680-18:before{content:"\f050"}.wi-wmo4680-20:before{content:"\f014"}.wi-wmo4680-21:before{content:"\f017"}.wi-wmo4680-22:before{content:"\f017"}.wi-wmo4680-23:before{content:"\f019"}.wi-wmo4680-24:before{content:"\f01b"}.wi-wmo4680-25:before{content:"\f015"}.wi-wmo4680-26:before{content:"\f01e"}.wi-wmo4680-27:before{content:"\f063"}.wi-wmo4680-28:before{content:"\f063"}.wi-wmo4680-29:before{content:"\f063"}.wi-wmo4680-30:before{content:"\f014"}.wi-wmo4680-31:before{content:"\f014"}.wi-wmo4680-32:before{content:"\f014"}.wi-wmo4680-33:before{content:"\f014"}.wi-wmo4680-34:before{content:"\f014"}.wi-wmo4680-35:before{content:"\f014"}.wi-wmo4680-40:before{content:"\f017"}.wi-wmo4680-41:before{content:"\f01c"}.wi-wmo4680-42:before{content:"\f019"}.wi-wmo4680-43:before{content:"\f01c"}.wi-wmo4680-44:before{content:"\f019"}.wi-wmo4680-45:before{content:"\f015"}.wi-wmo4680-46:before{content:"\f015"}.wi-wmo4680-47:before{content:"\f01b"}.wi-wmo4680-48:before{content:"\f01b"}.wi-wmo4680-50:before{content:"\f01c"}.wi-wmo4680-51:before{content:"\f01c"}.wi-wmo4680-52:before{content:"\f019"}.wi-wmo4680-53:before{content:"\f019"}.wi-wmo4680-54:before{content:"\f076"}.wi-wmo4680-55:before{content:"\f076"}.wi-wmo4680-56:before{content:"\f076"}.wi-wmo4680-57:before{content:"\f01c"}.wi-wmo4680-58:before{content:"\f019"}.wi-wmo4680-60:before{content:"\f01c"}.wi-wmo4680-61:before{content:"\f01c"}.wi-wmo4680-62:before{content:"\f019"}.wi-wmo4680-63:before{content:"\f019"}.wi-wmo4680-64:before{content:"\f015"}.wi-wmo4680-65:before{content:"\f015"}.wi-wmo4680-66:before{content:"\f015"}.wi-wmo4680-67:before{content:"\f017"}.wi-wmo4680-68:before{content:"\f017"}.wi-wmo4680-70:before{content:"\f01b"}.wi-wmo4680-71:before{content:"\f01b"}.wi-wmo4680-72:before{content:"\f01b"}.wi-wmo4680-73:before{content:"\f01b"}.wi-wmo4680-74:before{content:"\f076"}.wi-wmo4680-75:before{content:"\f076"}.wi-wmo4680-76:before{content:"\f076"}.wi-wmo4680-77:before{content:"\f01b"}.wi-wmo4680-78:before{content:"\f076"}.wi-wmo4680-80:before{content:"\f019"}.wi-wmo4680-81:before{content:"\f01c"}.wi-wmo4680-82:before{content:"\f019"}.wi-wmo4680-83:before{content:"\f019"}.wi-wmo4680-84:before{content:"\f01d"}.wi-wmo4680-85:before{content:"\f017"}.wi-wmo4680-86:before{content:"\f017"}.wi-wmo4680-87:before{content:"\f017"}.wi-wmo4680-89:before{content:"\f015"}.wi-wmo4680-90:before{content:"\f016"}.wi-wmo4680-91:before{content:"\f01d"}.wi-wmo4680-92:before{content:"\f01e"}.wi-wmo4680-93:before{content:"\f01e"}.wi-wmo4680-94:before{content:"\f016"}.wi-wmo4680-95:before{content:"\f01e"}.wi-wmo4680-96:before{content:"\f01e"}.wi-wmo4680-99:before{content:"\f056"}.wi-owm-200:before{content:"\f01e"}.wi-owm-201:before{content:"\f01e"}.wi-owm-202:before{content:"\f01e"}.wi-owm-210:before{content:"\f016"}.wi-owm-211:before{content:"\f016"}.wi-owm-212:before{content:"\f016"}.wi-owm-221:before{content:"\f016"}.wi-owm-230:before{content:"\f01e"}.wi-owm-231:before{content:"\f01e"}.wi-owm-232:before{content:"\f01e"}.wi-owm-300:before{content:"\f01c"}.wi-owm-301:before{content:"\f01c"}.wi-owm-302:before{content:"\f019"}.wi-owm-310:before{content:"\f017"}.wi-owm-311:before{content:"\f019"}.wi-owm-312:before{content:"\f019"}.wi-owm-313:before{content:"\f01a"}.wi-owm-314:before{content:"\f019"}.wi-owm-321:before{content:"\f01c"}.wi-owm-500:before{content:"\f01c"}.wi-owm-501:before{content:"\f019"}.wi-owm-502:before{content:"\f019"}.wi-owm-503:before{content:"\f019"}.wi-owm-504:before{content:"\f019"}.wi-owm-511:before{content:"\f017"}.wi-owm-520:before{content:"\f01a"}.wi-owm-521:before{content:"\f01a"}.wi-owm-522:before{content:"\f01a"}.wi-owm-531:before{content:"\f01d"}.wi-owm-600:before{content:"\f01b"}.wi-owm-601:before{content:"\f01b"}.wi-owm-602:before{content:"\f0b5"}.wi-owm-611:before{content:"\f017"}.wi-owm-612:before{content:"\f017"}.wi-owm-615:before{content:"\f017"}.wi-owm-616:before{content:"\f017"}.wi-owm-620:before{content:"\f017"}.wi-owm-621:before{content:"\f01b"}.wi-owm-622:before{content:"\f01b"}.wi-owm-701:before{content:"\f014"}.wi-owm-711:before{content:"\f062"}.wi-owm-721:before{content:"\f0b6"}.wi-owm-731:before{content:"\f063"}.wi-owm-741:before{content:"\f014"}.wi-owm-761:before{content:"\f063"}.wi-owm-762:before{content:"\f063"}.wi-owm-771:before{content:"\f011"}.wi-owm-781:before{content:"\f056"}.wi-owm-800:before{content:"\f00d"}.wi-owm-801:before{content:"\f041"}.wi-owm-802:before{content:"\f041"}.wi-owm-803:before{content:"\f013"}.wi-owm-804:before{content:"\f013"}.wi-owm-900:before{content:"\f056"}.wi-owm-901:before{content:"\f01d"}.wi-owm-902:before{content:"\f073"}.wi-owm-903:before{content:"\f076"}.wi-owm-904:before{content:"\f072"}.wi-owm-905:before{content:"\f021"}.wi-owm-906:before{content:"\f015"}.wi-owm-957:before{content:"\f050"}.wi-owm-day-200:before{content:"\f010"}.wi-owm-day-201:before{content:"\f010"}.wi-owm-day-202:before{content:"\f010"}.wi-owm-day-210:before{content:"\f005"}.wi-owm-day-211:before{content:"\f005"}.wi-owm-day-212:before{content:"\f005"}.wi-owm-day-221:before{content:"\f005"}.wi-owm-day-230:before{content:"\f010"}.wi-owm-day-231:before{content:"\f010"}.wi-owm-day-232:before{content:"\f010"}.wi-owm-day-300:before{content:"\f00b"}.wi-owm-day-301:before{content:"\f00b"}.wi-owm-day-302:before{content:"\f008"}.wi-owm-day-310:before{content:"\f008"}.wi-owm-day-311:before{content:"\f008"}.wi-owm-day-312:before{content:"\f008"}.wi-owm-day-313:before{content:"\f008"}.wi-owm-day-314:before{content:"\f008"}.wi-owm-day-321:before{content:"\f00b"}.wi-owm-day-500:before{content:"\f00b"}.wi-owm-day-501:before{content:"\f008"}.wi-owm-day-502:before{content:"\f008"}.wi-owm-day-503:before{content:"\f008"}.wi-owm-day-504:before{content:"\f008"}.wi-owm-day-511:before{content:"\f006"}.wi-owm-day-520:before{content:"\f009"}.wi-owm-day-521:before{content:"\f009"}.wi-owm-day-522:before{content:"\f009"}.wi-owm-day-531:before{content:"\f00e"}.wi-owm-day-600:before{content:"\f00a"}.wi-owm-day-601:before{content:"\f0b2"}.wi-owm-day-602:before{content:"\f00a"}.wi-owm-day-611:before{content:"\f006"}.wi-owm-day-612:before{content:"\f006"}.wi-owm-day-615:before{content:"\f006"}.wi-owm-day-616:before{content:"\f006"}.wi-owm-day-620:before{content:"\f006"}.wi-owm-day-621:before{content:"\f00a"}.wi-owm-day-622:before{content:"\f00a"}.wi-owm-day-701:before{content:"\f003"}.wi-owm-day-711:before{content:"\f062"}.wi-owm-day-721:before{content:"\f0b6"}.wi-owm-day-731:before{content:"\f063"}.wi-owm-day-741:before{content:"\f003"}.wi-owm-day-761:before{content:"\f063"}.wi-owm-day-762:before{content:"\f063"}.wi-owm-day-781:before{content:"\f056"}.wi-owm-day-800:before{content:"\f00d"}.wi-owm-day-801:before{content:"\f002"}.wi-owm-day-802:before{content:"\f002"}.wi-owm-day-803:before{content:"\f013"}.wi-owm-day-804:before{content:"\f013"}.wi-owm-day-900:before{content:"\f056"}.wi-owm-day-902:before{content:"\f073"}.wi-owm-day-903:before{content:"\f076"}.wi-owm-day-904:before{content:"\f072"}.wi-owm-day-906:before{content:"\f004"}.wi-owm-day-957:before{content:"\f050"}.wi-owm-night-200:before{content:"\f02d"}.wi-owm-night-201:before{content:"\f02d"}.wi-owm-night-202:before{content:"\f02d"}.wi-owm-night-210:before{content:"\f025"}.wi-owm-night-211:before{content:"\f025"}.wi-owm-night-212:before{content:"\f025"}.wi-owm-night-221:before{content:"\f025"}.wi-owm-night-230:before{content:"\f02d"}.wi-owm-night-231:before{content:"\f02d"}.wi-owm-night-232:before{content:"\f02d"}.wi-owm-night-300:before{content:"\f02b"}.wi-owm-night-301:before{content:"\f02b"}.wi-owm-night-302:before{content:"\f028"}.wi-owm-night-310:before{content:"\f028"}.wi-owm-night-311:before{content:"\f028"}.wi-owm-night-312:before{content:"\f028"}.wi-owm-night-313:before{content:"\f028"}.wi-owm-night-314:before{content:"\f028"}.wi-owm-night-321:before{content:"\f02b"}.wi-owm-night-500:before{content:"\f02b"}.wi-owm-night-501:before{content:"\f028"}.wi-owm-night-502:before{content:"\f028"}.wi-owm-night-503:before{content:"\f028"}.wi-owm-night-504:before{content:"\f028"}.wi-owm-night-511:before{content:"\f026"}.wi-owm-night-520:before{content:"\f029"}.wi-owm-night-521:before{content:"\f029"}.wi-owm-night-522:before{content:"\f029"}.wi-owm-night-531:before{content:"\f02c"}.wi-owm-night-600:before{content:"\f02a"}.wi-owm-night-601:before{content:"\f0b4"}.wi-owm-night-602:before{content:"\f02a"}.wi-owm-night-611:before{content:"\f026"}.wi-owm-night-612:before{content:"\f026"}.wi-owm-night-615:before{content:"\f026"}.wi-owm-night-616:before{content:"\f026"}.wi-owm-night-620:before{content:"\f026"}.wi-owm-night-621:before{content:"\f02a"}.wi-owm-night-622:before{content:"\f02a"}.wi-owm-night-701:before{content:"\f04a"}.wi-owm-night-711:before{content:"\f062"}.wi-owm-night-721:before{content:"\f0b6"}.wi-owm-night-731:before{content:"\f063"}.wi-owm-night-741:before{content:"\f04a"}.wi-owm-night-761:before{content:"\f063"}.wi-owm-night-762:before{content:"\f063"}.wi-owm-night-781:before{content:"\f056"}.wi-owm-night-800:before{content:"\f02e"}.wi-owm-night-801:before{content:"\f081"}.wi-owm-night-802:before{content:"\f086"}.wi-owm-night-803:before{content:"\f013"}.wi-owm-night-804:before{content:"\f013"}.wi-owm-night-900:before{content:"\f056"}.wi-owm-night-902:before{content:"\f073"}.wi-owm-night-903:before{content:"\f076"}.wi-owm-night-904:before{content:"\f072"}.wi-owm-night-906:before{content:"\f024"}.wi-owm-night-957:before{content:"\f050"}.wi-wu-chanceflurries:before{content:"\f064"}.wi-wu-chancerain:before{content:"\f019"}.wi-wu-chancesleat:before{content:"\f0b5"}.wi-wu-chancesnow:before{content:"\f01b"}.wi-wu-chancetstorms:before{content:"\f01e"}.wi-wu-clear:before{content:"\f00d"}.wi-wu-cloudy:before{content:"\f002"}.wi-wu-flurries:before{content:"\f064"}.wi-wu-hazy:before{content:"\f0b6"}.wi-wu-mostlycloudy:before{content:"\f002"}.wi-wu-mostlysunny:before{content:"\f00d"}.wi-wu-partlycloudy:before{content:"\f002"}.wi-wu-partlysunny:before{content:"\f00d"}.wi-wu-rain:before{content:"\f01a"}.wi-wu-sleat:before{content:"\f0b5"}.wi-wu-snow:before{content:"\f01b"}.wi-wu-sunny:before{content:"\f00d"}.wi-wu-tstorms:before{content:"\f01e"}.wi-wu-unknown:before{content:"\f00d"}/*# sourceMappingURL=weather-icons.min.css.map */
\ No newline at end of file
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..a7fc6fb
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,25 @@
+{
+ "compilerOptions": {
+ "target": "ES2020",
+ "useDefineForClassFields": true,
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
+ "module": "ESNext",
+ "skipLibCheck": true,
+
+ /* Bundler mode */
+ "moduleResolution": "bundler",
+ "allowImportingTsExtensions": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+
+ /* Linting */
+ "strict": true,
+ "noUnusedLocals": true,
+ "noUnusedParameters": true,
+ "noFallthroughCasesInSwitch": true
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
+}
diff --git a/tsconfig.node.json b/tsconfig.node.json
new file mode 100644
index 0000000..42872c5
--- /dev/null
+++ b/tsconfig.node.json
@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+ "composite": true,
+ "skipLibCheck": true,
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
+}
diff --git a/vite.config.ts b/vite.config.ts
new file mode 100644
index 0000000..f8a7da7
--- /dev/null
+++ b/vite.config.ts
@@ -0,0 +1,8 @@
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import macrosPlugin from 'vite-plugin-babel-macros';
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [react(), macrosPlugin()],
+});