commit fb0a8f19ad6523fc9d9474c0921f36679a8a93fb Author: Sam Rolfe Date: Wed Aug 26 13:19:24 2026 +1000 Initial commit: sam-iot-electronics diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad05e2a --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules/ +build/ +dist/ +coverage/ +.DS_Store +*.log +.env* +!.env.example \ No newline at end of file diff --git a/about.md b/about.md new file mode 100644 index 0000000..2a9aae1 --- /dev/null +++ b/about.md @@ -0,0 +1,38 @@ +#Iot +Iot projects +- AI voice +- Home Assistant home automation +- Lights Zibee Bulbs Main +- Lights ESP32 LED PIR Radar +- Lights Garden Solar +- Cameras Garden +- Garden Srinkler System +- Fish Feeder +- Lizard Feeder +- Front Door +- Switches +- RFID On Off +- Snapcast Rasperry Pi Players +- Rasperry Pi Smart TV's +- Home Box Inventory RFID +- Tree Classification RFID Wiki + +#Electronics +- Yogurt Maker +- Daily Calendar +- 3 Button Remote Lights +- Light Control System +- 240v step down switch +- Light Bar +- Horn +- Kill Switch +- Tracking Vehicles +#In Progress +- 240v to 12v Sensor lights +- Car Cameras +- Automated Garden Lights +- Proximity Unlock +- Face ID front Door +- Dog Feeder +- Dog Water +- Dog Door diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..7a1da85 --- /dev/null +++ b/bun.lock @@ -0,0 +1,367 @@ +{ + "lockfileVersion": 1, + "configVersion": 1, + "workspaces": { + "": { + "name": "iot-electronics-showcase", + "dependencies": { + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.13.2", + "vite": "^6.2.0", + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "tailwindcss": "^4.1.14", + "typescript": "~5.8.2", + }, + }, + }, + "packages": { + "@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="], + + "@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="], + + "@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/traverse": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="], + + "@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="], + + "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="], + + "@babel/helper-globals": ["@babel/helper-globals@7.28.0", "", {}, "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw=="], + + "@babel/helper-module-imports": ["@babel/helper-module-imports@7.28.6", "", { "dependencies": { "@babel/traverse": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw=="], + + "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.6", "", { "dependencies": { "@babel/helper-module-imports": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", "@babel/traverse": "^7.28.6" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA=="], + + "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.28.6", "", {}, "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug=="], + + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="], + + "@babel/helpers": ["@babel/helpers@7.29.2", "", { "dependencies": { "@babel/template": "^7.28.6", "@babel/types": "^7.29.0" } }, "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + + "@babel/plugin-transform-react-jsx-self": ["@babel/plugin-transform-react-jsx-self@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw=="], + + "@babel/plugin-transform-react-jsx-source": ["@babel/plugin-transform-react-jsx-source@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw=="], + + "@babel/template": ["@babel/template@7.28.6", "", { "dependencies": { "@babel/code-frame": "^7.28.6", "@babel/parser": "^7.28.6", "@babel/types": "^7.28.6" } }, "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ=="], + + "@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="], + + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="], + + "@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="], + + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="], + + "@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="], + + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="], + + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="], + + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="], + + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="], + + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="], + + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="], + + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="], + + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="], + + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="], + + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="], + + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="], + + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="], + + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="], + + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="], + + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="], + + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="], + + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="], + + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="], + + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="], + + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="], + + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="], + + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="], + + "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], + + "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], + + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + + "@rolldown/pluginutils": ["@rolldown/pluginutils@1.0.0-rc.3", "", {}, "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q=="], + + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.2", "", { "os": "android", "cpu": "arm" }, "sha512-dnlp69efPPg6Uaw2dVqzWRfAWRnYVb1XJ8CyyhIbZeaq4CA5/mLeZ1IEt9QqQxmbdvagjLIm2ZL8BxXv5lH4Yw=="], + + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.2", "", { "os": "android", "cpu": "arm64" }, "sha512-OqZTwDRDchGRHHm/hwLOL7uVPB9aUvI0am/eQuWMNyFHf5PSEQmyEeYYheA0EPPKUO/l0uigCp+iaTjoLjVoHg=="], + + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-UwRE7CGpvSVEQS8gUMBe1uADWjNnVgP3Iusyda1nSRwNDCsRjnGc7w6El6WLQsXmZTbLZx9cecegumcitNfpmA=="], + + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-gjEtURKLCC5VXm1I+2i1u9OhxFsKAQJKTVB8WvDAHF+oZlq0GTVFOlTlO1q3AlCTE/DF32c16ESvfgqR7343/g=="], + + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.2", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-Bcl6CYDeAgE70cqZaMojOi/eK63h5Me97ZqAQoh77VPjMysA/4ORQBRGo3rRy45x4MzVlU9uZxs8Uwy7ZaKnBw=="], + + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-LU+TPda3mAE2QB0/Hp5VyeKJivpC6+tlOXd1VMoXV/YFMvk/MNk5iXeBfB4MQGRWyOYVJ01625vjkr0Az98OJQ=="], + + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-2QxQrM+KQ7DAW4o22j+XZ6RKdxjLD7BOWTP0Bv0tmjdyhXSsr2Ul1oJDQqh9Zf5qOwTuTc7Ek83mOFaKnodPjg=="], + + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.2", "", { "os": "linux", "cpu": "arm" }, "sha512-TbziEu2DVsTEOPif2mKWkMeDMLoYjx95oESa9fkQQK7r/Orta0gnkcDpzwufEcAO2BLBsD7mZkXGFqEdMRRwfw=="], + + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-bO/rVDiDUuM2YfuCUwZ1t1cP+/yqjqz+Xf2VtkdppefuOFS2OSeAfgafaHNkFn0t02hEyXngZkxtGqXcXwO8Rg=="], + + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hr26p7e93Rl0Za+JwW7EAnwAvKkehh12BU1Llm9Ykiibg4uIr2rbpxG9WCf56GuvidlTG9KiiQT/TXT1yAWxTA=="], + + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-pOjB/uSIyDt+ow3k/RcLvUAOGpysT2phDn7TTUB3n75SlIgZzM6NKAqlErPhoFU+npgY3/n+2HYIQVbF70P9/A=="], + + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-2/w+q8jszv9Ww1c+6uJT3OwqhdmGP2/4T17cu8WuwyUuuaCDDJ2ojdyYwZzCxx0GcsZBhzi3HmH+J5pZNXnd+Q=="], + + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-11+aL5vKheYgczxtPVVRhdptAM2H7fcDR5Gw4/bTcteuZBlH4oP9f5s9zYO9aGZvoGeBpqXI/9TZZihZ609wKw=="], + + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.2", "", { "os": "linux", "cpu": "ppc64" }, "sha512-i16fokAGK46IVZuV8LIIwMdtqhin9hfYkCh8pf8iC3QU3LpwL+1FSFGej+O7l3E/AoknL6Dclh2oTdnRMpTzFQ=="], + + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-49FkKS6RGQoriDSK/6E2GkAsAuU5kETFCh7pG4yD/ylj9rKhTmO3elsnmBvRD4PgJPds5W2PkhC82aVwmUcJ7A=="], + + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.2", "", { "os": "linux", "cpu": "none" }, "sha512-mjYNkHPfGpUR00DuM1ZZIgs64Hpf4bWcz9Z41+4Q+pgDx73UwWdAYyf6EG/lRFldmdHHzgrYyge5akFUW0D3mQ=="], + + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.2", "", { "os": "linux", "cpu": "s390x" }, "sha512-ALyvJz965BQk8E9Al/JDKKDLH2kfKFLTGMlgkAbbYtZuJt9LU8DW3ZoDMCtQpXAltZxwBHevXz5u+gf0yA0YoA=="], + + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-UQjrkIdWrKI626Du8lCQ6MJp/6V1LAo2bOK9OTu4mSn8GGXIkPXk/Vsp4bLHCd9Z9Iz2OTEaokUE90VweJgIYQ=="], + + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.2", "", { "os": "linux", "cpu": "x64" }, "sha512-bTsRGj6VlSdn/XD4CGyzMnzaBs9bsRxy79eTqTCBsA8TMIEky7qg48aPkvJvFe1HyzQ5oMZdg7AnVlWQSKLTnw=="], + + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.2", "", { "os": "openbsd", "cpu": "x64" }, "sha512-6d4Z3534xitaA1FcMWP7mQPq5zGwBmGbhphh2DwaA1aNIXUu3KTOfwrWpbwI4/Gr0uANo7NTtaykFyO2hPuFLg=="], + + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.2", "", { "os": "none", "cpu": "arm64" }, "sha512-NetAg5iO2uN7eB8zE5qrZ3CSil+7IJt4WDFLcC75Ymywq1VZVD6qJ6EvNLjZ3rEm6gB7XW5JdT60c6MN35Z85Q=="], + + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-NCYhOotpgWZ5kdxCZsv6Iudx0wX8980Q/oW4pNFNihpBKsDbEA1zpkfxJGC0yugsUuyDZ7gL37dbzwhR0VI7pQ=="], + + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.2", "", { "os": "win32", "cpu": "ia32" }, "sha512-RXsaOqXxfoUBQoOgvmmijVxJnW2IGB0eoMO7F8FAjaj0UTywUO/luSqimWBJn04WNgUkeNhh7fs7pESXajWmkg=="], + + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-qdAzEULD+/hzObedtmV6iBpdL5TIbKVztGiK7O3/KYSf+HIzU257+MX1EXJcyIiDbMAqmbwaufcYPvyRryeZtA=="], + + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.2", "", { "os": "win32", "cpu": "x64" }, "sha512-Nd/SgG27WoA9e+/TdK74KnHz852TLa94ovOYySo/yMPuTmpckK/jIF2jSwS3g7ELSKXK13/cVdmg1Z/DaCWKxA=="], + + "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], + + "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], + + "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu": "arm64" }, "sha512-e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="], + + "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="], + + "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="], + + "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="], + + "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="], + + "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="], + + "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="], + + "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-7Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="], + + "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-2wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="], + + "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.4", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-FQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="], + + "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="], + + "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4", "", { "os": "win32", "cpu": "x64" }, "sha512-ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="], + + "@tailwindcss/vite": ["@tailwindcss/vite@4.2.4", "", { "dependencies": { "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "tailwindcss": "4.2.4" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-pCvohwOCspk3ZFn6eJzrrX3g4n2JY73H6MmYC87XfGPyTty4YsCjYTMArRZm/zOI8dIt3+EcrLHAFPe5A4bgtw=="], + + "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], + + "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="], + + "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="], + + "@types/babel__traverse": ["@types/babel__traverse@7.28.0", "", { "dependencies": { "@babel/types": "^7.28.2" } }, "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q=="], + + "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], + + "@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="], + + "@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="], + + "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], + + "@vitejs/plugin-react": ["@vitejs/plugin-react@5.2.0", "", { "dependencies": { "@babel/core": "^7.29.0", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-rc.3", "@types/babel__core": "^7.20.5", "react-refresh": "^0.18.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw=="], + + "baseline-browser-mapping": ["baseline-browser-mapping@2.10.23", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-xwVXGqevyKPsiuQdLj+dZMVjidjJV508TBqexND5HrF89cGdCYCJFB3qhcxRHSeMctdCfbR1jrxBajhDy7o29g=="], + + "browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="], + + "caniuse-lite": ["caniuse-lite@1.0.30001791", "", {}, "sha512-yk0l/YSrOnFZk3UROpDLQD9+kC1l4meK/wed583AXrzoarMGJcbRi2Q4RaUYbKxYAsZ8sWmaSa/DsLmdBeI1vQ=="], + + "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="], + + "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], + + "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], + + "debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="], + + "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], + + "electron-to-chromium": ["electron-to-chromium@1.5.344", "", {}, "sha512-4MxfbmNDm+KPh066EZy+eUnkcDPcZ35wNmOWzFuh/ijvHsve6kbLTLURy88uCNK5FbpN+yk2nQY6BYh1GEt+wg=="], + + "enhanced-resolve": ["enhanced-resolve@5.21.0", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA=="], + + "esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], + + "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="], + + "fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="], + + "framer-motion": ["framer-motion@12.38.0", "", { "dependencies": { "motion-dom": "^12.38.0", "motion-utils": "^12.36.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g=="], + + "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="], + + "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="], + + "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], + + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], + + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], + + "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], + + "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + + "lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="], + + "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="], + + "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="], + + "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="], + + "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="], + + "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="], + + "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="], + + "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="], + + "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="], + + "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="], + + "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="], + + "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], + + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], + + "lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="], + + "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + + "motion": ["motion@12.38.0", "", { "dependencies": { "framer-motion": "^12.38.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-uYfXzeHlgThchzwz5Te47dlv5JOUC7OB4rjJ/7XTUgtBZD8CchMN8qEJ4ZVsUmTyYA44zjV0fBwsiktRuFnn+w=="], + + "motion-dom": ["motion-dom@12.38.0", "", { "dependencies": { "motion-utils": "^12.36.0" } }, "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA=="], + + "motion-utils": ["motion-utils@12.36.0", "", {}, "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg=="], + + "ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="], + + "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], + + "node-releases": ["node-releases@2.0.38", "", {}, "sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw=="], + + "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], + + "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], + + "postcss": ["postcss@8.5.12", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA=="], + + "react": ["react@19.2.5", "", {}, "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA=="], + + "react-dom": ["react-dom@19.2.5", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.5" } }, "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag=="], + + "react-refresh": ["react-refresh@0.18.0", "", {}, "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw=="], + + "react-router": ["react-router@7.14.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-yCqNne6I8IB6rVCH7XUvlBK7/QKyqypBFGv+8dj4QBFJiiRX+FG7/nkdAvGElyvVZ/HQP5N19wzteuTARXi5Gw=="], + + "react-router-dom": ["react-router-dom@7.14.2", "", { "dependencies": { "react-router": "7.14.2" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" } }, "sha512-YZcM5ES8jJSM+KrJ9BdvHHqlnGTg5tH3sC5ChFRj4inosKctdyzBDhOyyHdGk597q2OT6NTrCA1OvB/YDwfekQ=="], + + "rollup": ["rollup@4.60.2", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.2", "@rollup/rollup-android-arm64": "4.60.2", "@rollup/rollup-darwin-arm64": "4.60.2", "@rollup/rollup-darwin-x64": "4.60.2", "@rollup/rollup-freebsd-arm64": "4.60.2", "@rollup/rollup-freebsd-x64": "4.60.2", "@rollup/rollup-linux-arm-gnueabihf": "4.60.2", "@rollup/rollup-linux-arm-musleabihf": "4.60.2", "@rollup/rollup-linux-arm64-gnu": "4.60.2", "@rollup/rollup-linux-arm64-musl": "4.60.2", "@rollup/rollup-linux-loong64-gnu": "4.60.2", "@rollup/rollup-linux-loong64-musl": "4.60.2", "@rollup/rollup-linux-ppc64-gnu": "4.60.2", "@rollup/rollup-linux-ppc64-musl": "4.60.2", "@rollup/rollup-linux-riscv64-gnu": "4.60.2", "@rollup/rollup-linux-riscv64-musl": "4.60.2", "@rollup/rollup-linux-s390x-gnu": "4.60.2", "@rollup/rollup-linux-x64-gnu": "4.60.2", "@rollup/rollup-linux-x64-musl": "4.60.2", "@rollup/rollup-openbsd-x64": "4.60.2", "@rollup/rollup-openharmony-arm64": "4.60.2", "@rollup/rollup-win32-arm64-msvc": "4.60.2", "@rollup/rollup-win32-ia32-msvc": "4.60.2", "@rollup/rollup-win32-x64-gnu": "4.60.2", "@rollup/rollup-win32-x64-msvc": "4.60.2", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ=="], + + "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], + + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], + + "set-cookie-parser": ["set-cookie-parser@2.7.2", "", {}, "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw=="], + + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], + + "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="], + + "undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="], + + "update-browserslist-db": ["update-browserslist-db@1.2.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w=="], + + "vite": ["vite@6.4.2", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ=="], + + "yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + } +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..cd42558 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Integrated IoT & Electronics Showcase + + +
+ + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..502a2f4 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "name": "iot-electronics-showcase", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite --port=3002 --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview", + "clean": "rm -rf dist", + "lint": "tsc --noEmit" + }, + "dependencies": { + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4", + "lucide-react": "^0.546.0", + "motion": "^12.23.24", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^7.13.2", + "vite": "^6.2.0" + }, + "devDependencies": { + "@types/node": "^25.6.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "tailwindcss": "^4.1.14", + "typescript": "~5.8.2" + } +} \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..26a1546 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,29 @@ +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Layout from "./components/Layout"; +import Home from "./pages/Home"; +import IoT from "./pages/IoT"; +import Electronics from "./pages/Electronics"; +import InProgress from "./pages/InProgress"; +import ProjectPage from "./pages/ProjectPage"; +import AiChat from "./pages/AiChat"; +import Contact from "./pages/Contact"; + +export default function App() { + return ( + + + + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); +} \ No newline at end of file diff --git a/src/components/AiChatBox.tsx b/src/components/AiChatBox.tsx new file mode 100644 index 0000000..f6a0a02 --- /dev/null +++ b/src/components/AiChatBox.tsx @@ -0,0 +1,72 @@ +import React, { useState } from "react"; +import { Send, Bot, Sparkles } from "lucide-react"; + +export default function AiChatBox() { + const [input, setInput] = useState(""); + const [messages, setMessages] = useState<{ role: string; text: string }[]>([ + { role: "ai", text: "Welcome to the IoT & Electronics AI assistant. I can help with project ideas, troubleshooting, and technical questions." }, + ]); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + setMessages([...messages, { role: "user", text: input }]); + setInput(""); + setTimeout(() => { + setMessages((prev) => [...prev, { + role: "ai", + text: "I'm currently running in static mode. Once my local AI backend is deployed, I'll provide detailed technical assistance for your IoT and electronics projects.", + }]); + }, 800); + }; + + return ( +
+
+
+ +
+
+
IoT Assistant
+
powered by local LLM
+
+ +
+ +
+ {messages.map((msg, i) => ( +
+ {msg.role === "ai" && ( +
+ +
+ )} +
+ {msg.text} +
+
+ ))} +
+ +
+
+ setInput(e.target.value)} + placeholder="Ask about IoT, electronics, troubleshooting..." + className="flex-1 bg-surface-container-highest text-on-surface placeholder-on-surface-variant/50 rounded-xl px-4 py-3 text-sm border border-outline-variant/50 focus:outline-none focus:border-primary/50 focus:glow-primary transition-all" + /> + +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/ContentBlocks.tsx b/src/components/ContentBlocks.tsx new file mode 100644 index 0000000..fc3e12b --- /dev/null +++ b/src/components/ContentBlocks.tsx @@ -0,0 +1,180 @@ +import React from "react"; +import { CheckSquare, Square, ExternalLink } from "lucide-react"; +import type { ContentBlock } from "../data/projectsData"; + +interface ContentBlocksProps { + blocks: ContentBlock[]; +} + +export default function ContentBlocks({ blocks }: ContentBlocksProps) { + return ( +
+ {blocks.map((block, index) => ( +
+ {block.title && ( +

{block.title}

+ )} + +
+ ))} +
+ ); +} + +function BlockRenderer({ block }: { block: ContentBlock }) { + switch (block.type) { + case "text": + return ; + case "specs": + return ; + case "bom": + return ; + case "code": + return ; + case "gallery": + return ; + case "stack": + return ; + case "steps": + return ; + case "links": + return ; + default: + return null; + } +} + +function TextBlock({ paragraphs }: { paragraphs: string[] }) { + return ( +
+ {paragraphs.map((p, i) => ( +

{p}

+ ))} +
+ ); +} + +function SpecsBlock({ specs }: { specs: NonNullable }) { + return ( +
+ {specs.map((spec, i) => ( +
+
{spec.label}
+
{spec.value}
+
+ ))} +
+ ); +} + +function BomBlock({ bom }: { bom: NonNullable }) { + return ( +
+ + + + + + + + + + {bom.map((item, i) => ( + + + + + + ))} + +
PartQtySpecs
{item.part}{item.qty}{item.specs || "—"}
+
+ ); +} + +function CodeBlock({ code }: { code: ContentBlock["code"] }) { + if (!code) return null; + return ( +
+
+ {code.filename} + {code.language} +
+
+        {code.code}
+      
+
+ ); +} + +function GalleryBlock({ gallery }: { gallery: NonNullable }) { + return ( +
+ {gallery.map((item, i) => ( +
+
+
+
{item.title}
+
+
+ ))} +
+ ); +} + +function StackBlock({ stack }: { stack: NonNullable }) { + return ( +
+ {stack.map((item, i) => ( +
+ {item.name} + {item.role} +
+ ))} +
+ ); +} + +function StepsBlock({ steps }: { steps: NonNullable }) { + return ( +
+ {steps.map((step, i) => ( +
+
+
{i + 1}
+ {i < steps.length - 1 &&
} +
+
+
{step.title}
+
{step.description}
+
+
+ ))} +
+ ); +} + +function LinksBlock({ links }: { links: NonNullable }) { + return ( + + ); +} \ No newline at end of file diff --git a/src/components/ExternalLinkCard.tsx b/src/components/ExternalLinkCard.tsx new file mode 100644 index 0000000..f18a18d --- /dev/null +++ b/src/components/ExternalLinkCard.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { Globe, ArrowUpRight } from "lucide-react"; + +interface ExternalLinkCardProps { + title: string; + description: string; + url: string; +} + +export default function ExternalLinkCard({ title, description, url }: ExternalLinkCardProps) { + return ( + +
+
+ +
+
+
+

{title}

+ +
+

{description}

+
+ + {url} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/HorizontalTabs.tsx b/src/components/HorizontalTabs.tsx new file mode 100644 index 0000000..1ccf858 --- /dev/null +++ b/src/components/HorizontalTabs.tsx @@ -0,0 +1,37 @@ +import React from "react"; +import { motion } from "motion/react"; + +export interface Tab { + id: string; + label: string; + icon: string; +} + +interface HorizontalTabsProps { + tabs: Tab[]; + activeTab: string; + onTabChange: (tabId: string) => void; +} + +export default function HorizontalTabs({ tabs, activeTab, onTabChange }: HorizontalTabsProps) { + return ( +
+
+ {tabs.map((tab) => ( + + ))} +
+
+
+ ); +} \ No newline at end of file diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..53af98b --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,238 @@ +import React, { useState } from "react"; +import { Link, useLocation } from "react-router-dom"; +import { Menu, X, Github, Linkedin, Twitter, ChevronDown, Globe, Briefcase, Server, Cpu, Compass } from "lucide-react"; +import { motion, AnimatePresence } from "motion/react"; +import { navItems } from "@/src/data/navItems"; + +const externalSites = [ + { url: "https://sam-developer.lab.audasmedia.com.au/", label: "Portfolio", icon: Briefcase }, + { url: "https://sam-devops.lab.audasmedia.com.au/", label: "DevOps Hub", icon: Server }, + { url: "https://sam-pursuits.lab.audasmedia.com.au/", label: "Pursuits", icon: Compass }, +]; + +export default function Layout({ children }: { children: React.ReactNode }) { + const location = useLocation(); + const [mobileOpen, setMobileOpen] = useState(false); + const [expandedAccordion, setExpandedAccordion] = useState( + location.pathname.startsWith("/iot") || location.pathname.startsWith("/electronics") || location.pathname.startsWith("/in-progress") + ? navItems.find((i) => location.pathname.startsWith(i.path) && i.path !== "/")?.path || null + : null + ); + + const handleNavClick = (item: typeof navItems[0]) => { + if (item.children) { + const isExpanded = expandedAccordion === item.path; + setExpandedAccordion(isExpanded ? null : item.path); + } + }; + + return ( +
+
+
+
+
I
+ IoT & Electronics +
+ +
+
+ + + {mobileOpen && ( + setMobileOpen(false)} + /> + )} + + + + {mobileOpen && ( + + setMobileOpen(false)} + /> + + {/* My Sites */} + + + )} + + + + +
+
+
+
+
{children}
+
+
+ ); +} + +function NavContent({ + location, + expandedAccordion, + setExpandedAccordion, + handleNavClick, + onItemClick, +}: { + location: ReturnType; + expandedAccordion: string | null; + setExpandedAccordion: (v: string | null) => void; + handleNavClick: (item: typeof navItems[0]) => void; + onItemClick?: () => void; +}) { + return ( + <> + {navItems.map((item) => { + const isActive = location.pathname === item.path; + const isChildActive = item.children?.some((c) => location.pathname === c.path); + const isExpanded = expandedAccordion === item.path; + + return ( +
+ { + if (item.children) { + e.preventDefault(); + handleNavClick(item); + } else if (onItemClick) { + onItemClick(); + } + }} + className={`relative group flex items-center gap-3 px-4 py-3 rounded-xl text-sm transition-all ${ + isActive || isChildActive + ? "bg-primary-container/15 text-primary font-medium shadow-glow" + : "text-on-surface-variant hover:text-primary hover:bg-surface-container-high" + }`} + > + + {item.label} + {item.children && ( + + )} + {(isActive || isChildActive) && ( + + )} + + + + {isExpanded && item.children && ( + +
+ {item.children.map((child) => { + const isChildActive = location.pathname === child.path; + return ( + + {child.label} + + ); + })} +
+
+ )} +
+
+ ); + })} + + ); +} diff --git a/src/components/MySites.tsx b/src/components/MySites.tsx new file mode 100644 index 0000000..942b554 --- /dev/null +++ b/src/components/MySites.tsx @@ -0,0 +1,53 @@ +import React from "react"; +import { Globe } from "lucide-react"; + +const externalSites = [ + { url: "https://sam-developer.home.lab/", label: "Portfolio" }, + { url: "https://sam-devops.home.lab/", label: "DevOps Hub" }, + { url: "https://sam-pursuits.home.lab/", label: "Pursuits" }, +]; + +export function MySitesMobile({ onItemClick }: { onItemClick?: () => void }) { + return ( +
+
+ My Sites +
+ {externalSites.map((site) => ( + + + {site.label} + + ))} +
+ ); +} + +export function MySitesDesktop() { + return ( +
+
+ My Sites +
+ {externalSites.map((site) => ( + + + {site.label} + + ))} +
+ ); +} diff --git a/src/components/PageHeader.tsx b/src/components/PageHeader.tsx new file mode 100644 index 0000000..a95b84d --- /dev/null +++ b/src/components/PageHeader.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +interface PageHeaderProps { + title: string; + description: string; + tag?: string; +} + +export default function PageHeader({ title, description, tag }: PageHeaderProps) { + return ( +
+ {tag && ( +
+ {tag} +
+ )} +

{title}

+

{description}

+
+ ); +} \ No newline at end of file diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx new file mode 100644 index 0000000..bebef8a --- /dev/null +++ b/src/components/ProjectCard.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { motion } from "motion/react"; +import { CircleDot, Circle, Clock, AlertCircle } from "lucide-react"; +import type { Project } from "../data/projectsData"; + +interface ProjectCardProps { + project: Project; + basePath: string; +} + +const statusConfig = { + online: { icon: , label: "Online", className: "bg-tertiary/15 text-tertiary" }, + offline: { icon: , label: "Offline", className: "bg-on-surface-variant/15 text-on-surface-variant" }, + "in-progress": { icon: , label: "In Progress", className: "bg-secondary/15 text-secondary" }, + planned: { icon: , label: "Planned", className: "bg-flamingo/15 text-flamingo" }, +}; + +export default function ProjectCard({ project, basePath }: ProjectCardProps) { + const status = statusConfig[project.status]; + + return ( + + +
+
+
+ + {status.icon} + {status.label} + +
+
+
{project.category}
+
+
+ +
+

{project.title}

+

{project.description}

+ +
+ {project.tags.map((tag) => ( + {tag} + ))} +
+
+ + + ); +} \ No newline at end of file diff --git a/src/data/navItems.ts b/src/data/navItems.ts new file mode 100644 index 0000000..b781948 --- /dev/null +++ b/src/data/navItems.ts @@ -0,0 +1,75 @@ +import { + LayoutDashboard, + Wifi, + Cpu, + Wrench, + Bot, + Mail, +} from "lucide-react"; + +export interface NavItem { + path: string; + icon: typeof LayoutDashboard; + label: string; + children?: { path: string; label: string }[]; +} + +export const navItems: NavItem[] = [ + { path: "/", icon: LayoutDashboard, label: "Home" }, + { + path: "/iot", + icon: Wifi, + label: "IoT Projects", + children: [ + { path: "/iot/ai-voice", label: "AI Voice" }, + { path: "/iot/home-assistant", label: "Home Assistant" }, + { path: "/iot/zigbee-lights", label: "Zigbee Lights" }, + { path: "/iot/esp32-led", label: "ESP32 LED PIR" }, + { path: "/iot/garden-solar", label: "Garden Solar" }, + { path: "/iot/garden-cameras", label: "Garden Cameras" }, + { path: "/iot/sprinkler", label: "Sprinkler System" }, + { path: "/iot/fish-feeder", label: "Fish Feeder" }, + { path: "/iot/lizard-feeder", label: "Lizard Feeder" }, + { path: "/iot/front-door", label: "Front Door" }, + { path: "/iot/switches", label: "Switches" }, + { path: "/iot/rfid", label: "RFID On/Off" }, + { path: "/iot/snapcast", label: "Snapcast Players" }, + { path: "/iot/smart-tvs", label: "Smart TVs" }, + { path: "/iot/box-inventory", label: "Box Inventory" }, + { path: "/iot/tree-wiki", label: "Tree Classification" }, + ], + }, + { + path: "/electronics", + icon: Cpu, + label: "Electronics", + children: [ + { path: "/electronics/yogurt-maker", label: "Yogurt Maker" }, + { path: "/electronics/daily-calendar", label: "Daily Calendar" }, + { path: "/electronics/remote-lights", label: "3-Button Remote" }, + { path: "/electronics/light-control", label: "Light Control" }, + { path: "/electronics/step-down", label: "240V Step-Down" }, + { path: "/electronics/light-bar", label: "Light Bar" }, + { path: "/electronics/horn", label: "Horn" }, + { path: "/electronics/kill-switch", label: "Kill Switch" }, + { path: "/electronics/vehicle-tracking", label: "Vehicle Tracking" }, + ], + }, + { + path: "/in-progress", + icon: Wrench, + label: "In Progress", + children: [ + { path: "/in-progress/sensor-lights", label: "240V Sensor Lights" }, + { path: "/in-progress/car-cameras", label: "Car Cameras" }, + { path: "/in-progress/auto-garden", label: "Automated Garden Lights" }, + { path: "/in-progress/proximity-unlock", label: "Proximity Unlock" }, + { path: "/in-progress/face-id", label: "Face ID Door" }, + { path: "/in-progress/dog-feeder", label: "Dog Feeder" }, + { path: "/in-progress/dog-water", label: "Dog Water" }, + { path: "/in-progress/dog-door", label: "Dog Door" }, + ], + }, + { path: "/ai", icon: Bot, label: "AI" }, + { path: "/contact", icon: Mail, label: "Contact" }, +]; \ No newline at end of file diff --git a/src/data/projectsData.ts b/src/data/projectsData.ts new file mode 100644 index 0000000..85ea2ff --- /dev/null +++ b/src/data/projectsData.ts @@ -0,0 +1,1443 @@ +import React from "react"; + +export type ContentBlockType = "text" | "specs" | "bom" | "code" | "gallery" | "steps" | "links" | "stack"; + +export interface SpecItem { + label: string; + value: string; +} + +export interface BomItem { + part: string; + qty: number; + specs?: string; +} + +export interface CodeSnippet { + language: string; + filename: string; + code: string; +} + +export interface GalleryItem { + title: string; + gradient: string; +} + +export interface StepItem { + title: string; + description: string; +} + +export interface LinkItem { + title: string; + url: string; + description: string; +} + +export interface StackItem { + name: string; + role: string; +} + +export interface ContentBlock { + type: ContentBlockType; + title?: string; + paragraphs?: string[]; + specs?: SpecItem[]; + bom?: BomItem[]; + code?: CodeSnippet; + gallery?: GalleryItem[]; + steps?: StepItem[]; + links?: LinkItem[]; + stack?: StackItem[]; +} + +export interface Section { + id: string; + title: string; + icon: string; + blocks: ContentBlock[]; +} + +export interface Project { + slug: string; + title: string; + description: string; + category: string; + status: "online" | "offline" | "in-progress" | "planned"; + tags: string[]; + gradient: string; + sections: Section[]; +} + +// Helper to create sections for a project +function createProjectSections(title: string, category: string, opts: { + overviewBlocks?: ContentBlock[]; + galleryGradients?: string[]; + bom?: BomItem[]; + code?: { language: string; filename: string; code: string }; + stack?: StackItem[]; + steps?: StepItem[]; + links?: LinkItem[]; +}): Section[] { + const sections: Section[] = []; + + // Overview + sections.push({ + id: "overview", + title: "Overview", + icon: "info", + blocks: opts.overviewBlocks || [ + { type: "text", paragraphs: [`${title} is a ${category.toLowerCase()} project designed for smart home integration.`] }, + ], + }); + + // Gallery + if (opts.galleryGradients) { + sections.push({ + id: "gallery", + title: "Gallery", + icon: "image", + blocks: [{ + type: "gallery", + gallery: opts.galleryGradients.map((g, i) => ({ title: `${title} - View ${i + 1}`, gradient: g })), + }], + }); + } + + // Hardware + if (opts.bom) { + sections.push({ + id: "hardware", + title: "Hardware", + icon: "cpu", + blocks: [ + { type: "text", title: "Bill of Materials", paragraphs: ["Components used in this build:"] }, + { type: "bom", bom: opts.bom }, + ], + }); + } + + // Software + if (opts.stack) { + sections.push({ + id: "software", + title: "Software", + icon: "code", + blocks: [ + { type: "text", title: "Tech Stack", paragraphs: ["Software and dependencies used:"] }, + { type: "stack", stack: opts.stack }, + ], + }); + } + + // Code + if (opts.code) { + sections.push({ + id: "code", + title: "Code", + icon: "terminal", + blocks: [{ type: "code", code: opts.code }], + }); + } + + // Guide + if (opts.steps) { + sections.push({ + id: "guide", + title: "User Guide", + icon: "book", + blocks: [ + { type: "text", title: "Setup Instructions", paragraphs: ["Follow these steps to set up and configure the project:"] }, + { type: "steps", steps: opts.steps }, + ], + }); + } + + // Links + if (opts.links) { + sections.push({ + id: "links", + title: "Links & Docs", + icon: "link", + blocks: [{ type: "links", links: opts.links }], + }); + } + + return sections; +} + +export const iotProjects: Project[] = [ + { + slug: "ai-voice", + title: "AI Voice", + description: "Local voice assistant using open-source LLMs with custom wake word detection.", + category: "Voice", + status: "online", + tags: ["AI", "Voice", "LLM", "Raspberry Pi"], + gradient: "from-primary/30 to-secondary/20", + sections: createProjectSections("AI Voice", "Voice", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "AI Voice is a fully local voice assistant that runs entirely on-device using open-source models. It uses Whisper for speech recognition, a quantized Llama model for natural language understanding, and Piper for text-to-speech synthesis.", + "The system features a custom-trained wake word detector using Edge Impulse, enabling hands-free activation without cloud dependencies.", + ]}, + { type: "specs", specs: [ + { label: "Platform", value: "Raspberry Pi 4 (8GB)" }, + { label: "Wake Word", value: "Custom trained (Edge Impulse)" }, + { label: "ASR", value: "OpenAI Whisper (base)" }, + { label: "LLM", value: "Llama 3.2 3B (Q4_K_M)" }, + { label: "TTS", value: "Piper TTS" }, + { label: "Latency", value: "~800ms end-to-end" }, + ]}, + ], + galleryGradients: ["from-primary/40 to-secondary/30", "from-secondary/40 to-tertiary/30", "from-tertiary/40 to-primary/30"], + bom: [ + { part: "Raspberry Pi 4", qty: 1, specs: "8GB RAM, 64GB SD" }, + { part: "ReSpeaker 4-Mic Array", qty: 1, specs: "USB, 3m range" }, + { part: "HiFiBerry Amp2", qty: 1, specs: "30W stereo" }, + { part: "Speaker", qty: 2, specs: "8 Ohm, 15W" }, + { part: "USB-C Power Supply", qty: 1, specs: "5V 3A" }, + ], + code: { + language: "python", + filename: "voice_assistant.py", + code: `#!/usr/bin/env python3 +import whisper +import llama_cpp +import pvporcupine + +# Load models +asr_model = whisper.load_model("base") +llm = llama_cpp.Llama(model_path="llama-3.2-3b.q4_k_m.gguf") +picovoice = pvporcupine.create(keyword_paths=["wake-word.ppn"]) + +print("AI Voice Assistant ready. Say the wake word...") +`, + }, + stack: [ + { name: "Python 3.11", role: "Runtime" }, + { name: "Whisper", role: "Speech-to-text" }, + { name: "Llama.cpp", role: "LLM inference" }, + { name: "Piper TTS", role: "Text-to-speech" }, + { name: "Edge Impulse", role: "Wake word" }, + { name: "MQTT", role: "Home integration" }, + ], + steps: [ + { title: "Flash Raspberry Pi OS", description: "Install Raspberry Pi OS Lite (64-bit) to a 64GB SD card." }, + { title: "Install Dependencies", description: "Run setup script to install Python, pip, and system dependencies." }, + { title: "Download Models", description: "Download Whisper base, Llama 3.2 3B Q4_K_M, and Piper voice model." }, + { title: "Configure Audio", description: "Set ReSpeaker as default input and HiFiBerry as output in ALSA." }, + { title: "Test Wake Word", description: "Verify wake word detection responds within 2 seconds." }, + { title: "Enable Home Assistant", description: "Configure MQTT topics for device control integration." }, + ], + links: [ + { title: "Whisper GitHub", url: "https://github.com/openai/whisper", description: "OpenAI Whisper speech recognition" }, + { title: "Llama.cpp", url: "https://github.com/ggerganov/llama.cpp", description: "C++ LLM inference engine" }, + { title: "Piper TTS", url: "https://github.com/rhasspy/piper", description: "Fast local text-to-speech" }, + ], + }), + }, + { + slug: "home-assistant", + title: "Home Assistant", + description: "Central automation hub managing all IoT devices with custom dashboards.", + category: "Automation", + status: "online", + tags: ["Automation", "Hub", "Dashboard", "MQTT"], + gradient: "from-secondary/30 to-primary/20", + sections: createProjectSections("Home Assistant", "Automation", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Home Assistant serves as the central nervous system for the entire smart home. It integrates 42 devices across Zigbee, WiFi, and ESPHome protocols, providing unified control and automation.", + "Custom dashboards display real-time status, energy consumption, and security feeds. Automations handle everything from morning routines to vacation mode.", + ]}, + { type: "specs", specs: [ + { label: "Version", value: "2025.4.2" }, + { label: "Devices", value: "42" }, + { label: "Automations", value: "18" }, + { label: "Integrations", value: "34" }, + { label: "Add-ons", value: "Frigate, ESPHome, Node-RED" }, + ]}, + ], + galleryGradients: ["from-secondary/40 to-primary/30", "from-primary/40 to-tertiary/30"], + bom: [ + { part: "Mini PC", qty: 1, specs: "Intel N100, 16GB RAM, 512GB NVMe" }, + { part: "Sonoff ZBDongle-E", qty: 1, specs: "Zigbee 3.0 Coordinator" }, + { part: "UniFi Switch", qty: 1, specs: "8-port PoE" }, + { part: "UPS", qty: 1, specs: "600VA, 30min backup" }, + ], + stack: [ + { name: "Home Assistant OS", role: "Platform" }, + { name: "ESPHome", role: "Firmware" }, + { name: "MQTT Broker", role: "Messaging" }, + { name: "Node-RED", role: "Flows" }, + { name: "InfluxDB", role: "Metrics" }, + { name: "Grafana", role: "Dashboards" }, + ], + steps: [ + { title: "Install HAOS", description: "Flash Home Assistant Operating System to Mini PC." }, + { title: "Configure Network", description: "Set static IP, VLANs, and integrate UniFi." }, + { title: "Add Zigbee Coordinator", description: "Plug in ZBDongle and pair devices." }, + { title: "Create Dashboards", description: "Build room-based dashboards with conditional cards." }, + { title: "Set up Automations", description: "Create blueprint-based automations for lighting, climate, and security." }, + ], + links: [ + { title: "Home Assistant Docs", url: "https://www.home-assistant.io/docs/", description: "Official documentation" }, + { title: "ESPHome", url: "https://esphome.io/", description: "ESP device firmware" }, + ], + }), + }, + { + slug: "zigbee-lights", + title: "Zigbee Bulbs Main", + description: "Mesh network of Zigbee 3.0 smart bulbs with color temperature control.", + category: "Lighting", + status: "online", + tags: ["Zigbee", "Lighting", "Mesh"], + gradient: "from-flamingo/30 to-secondary/20", + sections: createProjectSections("Zigbee Lights", "Lighting", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "24 Zigbee 3.0 smart bulbs form a self-healing mesh network throughout the house. Each bulb acts as a router, extending network coverage. Color temperature ranges from 2200K (warm candlelight) to 6500K (daylight).", + ]}, + { type: "specs", specs: [ + { label: "Protocol", value: "Zigbee 3.0" }, + { label: "Bulbs", value: "24" }, + { label: "Gateway", value: "Sonoff ZBDongle-E" }, + { label: "Color Temp", value: "2200K - 6500K" }, + { label: "Brightness", value: "806 lumens" }, + ]}, + ], + galleryGradients: ["from-flamingo/40 to-secondary/30", "from-secondary/40 to-primary/30"], + bom: [ + { part: "Philips Hue White Ambiance", qty: 12, specs: "E27, 806lm" }, + { part: "IKEA TRADFRI", qty: 8, specs: "GU10, 400lm" }, + { part: "Gledopto RGB+CCT", qty: 4, specs: "E14, 470lm" }, + { part: "Sonoff ZBDongle-E", qty: 1, specs: "Coordinator" }, + ], + steps: [ + { title: "Install Bulbs", description: "Replace existing bulbs with Zigbee variants." }, + { title: "Pair to Coordinator", description: "Put coordinator in pairing mode and add each bulb." }, + { title: "Create Groups", description: "Group bulbs by room for synchronized control." }, + { title: "Set Circadian Rhythm", description: "Configure adaptive lighting for automatic color temp adjustment." }, + ], + }), + }, + { + slug: "esp32-led", + title: "ESP32 LED PIR Radar", + description: "Motion-activated LED strips using ESP32 with PIR and radar sensors.", + category: "Lighting", + status: "online", + tags: ["ESP32", "LED", "Motion", "PIR"], + gradient: "from-tertiary/30 to-primary/20", + sections: createProjectSections("ESP32 LED PIR", "Lighting", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Under-cabinet and hallway LED strips controlled by ESP32 microcontrollers. Dual-sensor approach using PIR for initial detection and mmWave radar for sustained presence. Automatically adjusts brightness based on ambient light.", + ]}, + { type: "specs", specs: [ + { label: "MCU", value: "ESP32-S3" }, + { label: "LEDs", value: "WS2812B, 60/m" }, + { label: "PIR", value: "AM312" }, + { label: "Radar", value: "LD2410B" }, + { label: "Ambient", value: "BH1750" }, + ]}, + ], + galleryGradients: ["from-tertiary/40 to-primary/30", "from-primary/40 to-secondary/30"], + bom: [ + { part: "ESP32-S3 DevKit", qty: 6, specs: "N8R8" }, + { part: "WS2812B Strip", qty: 12, specs: "5m, 60/m, IP30" }, + { part: "LD2410B Radar", qty: 6, specs: "24GHz" }, + { part: "AM312 PIR", qty: 6, specs: "Mini" }, + { part: "BH1750", qty: 6, specs: "Light sensor" }, + { part: "5V PSU", qty: 6, specs: "10A" }, + ], + code: { + language: "cpp", + filename: "motion_led.yaml", + code: `esphome: + name: motion-led-kitchen + platform: ESP32 + board: esp32-s3-devkitc-1 + +sensor: + - platform: bh1750 + name: "Ambient Light" + address: 0x23 + update_interval: 1s + +binary_sensor: + - platform: gpio + pin: GPIO4 + name: "PIR Motion" + device_class: motion + - platform: ld2410 + has_target: "Radar Presence" + +light: + - platform: neopixelbus + name: "Cabinet LEDs" + type: GRB + pin: GPIO5 + num_leds: 120 + variant: WS2812X`, + }, + steps: [ + { title: "Flash ESPHome", description: "Configure and flash ESP32-S3 with ESPHome firmware." }, + { title: "Wire Sensors", description: "Connect PIR to GPIO4, radar via UART, and light sensor via I2C." }, + { title: "Install LEDs", description: "Mount LED strips under cabinets with diffusion channel." }, + { title: "Calibrate Sensitivity", description: "Tune PIR and radar sensitivity to avoid false triggers." }, + ], + }), + }, + { + slug: "garden-solar", + title: "Garden Solar Lights", + description: "Solar-powered garden lighting with dusk-to-dawn automation.", + category: "Lighting", + status: "online", + tags: ["Solar", "Outdoor", "Auto"], + gradient: "from-tertiary/30 to-secondary/20", + sections: createProjectSections("Garden Solar", "Lighting", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "16 solar garden lights with integrated battery, solar panel, and ESP8266 for smart control. Each unit charges during the day and automatically illuminates at dusk. Brightness adjusts based on moon phase and weather.", + ]}, + { type: "specs", specs: [ + { label: "Power", value: "Solar + 18650 Li-ion" }, + { label: "Count", value: "16 units" }, + { label: "LED", value: "SMD2835, 3W" }, + { label: "Control", value: "ESPHome + HA" }, + { label: "Runtime", value: "8-12 hours" }, + ]}, + ], + bom: [ + { part: "Solar Garden Light", qty: 16, specs: "3W LED, IP65" }, + { part: "18650 Battery", qty: 16, specs: "3000mAh" }, + { part: "ESP-01S", qty: 16, specs: "WiFi module" }, + { part: "TP4056", qty: 16, specs: "Charge controller" }, + ], + steps: [ + { title: "Modify Lights", description: "Open each light and add ESP-01S + battery monitoring." }, + { title: "Install in Garden", description: "Space lights 2m apart in full sun locations." }, + { title: "Add to Home Assistant", description: "Flash ESPHome config and add to HA dashboard." }, + { title: "Configure Automation", description: "Set dusk trigger and adaptive brightness." }, + ], + }), + }, + { + slug: "garden-cameras", + title: "Garden Cameras", + description: "IP camera network with AI person/vehicle classification.", + category: "Security", + status: "online", + tags: ["Camera", "AI", "Security", "Frigate"], + gradient: "from-primary/30 to-tertiary/20", + sections: createProjectSections("Garden Cameras", "Security", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "4-camera IP network monitoring garden, driveway, and perimeter. Frigate NVR provides 24/7 recording with AI object detection. Person and vehicle alerts sent to mobile via Home Assistant.", + ]}, + { type: "specs", specs: [ + { label: "Resolution", value: "4K (8MP)" }, + { label: "FPS", value: "15" }, + { label: "Storage", value: "NVR 4TB" }, + { label: "AI", value: "Frigate + OpenVINO" }, + { label: "Retention", value: "30 days" }, + ]}, + ], + galleryGradients: ["from-primary/40 to-tertiary/30", "from-tertiary/40 to-secondary/30"], + bom: [ + { part: "Reolink RLC-810A", qty: 2, specs: "4K, PoE" }, + { part: "Reolink RLC-511WA", qty: 2, specs: "5MP, WiFi" }, + { part: "NVR", qty: 1, specs: "i5, 4TB" }, + { part: "PoE Switch", qty: 1, specs: "4-port" }, + ], + stack: [ + { name: "Frigate", role: "NVR + AI" }, + { name: "OpenVINO", role: "Inference" }, + { name: "Coral TPU", role: "Accelerator" }, + { name: "Home Assistant", role: "Alerts" }, + ], + steps: [ + { title: "Mount Cameras", description: "Install at 3m height with weatherproof junction boxes." }, + { title: "Run Ethernet", description: "Pull Cat6 to each camera location." }, + { title: "Configure Frigate", description: "Set detection zones, object filters, and recording rules." }, + { title: "Set up Alerts", description: "Configure HA notifications for person/vehicle events." }, + ], + }), + }, + { + slug: "sprinkler", + title: "Garden Sprinkler System", + description: "Automated irrigation with soil moisture sensors and weather integration.", + category: "Garden", + status: "online", + tags: ["Irrigation", "Sensors", "Auto", "Garden"], + gradient: "from-secondary/30 to-tertiary/20", + sections: createProjectSections("Sprinkler System", "Garden", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "6-zone automated irrigation controlled by ESP32. Soil moisture sensors in each zone prevent overwatering. Weather API integration skips watering when rain is forecast.", + ]}, + { type: "specs", specs: [ + { label: "Zones", value: "6" }, + { label: "Sensors", value: "Capacitive soil moisture" }, + { label: "Valves", value: "24V AC solenoid" }, + { label: "Pump", value: "12V DC, 5L/min" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "Relay Module", qty: 1, specs: "8-channel, 24V" }, + { part: "Soil Sensor", qty: 6, specs: "Capacitive, V1.2" }, + { part: "Solenoid Valve", qty: 6, specs: "24V AC, 3/4\"" }, + { part: "Water Pump", qty: 1, specs: "12V DC, 5L/min" }, + ], + steps: [ + { title: "Install Sprinkler Lines", description: "Bury 19mm poly pipe to each zone." }, + { title: "Mount Valves", description: "Install valve manifold in weatherproof box." }, + { title: "Place Sensors", description: "Bury moisture sensors 10cm deep in each zone." }, + { title: "Configure Controller", description: "Set thresholds and schedule in ESPHome." }, + ], + }), + }, + { + slug: "fish-feeder", + title: "Fish Feeder", + description: "Automated aquarium fish feeder with portion control and scheduling.", + category: "Feeders", + status: "online", + tags: ["Feeder", "Aquarium", "Timer"], + gradient: "from-primary/30 to-flamingo/20", + sections: createProjectSections("Fish Feeder", "Feeders", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Automatic fish feeder with precise portion control. 200g hopper lasts 2 weeks. Battery backup ensures feeding continues during power outages.", + ]}, + { type: "specs", specs: [ + { label: "Capacity", value: "200g" }, + { label: "Schedule", value: "3x daily" }, + { label: "Portion", value: "0.5 - 5g" }, + { label: "Power", value: "USB + 18650 backup" }, + ]}, + ], + bom: [ + { part: "ESP32-C3", qty: 1, specs: "Mini" }, + { part: "28BYJ-48 Stepper", qty: 1, specs: "With driver" }, + { part: "Load Cell", qty: 1, specs: "1kg, HX711" }, + { part: "3D Printed Hopper", qty: 1, specs: "PETG, 200ml" }, + ], + steps: [ + { title: "Print Parts", description: "3D print hopper, auger, and mounting bracket." }, + { title: "Assemble Mechanism", description: "Install stepper motor and auger in hopper." }, + { title: "Calibrate Portions", description: "Use load cell to calibrate grams per rotation." }, + { title: "Set Schedule", description: "Configure feeding times and portions in ESPHome." }, + ], + }), + }, + { + slug: "lizard-feeder", + title: "Lizard Feeder", + description: "Precision feeder for reptiles with live insect dispensing.", + category: "Feeders", + status: "online", + tags: ["Feeder", "Reptile", "Temp"], + gradient: "from-flamingo/30 to-primary/20", + sections: createProjectSections("Lizard Feeder", "Feeders", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Automated reptile feeder designed for live insects. Vibration-based dispensing releases 2-3 crickets at a time. Temperature and humidity monitoring ensures optimal habitat conditions.", + ]}, + { type: "specs", specs: [ + { label: "Type", value: "Live insect" }, + { label: "Schedule", value: "2x daily" }, + { label: "Count", value: "2-3 per feed" }, + { label: "Sensors", value: "DHT22 + probe" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "Vibration Motor", qty: 1, specs: "3V, 10mm" }, + { part: "DHT22", qty: 1, specs: "Temp/Humidity" }, + { part: "DS18B20", qty: 1, specs: "Waterproof probe" }, + ], + steps: [ + { title: "Build Hopper", description: "Create inclined hopper with vibration plate." }, + { title: "Install Sensors", description: "Mount DHT22 in habitat, probe in basking spot." }, + { title: "Calibrate Motor", description: "Find vibration duration for consistent dispensing." }, + { title: "Set Schedule", description: "Program 2 daily feeds with habitat alerts." }, + ], + }), + }, + { + slug: "front-door", + title: "Front Door", + description: "Smart lock with NFC, PIN, and fingerprint access.", + category: "Access", + status: "online", + tags: ["Lock", "NFC", "Fingerprint", "Security"], + gradient: "from-secondary/30 to-primary/20", + sections: createProjectSections("Front Door", "Access", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Multi-modal smart lock supporting NFC tags, PIN codes, and fingerprint biometrics. Doorbell camera with two-way audio. All access events logged to Home Assistant with photo capture.", + ]}, + { type: "specs", specs: [ + { label: "Methods", value: "NFC / PIN / Fingerprint" }, + { label: "Battery", value: "8x AA, 6 months" }, + { label: "Camera", value: "1080p, 160° FOV" }, + { label: "Users", value: "25" }, + ]}, + ], + bom: [ + { part: "Aqara A100", qty: 1, specs: "Smart lock" }, + { part: "NFC Tags", qty: 5, specs: "NTAG216" }, + { part: "Doorbell Cam", qty: 1, specs: "Amcrest 1080p" }, + { part: "Relay Module", qty: 1, specs: "For existing chime" }, + ], + steps: [ + { title: "Remove Old Lock", description: "Uninstall existing deadbolt." }, + { title: "Install Smart Lock", description: "Mount Aqara lock per instructions." }, + { title: "Enroll Users", description: "Add fingerprints, PINs, and NFC tags." }, + { title: "Configure Automations", description: "Set up welcome lights and away mode." }, + ], + }), + }, + { + slug: "switches", + title: "Switches", + description: "Smart wall switches with physical buttons and LED indicators.", + category: "Control", + status: "online", + tags: ["Switch", "Wall", "Physical", "MQTT"], + gradient: "from-tertiary/30 to-primary/20", + sections: createProjectSections("Smart Switches", "Control", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "18 smart wall switches replacing traditional toggles. Momentary buttons with LED rings show status. ESP8266 inside each switch connects via WiFi and MQTT to Home Assistant.", + ]}, + { type: "specs", specs: [ + { label: "Count", value: "18" }, + { label: "Type", value: "Momentary + LED ring" }, + { label: "Protocol", value: "WiFi + MQTT" }, + { label: "Input", value: "240V AC" }, + ]}, + ], + bom: [ + { part: "Shelly 1L", qty: 18, specs: "WiFi relay" }, + { part: "LED Ring", qty: 18, specs: "WS2812B, 12-LED" }, + { part: "Momentary Switch", qty: 18, specs: "10A" }, + ], + steps: [ + { title: "Turn off Power", description: "Switch off breaker for lighting circuit." }, + { title: "Install Shelly", description: "Wire behind existing switch plate." }, + { title: "Configure MQTT", description: "Set topics and retain flags." }, + { title: "Test", description: "Verify local control and HA integration." }, + ], + }), + }, + { + slug: "rfid", + title: "RFID On/Off", + description: "RFID tag system for toggling devices and scenes.", + category: "Access", + status: "online", + tags: ["RFID", "Tags", "Scenes"], + gradient: "from-primary/30 to-secondary/20", + sections: createProjectSections("RFID On/Off", "Access", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "RC522 RFID reader mounted near entrance. Each family member has a personalized tag that triggers their preferred scene — lighting, temperature, and music.", + ]}, + { type: "specs", specs: [ + { label: "Reader", value: "RC522" }, + { label: "Tags", value: "12" }, + { label: "Range", value: "5cm" }, + { label: "Protocol", value: "SPI" }, + ]}, + ], + bom: [ + { part: "RC522 Module", qty: 1, specs: "13.56MHz" }, + { part: "NFC Tags", qty: 12, specs: "NTAG216" }, + { part: "ESP32", qty: 1, specs: "DevKit" }, + ], + code: { + language: "cpp", + filename: "rfid_scene.ino", + code: `#include +#include + +#define RST_PIN 22 +#define SS_PIN 5 + +MFRC522 mfrc522(SS_PIN, RST_PIN); + +void setup() { + SPI.begin(); + mfrc522.PCD_Init(); +} + +void loop() { + if (!mfrc522.PICC_IsNewCardPresent()) return; + if (!mfrc522.PICC_ReadCardSerial()) return; + + String uid = ""; + for (byte i = 0; i < mfrc522.uid.size; i++) { + uid += String(mfrc522.uid.uidByte[i], HEX); + } + + mqtt.publish("rfid/tag", uid.c_str()); + delay(2000); +}`, + }, + steps: [ + { title: "Mount Reader", description: "Install RC522 in 3D printed enclosure near door." }, + { title: "Program Tags", description: "Write unique IDs to each NFC tag." }, + { title: "Configure Scenes", description: "Map each tag to a Home Assistant scene." }, + { title: "Test", description: "Verify each tag triggers the correct automation." }, + ], + }), + }, + { + slug: "snapcast", + title: "Snapcast Raspberry Pi Players", + description: "Multi-room audio system using Raspberry Pi endpoints.", + category: "Media", + status: "online", + tags: ["Audio", "Multi-room", "Pi"], + gradient: "from-secondary/30 to-tertiary/20", + sections: createProjectSections("Snapcast Players", "Media", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "6 Raspberry Pi audio endpoints synchronized via Snapcast server. Each room has independent volume and source selection. Supports Spotify Connect, AirPlay, and local media.", + ]}, + { type: "specs", specs: [ + { label: "Players", value: "6" }, + { label: "Source", value: "Snapcast Server" }, + { label: "Output", value: "HiFiBerry DAC + Amp" }, + { label: "Latency", value: "<100ms sync" }, + ]}, + ], + bom: [ + { part: "Raspberry Pi Zero 2 W", qty: 6, specs: "512MB" }, + { part: "HiFiBerry MiniAmp", qty: 6, specs: "2x15W" }, + { part: "Speaker", qty: 12, specs: "8 Ohm, 15W" }, + { part: "PoE HAT", qty: 2, specs: "For main units" }, + ], + stack: [ + { name: "Snapcast", role: "Audio sync" }, + { name: "Spotifyd", role: "Spotify Connect" }, + { name: "Shairport", role: "AirPlay" }, + { name: "MPD", role: "Local media" }, + ], + steps: [ + { title: "Install OS", description: "Flash Raspberry Pi OS Lite to all units." }, + { title: "Install Snapclient", description: "Configure each Pi as a Snapcast client." }, + { title: "Connect Speakers", description: "Wire HiFiBerry amp to ceiling speakers." }, + { title: "Group Zones", description: "Create room groups in Snapcast server." }, + ], + }), + }, + { + slug: "smart-tvs", + title: "Raspberry Pi Smart TVs", + description: "Custom smart TV interfaces on Raspberry Pi 4 with LibreELEC.", + category: "Media", + status: "online", + tags: ["TV", "Pi", "Media"], + gradient: "from-tertiary/30 to-secondary/20", + sections: createProjectSections("Smart TVs", "Media", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "3 Raspberry Pi 4 media centers running LibreELEC. Custom skin with quick access to local NAS, streaming apps, and live TV. CEC remote control via TV remote.", + ]}, + { type: "specs", specs: [ + { label: "Devices", value: "3" }, + { label: "OS", value: "LibreELEC 12" }, + { label: "Player", value: "Kodi Omega" }, + { label: "Remote", value: "CEC + App" }, + ]}, + ], + bom: [ + { part: "Raspberry Pi 4", qty: 3, specs: "4GB" }, + { part: "Flirc Case", qty: 3, specs: "Aluminum, passive cooling" }, + { part: "HDMI Cable", qty: 3, specs: "2.0, 4K" }, + ], + steps: [ + { title: "Flash LibreELEC", description: "Use USB-SD creator to flash OS." }, + { title: "Install Add-ons", description: "Add Netflix, YouTube, and local media sources." }, + { title: "Configure CEC", description: "Enable CEC in Kodi settings." }, + { title: "Sync Library", description: "Point to NAS and scan media library." }, + ], + }), + }, + { + slug: "box-inventory", + title: "Home Box Inventory RFID", + description: "RFID-tagged storage boxes with searchable inventory database.", + category: "Inventory", + status: "online", + tags: ["RFID", "Inventory", "Storage"], + gradient: "from-primary/20 to-tertiary/20", + sections: createProjectSections("Box Inventory", "Inventory", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "200+ storage boxes tagged with RFID stickers. Handheld scanner queries the SQLite database to instantly locate items. Web interface allows searching by keyword or browsing by box.", + ]}, + { type: "specs", specs: [ + { label: "Tags", value: "200+" }, + { label: "Scanner", value: "Handheld RFID (USB)" }, + { label: "Database", value: "SQLite + Web UI" }, + { label: "Sync", value: "Home Assistant" }, + ]}, + ], + bom: [ + { part: "RFID Tags", qty: 250, specs: "13.56MHz, MIFARE" }, + { part: "USB Reader", qty: 1, specs: "ACR122U" }, + { part: "Label Printer", qty: 1, specs: "Thermal, 50x25mm" }, + ], + steps: [ + { title: "Print Labels", description: "Generate and print numbered labels with RFID tags." }, + { title: "Tag Boxes", description: "Attach labels to each storage box." }, + { title: "Catalog Items", description: "Scan box and record contents in database." }, + { title: "Deploy Web UI", description: "Host searchable interface on local server." }, + ], + }), + }, + { + slug: "tree-wiki", + title: "Tree Classification RFID Wiki", + description: "RFID tags on trees for species identification and care tracking.", + category: "Inventory", + status: "online", + tags: ["RFID", "Trees", "Wiki", "Garden"], + gradient: "from-secondary/20 to-tertiary/20", + sections: createProjectSections("Tree Wiki", "Inventory", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "45 trees tagged with waterproof epoxy RFID tags. Wiki-style database includes species info, planting date, pruning schedule, and health notes. NFC phone tap opens tree profile instantly.", + ]}, + { type: "specs", specs: [ + { label: "Trees", value: "45" }, + { label: "Tags", value: "RFID Epoxy, 13.56MHz" }, + { label: "Database", value: "Wiki.js" }, + { label: "Access", value: "NFC phone tap" }, + ]}, + ], + bom: [ + { part: "RFID Epoxy Tags", qty: 50, specs: "13.56MHz, IP68" }, + { part: "NFC Phone", qty: 1, specs: "Any NFC-enabled" }, + { part: "Cable Ties", qty: 50, specs: "UV-resistant" }, + ], + steps: [ + { title: "Tag Trees", description: "Attach RFID tags to trunk at 1m height." }, + { title: "Record Data", description: "Photograph and document each tree species and condition." }, + { title: "Create Wiki", description: "Build Wiki.js instance with tree profiles." }, + { title: "Link Tags", description: "Map RFID UIDs to wiki page URLs." }, + ], + }), + }, +]; + +export const electronicsProjects: Project[] = [ + { + slug: "yogurt-maker", + title: "Yogurt Maker", + description: "Precision temperature-controlled yogurt maker with timer.", + category: "Appliances", + status: "online", + tags: ["Temperature", "Timer", "Food"], + gradient: "from-tertiary/30 to-primary/20", + sections: createProjectSections("Yogurt Maker", "Appliances", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Custom yogurt maker maintaining 42°C ±1°C for 8 hours. PID temperature control with DS18B20 probe. Digital timer with automatic cooldown to 4°C after fermentation.", + ]}, + { type: "specs", specs: [ + { label: "Temp", value: "42°C ± 1°" }, + { label: "Timer", value: "Digital 8h" }, + { label: "Capacity", value: "1L" }, + { label: "Power", value: "40W" }, + ]}, + ], + bom: [ + { part: "Arduino Nano", qty: 1, specs: "ATmega328P" }, + { part: "DS18B20", qty: 1, specs: "Waterproof probe" }, + { part: "Relay Module", qty: 1, specs: "5V, 10A" }, + { part: "Heating Pad", qty: 1, specs: "40W, 12V" }, + { part: "LCD Display", qty: 1, specs: "16x2, I2C" }, + ], + steps: [ + { title: "Build Enclosure", description: "Insulated box with heating pad at base." }, + { title: "Wire Controller", description: "Connect Arduino, sensor, relay, and display." }, + { title: "Upload Firmware", description: "Flash PID control sketch with timer logic." }, + { title: "Calibrate", description: "Verify temperature stability over 8 hours." }, + ], + }), + }, + { + slug: "daily-calendar", + title: "Daily Calendar", + description: "E-ink display showing schedule, weather, and tasks.", + category: "Display", + status: "online", + tags: ["E-ink", "Calendar", "Low-power"], + gradient: "from-primary/30 to-secondary/20", + sections: createProjectSections("Daily Calendar", "Display", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "7.5\" E-ink display mounted in the kitchen showing daily schedule, weather forecast, and task list. Updates every 15 minutes via WiFi. Battery lasts 3 months between charges.", + ]}, + { type: "specs", specs: [ + { label: "Display", value: "7.5\" E-ink" }, + { label: "Resolution", value: "800x480" }, + { label: "Battery", value: "3 months" }, + { label: "Refresh", value: "Every 15min" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit, deep sleep" }, + { part: "Waveshare E-ink", qty: 1, specs: "7.5\" HAT" }, + { part: "LiPo Battery", qty: 1, specs: "2000mAh" }, + { part: "3D Frame", qty: 1, specs: "Wall mount" }, + ], + steps: [ + { title: "Assemble Display", description: "Mount E-ink HAT on ESP32." }, + { title: "Configure WiFi", description: "Set up connection and API endpoints." }, + { title: "Design Layout", description: "Create bitmap layout for calendar view." }, + { title: "Set Sleep Schedule", description: "Configure deep sleep for 15-minute wake cycle." }, + ], + }), + }, + { + slug: "remote-lights", + title: "3-Button Remote Lights", + description: "Handheld remote for three lighting zones with dimming.", + category: "Control", + status: "online", + tags: ["Remote", "RF", "Dimming"], + gradient: "from-secondary/30 to-tertiary/20", + sections: createProjectSections("3-Button Remote", "Control", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Compact handheld remote controlling three lighting zones. 433MHz RF with 30m range. Each button supports short press (on/off) and long press (dim up/down).", + ]}, + { type: "specs", specs: [ + { label: "Buttons", value: "3 + dim" }, + { label: "Protocol", value: "433MHz ASK" }, + { label: "Range", value: "30m" }, + { label: "Battery", value: "CR2032" }, + ]}, + ], + bom: [ + { part: "ATTiny85", qty: 1, specs: "8-pin" }, + { part: "433MHz TX", qty: 1, specs: "STX882" }, + { part: "Tactile Buttons", qty: 3, specs: "SMD" }, + { part: "CR2032 Holder", qty: 1, specs: "SMD" }, + ], + steps: [ + { title: "Design PCB", description: "Create compact remote layout in KiCad." }, + { title: "Assemble", description: "Solder components and program ATTiny." }, + { title: "Pair Receivers", description: "Teach each zone receiver the remote code." }, + { title: "Test Range", description: "Verify 30m range through walls." }, + ], + }), + }, + { + slug: "light-control", + title: "Light Control System", + description: "Centralized lighting control board with relay-based switching.", + category: "Control", + status: "online", + tags: ["Relay", "MQTT", "Zones"], + gradient: "from-tertiary/30 to-secondary/20", + sections: createProjectSections("Light Control", "Control", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "8-channel relay board in a DIN rail enclosure. Each channel controls a lighting zone via MQTT commands from Home Assistant. Manual override buttons on front panel.", + ]}, + { type: "specs", specs: [ + { label: "Relays", value: "8-channel" }, + { label: "Rating", value: "10A 240V" }, + { label: "Input", value: "12-24V DC" }, + { label: "Control", value: "MQTT + Manual" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "Relay Module", qty: 1, specs: "8-channel, 10A" }, + { part: "DIN Enclosure", qty: 1, specs: "6-module" }, + { part: "Terminal Blocks", qty: 8, specs: "2-pole" }, + ], + steps: [ + { title: "Mount in Enclosure", description: "Install ESP32 and relay module on DIN rail." }, + { title: "Wire Relays", description: "Connect line and load to terminal blocks." }, + { title: "Flash Firmware", description: "Upload MQTT relay control sketch." }, + { title: "Configure Topics", description: "Set per-zone MQTT topics in Home Assistant." }, + ], + }), + }, + { + slug: "step-down", + title: "240V Step-Down Switch", + description: "Safe voltage step-down converter with fuse protection.", + category: "Power", + status: "online", + tags: ["Power", "Safety", "Converter"], + gradient: "from-primary/30 to-tertiary/20", + sections: createProjectSections("Step-Down", "Power", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Isolated 240V AC to 12V/5V DC converter in a sealed enclosure. Fuse protection on both input and output. Ground fault detection with automatic shutdown.", + ]}, + { type: "specs", specs: [ + { label: "Input", value: "240V AC" }, + { label: "Output", value: "12V 5A + 5V 3A" }, + { label: "Isolation", value: "Reinforced" }, + { label: "Protection", value: "Fuse + GFI" }, + ]}, + ], + bom: [ + { part: "Meanwell LRS-100", qty: 1, specs: "12V 8.5A" }, + { part: "Buck Converter", qty: 1, specs: "12V to 5V" }, + { part: "Enclosure", qty: 1, specs: "IP65, ABS" }, + { part: "Fuse Holder", qty: 2, specs: "5x20mm" }, + ], + steps: [ + { title: "Mount PSU", description: "Install Meanwell in enclosure with heatsink." }, + { title: "Add Buck Converter", description: "Wire 12V to 5V step-down." }, + { title: "Install Protection", description: "Add fuses and GFI module." }, + { title: "Test Load", description: "Verify output stability at full load." }, + ], + }), + }, + { + slug: "light-bar", + title: "Light Bar", + description: "Custom LED light bar with adjustable color temperature.", + category: "Display", + status: "online", + tags: ["LED", "Workshop", "Adjustable"], + gradient: "from-flamingo/30 to-primary/20", + sections: createProjectSections("Light Bar", "Display", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "1.2m LED light bar for workshop bench. Warm white (2700K) and cool white (6500K) channels with independent dimming. Touch slider for quick adjustments.", + ]}, + { type: "specs", specs: [ + { label: "LEDs", value: "WS2812B, 60/m" }, + { label: "Length", value: "1.2m" }, + { label: "Power", value: "36W max" }, + { label: "Control", value: "Touch + App" }, + ]}, + ], + bom: [ + { part: "Aluminum Profile", qty: 1, specs: "1.2m, diffuser" }, + { part: "LED Strip", qty: 2, specs: "WW + CW, 60/m" }, + { part: "ESP32-C3", qty: 1, specs: "Mini" }, + { part: "Touch Slider", qty: 1, specs: "Capacitive" }, + ], + steps: [ + { title: "Cut Profile", description: "Cut aluminum channel to 1.2m." }, + { title: "Install LEDs", description: "Mount dual-color strips side by side." }, + { title: "Wire Controller", description: "Connect ESP32, MOSFETs, and touch sensor." }, + { title: "Mount", description: "Attach above workbench with brackets." }, + ], + }), + }, + { + slug: "horn", + title: "Horn", + description: "Custom electronic horn with multiple tone patterns.", + category: "Safety", + status: "online", + tags: ["Audio", "Alert", "Vehicle"], + gradient: "from-secondary/30 to-flamingo/20", + sections: createProjectSections("Horn", "Safety", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "110dB electronic horn for workshop alerts and vehicle warning. 8 programmable tone patterns selected via button. Weather-resistant housing for outdoor use.", + ]}, + { type: "specs", specs: [ + { label: "Power", value: "12V DC" }, + { label: "Tones", value: "8 patterns" }, + { label: "Volume", value: "110dB max" }, + { label: "Housing", value: "IP65" }, + ]}, + ], + bom: [ + { part: "Piezo Horn", qty: 1, specs: "110dB" }, + { part: "Arduino Nano", qty: 1, specs: "" }, + { part: "MOSFET", qty: 1, specs: "IRLZ44N" }, + { part: "Push Button", qty: 1, specs: "Waterproof" }, + ], + steps: [ + { title: "Build Driver", description: "Assemble MOSFET driver circuit." }, + { title: "Program Tones", description: "Upload tone generation sketch." }, + { title: "Install Horn", description: "Mount in weatherproof location." }, + { title: "Test Patterns", description: "Verify all 8 tones at full volume." }, + ], + }), + }, + { + slug: "kill-switch", + title: "Kill Switch", + description: "Emergency power cut-off switch for workshop tools.", + category: "Safety", + status: "online", + tags: ["Safety", "Emergency", "Power"], + gradient: "from-primary/30 to-secondary/20", + sections: createProjectSections("Kill Switch", "Safety", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Big red emergency stop button cutting power to all workshop tools. 16A rating handles multiple machines. Safety cover prevents accidental activation. LED ring shows power status.", + ]}, + { type: "specs", specs: [ + { label: "Rating", value: "16A 240V" }, + { label: "Type", value: "Mushroom + Cover" }, + { label: "Indicator", value: "LED ring" }, + { label: "Contacts", value: "NC + NO" }, + ]}, + ], + bom: [ + { part: "E-Stop Button", qty: 1, specs: "40mm mushroom" }, + { part: "Contactor", qty: 1, specs: "16A, 240V" }, + { part: "Enclosure", qty: 1, specs: "IP54" }, + { part: "LED Ring", qty: 1, specs: "24V" }, + ], + steps: [ + { title: "Mount Button", description: "Install on workshop wall at eye level." }, + { title: "Wire Contactor", description: "Wire NC contacts to break tool circuit." }, + { title: "Add Indicator", description: "Connect LED ring to show power state." }, + { title: "Test", description: "Verify immediate power cut on press." }, + ], + }), + }, + { + slug: "vehicle-tracking", + title: "Tracking Vehicles", + description: "GPS tracking system for vehicles with cellular connectivity.", + category: "Safety", + status: "online", + tags: ["GPS", "Cellular", "Tracking"], + gradient: "from-tertiary/30 to-primary/20", + sections: createProjectSections("Vehicle Tracking", "Safety", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Hidden GPS tracker with 4G LTE connectivity. Real-time location updates every 30 seconds. Geofence alerts when vehicle leaves designated areas. Battery backup for 48 hours if main power cut.", + ]}, + { type: "specs", specs: [ + { label: "GPS", value: "NEO-M9N" }, + { label: "Cellular", value: "SIM7600G" }, + { label: "Power", value: "Vehicle 12V + LiPo" }, + { label: "Backup", value: "48 hours" }, + ]}, + ], + bom: [ + { part: "ESP32-S3", qty: 1, specs: "With LTE" }, + { part: "GPS Module", qty: 1, specs: "NEO-M9N" }, + { part: "4G Modem", qty: 1, specs: "SIM7600G" }, + { part: "LiPo Battery", qty: 1, specs: "2000mAh" }, + ], + steps: [ + { title: "Install GPS Antenna", description: "Mount under dashboard with clear sky view." }, + { title: "Wire Power", description: "Connect to ignition-switched 12V." }, + { title: "Insert SIM", description: "Add data SIM and test cellular connection." }, + { title: "Configure Alerts", description: "Set geofences and speed alerts." }, + ], + }), + }, +]; + +export const inProgressProjects: Project[] = [ + { + slug: "sensor-lights", + title: "240V to 12V Sensor Lights", + description: "Converting mains sensor lights to 12V DC for safer operation.", + category: "Lighting", + status: "in-progress", + tags: ["Conversion", "PIR", "Safety"], + gradient: "from-secondary/30 to-primary/20", + sections: createProjectSections("240V Sensor Lights", "Lighting", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Converting existing 240V AC PIR security lights to 12V DC. Safer low-voltage wiring for garden paths. Solar panel + battery backup for off-grid operation.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Prototyping" }, + { label: "Completion", value: "40%" }, + { label: "Input", value: "12V DC / Solar" }, + { label: "Sensor", value: "PIR + LDR" }, + ]}, + ], + bom: [ + { part: "12V LED Flood", qty: 4, specs: "20W" }, + { part: "PIR Module", qty: 4, specs: "HC-SR501" }, + { part: "Solar Panel", qty: 1, specs: "100W" }, + { part: "Charge Controller", qty: 1, specs: "PWM 10A" }, + ], + steps: [ + { title: "Remove 240V Lights", description: "Safely disconnect and remove old fittings." }, + { title: "Install 12V LEDs", description: "Mount new low-voltage flood lights." }, + { title: "Add PIR Sensors", description: "Connect HC-SR501 to each light." }, + { title: "Wire Solar", description: "Install panel, battery, and charge controller." }, + ], + }), + }, + { + slug: "car-cameras", + title: "Car Cameras", + description: "Multi-camera dash cam system with front, rear, and cabin views.", + category: "Security", + status: "in-progress", + tags: ["Camera", "Dash", "Multi-view"], + gradient: "from-primary/30 to-tertiary/20", + sections: createProjectSections("Car Cameras", "Security", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "3-camera dash cam system with front (4K), rear (1080p), and cabin (1080p IR) views. Loop recording to 256GB SD with G-sensor impact detection. Parking mode with motion detection.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Testing" }, + { label: "Completion", value: "65%" }, + { label: "Front", value: "4K, 140°" }, + { label: "Rear/Cabin", value: "1080p" }, + ]}, + ], + bom: [ + { part: "Dash Cam Front", qty: 1, specs: "4K, GPS" }, + { part: "Rear Camera", qty: 1, specs: "1080p" }, + { part: "Cabin Camera", qty: 1, specs: "1080p IR" }, + { part: "SD Card", qty: 1, specs: "256GB U3" }, + { part: "Hardwire Kit", qty: 1, specs: "12V to 5V" }, + ], + steps: [ + { title: "Mount Front Cam", description: "Install behind rearview mirror." }, + { title: "Run Rear Cable", description: "Route cable through headliner to trunk." }, + { title: "Install Cabin Cam", description: "Mount on windshield facing interior." }, + { title: "Hardwire Power", description: "Connect to fuse box for parking mode." }, + ], + }), + }, + { + slug: "auto-garden", + title: "Automated Garden Lights", + description: "Fully automated garden lighting with adaptive brightness.", + category: "Lighting", + status: "in-progress", + tags: ["Auto", "Garden", "Adaptive"], + gradient: "from-tertiary/30 to-secondary/20", + sections: createProjectSections("Automated Garden Lights", "Lighting", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Garden lights that adapt to ambient conditions. Sunset detection triggers activation, motion sensors boost brightness when people present. Moon phase data dims lights on bright nights to reduce light pollution.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Planning" }, + { label: "Completion", value: "25%" }, + { label: "Sensors", value: "Light + Motion + Temp" }, + { label: "Control", value: "ESPHome + HA" }, + ]}, + ], + bom: [ + { part: "ESP32-C3", qty: 4, specs: "Mini" }, + { part: "BH1750", qty: 4, specs: "Light sensor" }, + { part: "MMWave Radar", qty: 4, specs: "LD2410" }, + { part: "LED Driver", qty: 4, specs: "12V 30W" }, + ], + steps: [ + { title: "Design Layout", description: "Plan light positions and sensor coverage." }, + { title: "Build Controllers", description: "Assemble ESP32 + sensor boards." }, + { title: "Install Fixtures", description: "Mount lights and run low-voltage cable." }, + { title: "Program Logic", description: "Write adaptive brightness algorithm." }, + ], + }), + }, + { + slug: "proximity-unlock", + title: "Proximity Unlock", + description: "Bluetooth-based proximity detection for automatic door unlocking.", + category: "Access", + status: "in-progress", + tags: ["Bluetooth", "Proximity", "Auto"], + gradient: "from-secondary/30 to-primary/20", + sections: createProjectSections("Proximity Unlock", "Access", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Door unlocks automatically when authorized Bluetooth device (phone/watch) comes within 2 meters. RSSI-based distance estimation with machine learning to prevent false triggers from adjacent rooms.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Prototyping" }, + { label: "Completion", value: "50%" }, + { label: "Protocol", value: "BLE 5.0" }, + { label: "Range", value: "2m" }, + ]}, + ], + bom: [ + { part: "ESP32-S3", qty: 1, specs: "BLE 5.0" }, + { part: "Relay Module", qty: 1, specs: "5V" }, + { part: "Door Sensor", qty: 1, specs: "Reed switch" }, + ], + steps: [ + { title: "Install ESP32", description: "Mount near door with antenna clear." }, + { title: "Pair Devices", description: "Register family phones as authorized beacons." }, + { title: "Calibrate RSSI", description: "Map signal strength to distance." }, + { title: "Add Safety Delay", description: "Require 3-second presence before unlock." }, + ], + }), + }, + { + slug: "face-id", + title: "Face ID Front Door", + description: "Facial recognition access control with 3D depth sensing.", + category: "Access", + status: "in-progress", + tags: ["AI", "Face", "Security"], + gradient: "from-primary/30 to-secondary/20", + sections: createProjectSections("Face ID Front Door", "Access", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Local AI facial recognition door lock. 3D depth sensing prevents photo spoofing. All processing on-device — no cloud. Enrolls 10 users with liveness detection.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Development" }, + { label: "Completion", value: "30%" }, + { label: "Camera", value: "OAK-D Lite" }, + { label: "Users", value: "10" }, + ]}, + ], + bom: [ + { part: "OAK-D Lite", qty: 1, specs: "Depth AI camera" }, + { part: "Raspberry Pi 4", qty: 1, specs: "8GB" }, + { part: "Relay Module", qty: 1, specs: "For lock" }, + { part: "IR Floodlight", qty: 1, specs: "850nm" }, + ], + steps: [ + { title: "Mount Camera", description: "Install OAK-D at 1.5m height." }, + { title: "Train Model", description: "Capture face embeddings for each user." }, + { title: "Add Depth Check", description: "Verify liveness with depth map." }, + { title: "Integrate Lock", description: "Connect relay to existing strike plate." }, + ], + }), + }, + { + slug: "dog-feeder", + title: "Dog Feeder", + description: "Automated dog feeder with large capacity hopper.", + category: "Feeders", + status: "in-progress", + tags: ["Feeder", "Dog", "Auto"], + gradient: "from-flamingo/30 to-tertiary/20", + sections: createProjectSections("Dog Feeder", "Feeders", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Large-capacity automatic dog feeder for medium/large breeds. 5L hopper holds 2 weeks of food. Auger mechanism dispenses precise portions. Camera verifies food dropped.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Design" }, + { label: "Completion", value: "15%" }, + { label: "Capacity", value: "5L" }, + { label: "Portion", value: "50-300g" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "Nema 17 Stepper", qty: 1, specs: "With gearbox" }, + { part: "Load Cell", qty: 1, specs: "5kg" }, + { part: "Camera", qty: 1, specs: "ESP32-CAM" }, + ], + steps: [ + { title: "Design Hopper", description: "CAD model for 5L capacity with slope." }, + { title: "Build Auger", description: "3D print auger with consistent pitch." }, + { title: "Add Scale", description: "Install load cell for portion verification." }, + { title: "Test with Food", description: "Verify no jam with different kibble sizes." }, + ], + }), + }, + { + slug: "dog-water", + title: "Dog Water", + description: "Automatic water dispenser with filtration and monitoring.", + category: "Feeders", + status: "in-progress", + tags: ["Water", "Dog", "Filter"], + gradient: "from-tertiary/30 to-primary/20", + sections: createProjectSections("Dog Water", "Feeders", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Automatic water bowl with constant filtration and level monitoring. Refills from 10L reservoir when low. Alerts when filter needs changing or reservoir empty.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Prototyping" }, + { label: "Completion", value: "35%" }, + { label: "Capacity", value: "10L" }, + { label: "Filter", value: "Carbon + UV" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "Ultrasonic Sensor", qty: 1, specs: "Waterproof" }, + { part: "Pump", qty: 1, specs: "12V, 5L/min" }, + { part: "Filter Housing", qty: 1, specs: "10\" standard" }, + ], + steps: [ + { title: "Build Reservoir", description: "Assemble 10L food-safe container." }, + { title: "Install Pump", description: "Add submersible pump with tubing." }, + { title: "Add Sensor", description: "Mount ultrasonic sensor for level detection." }, + { title: "Test Filtration", description: "Verify water quality over 30 days." }, + ], + }), + }, + { + slug: "dog-door", + title: "Dog Door", + description: "Smart pet door with RFID collar detection and curfew.", + category: "Access", + status: "in-progress", + tags: ["Door", "RFID", "Pet"], + gradient: "from-primary/30 to-flamingo/20", + sections: createProjectSections("Dog Door", "Access", { + overviewBlocks: [ + { type: "text", paragraphs: [ + "Motorized pet door with RFID collar reader. Only opens for registered pets. Curfew mode locks door at night. Logs all entries/exits with timestamp.", + ]}, + { type: "specs", specs: [ + { label: "Stage", value: "Planning" }, + { label: "Completion", value: "20%" }, + { label: "RFID", value: "134.2kHz FDX-B" }, + { label: "Motor", value: "Servo, 15kg/cm" }, + ]}, + ], + bom: [ + { part: "ESP32", qty: 1, specs: "DevKit" }, + { part: "RFID Reader", qty: 1, specs: "134.2kHz" }, + { part: "Servo Motor", qty: 1, specs: "MG996R" }, + { part: "Flap", qty: 1, specs: "Insulated, 300x200mm" }, + ], + steps: [ + { title: "Cut Opening", description: "Cut wall opening to fit door frame." }, + { title: "Install Reader", description: "Mount RFID antenna at entry point." }, + { title: "Add Motor", description: "Connect servo to locking mechanism." }, + { title: "Program Curfew", description: "Set lock/unlock schedule in firmware." }, + ], + }), + }, +]; + +export const allProjects = [...iotProjects, ...electronicsProjects, ...inProgressProjects]; + +export function getProjectBySlug(slug: string): Project | undefined { + return allProjects.find((p) => p.slug === slug); +} + +export function getProjectsByCategory(category: string): Project[] { + return allProjects.filter((p) => p.category === category); +} + +export function getCategories(projects: Project[]): string[] { + return [...new Set(projects.map((p) => p.category))]; +} \ No newline at end of file diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..2ee532b --- /dev/null +++ b/src/index.css @@ -0,0 +1,93 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Space+Grotesk:wght@400;500;600;700&display=swap'); +@import "tailwindcss"; + +@theme { + --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif; + --font-mono: "Space Grotesk", ui-monospace, SFMono-Regular, monospace; + + --color-surface: #111415; + --color-surface-dim: #111415; + --color-surface-bright: #373a3b; + --color-surface-container-lowest: #0c0f10; + --color-surface-container-low: #191c1e; + --color-surface-container: #1d2022; + --color-surface-container-high: #272a2c; + --color-surface-container-highest: #323537; + --color-on-surface: #e1e3e5; + --color-on-surface-variant: #c0c8cd; + --color-inverse-surface: #e1e3e5; + --color-inverse-on-surface: #2e3133; + --color-outline: #8a9297; + --color-outline-variant: #40484c; + --color-surface-tint: #93cfea; + + --color-primary: #a1ddf9; + --color-on-primary: #003545; + --color-primary-container: #85c1dc; + --color-on-primary-container: #005067; + --color-inverse-primary: #24657d; + --color-primary-fixed: #bce9ff; + --color-primary-fixed-dim: #93cfea; + --color-on-primary-fixed: #001f2a; + --color-on-primary-fixed-variant: #004d63; + + --color-secondary: #e3b5ff; + --color-on-secondary: #441f5e; + --color-secondary-container: #5c3676; + --color-on-secondary-container: #d1a4ed; + --color-secondary-fixed: #f3daff; + --color-secondary-fixed-dim: #e3b5ff; + --color-on-secondary-fixed: #2d0547; + --color-on-secondary-fixed-variant: #5c3676; + + --color-tertiary: #9ae2d8; + --color-on-tertiary: #003732; + --color-tertiary-container: #7fc6bc; + --color-on-tertiary-container: #00534c; + --color-tertiary-fixed: #a8f0e5; + --color-tertiary-fixed-dim: #8cd4c9; + --color-on-tertiary-fixed: #00201d; + --color-on-tertiary-fixed-variant: #005049; + + --color-error: #ffb4ab; + --color-on-error: #690005; + --color-error-container: #93000a; + --color-on-error-container: #ffdad6; + --color-flamingo: #eebebe; + + --color-background: #111415; + --color-on-background: #e1e3e5; + --color-surface-variant: #323537; +} + +@layer base { + body { + @apply bg-background text-on-background font-sans antialiased; + } + * { + scrollbar-width: thin; + scrollbar-color: var(--color-surface-container-highest) transparent; + } +} + +@layer utilities { + .glass { + background: color-mix(in srgb, var(--color-surface-container-high) 70%, transparent); + backdrop-filter: blur(16px); + -webkit-backdrop-filter: blur(16px); + } + .glass-card { + background: color-mix(in srgb, var(--color-surface-container) 75%, transparent); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--color-outline-variant); + } + .shadow-glow { + box-shadow: 0 0 20px color-mix(in srgb, var(--color-primary) 8%, transparent); + } + .glow-primary { + box-shadow: 0 0 16px color-mix(in srgb, var(--color-primary) 12%, transparent); + } + .no-scrollbar::-webkit-scrollbar { display: none; } + .no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } +} \ No newline at end of file diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..d8956f9 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,10 @@ +import {StrictMode} from 'react'; +import {createRoot} from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render( + + + , +); \ No newline at end of file diff --git a/src/pages/AiChat.tsx b/src/pages/AiChat.tsx new file mode 100644 index 0000000..4a1aa30 --- /dev/null +++ b/src/pages/AiChat.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import { Sparkles, Cpu } from "lucide-react"; +import PageHeader from "../components/PageHeader"; +import AiChatBox from "../components/AiChatBox"; + +export default function AiChat() { + return ( +
+ + +
+
+ +
+ +
+
+
+ +

About

+
+

+ Specialized in IoT architecture, electronics troubleshooting, component selection, and smart home automation design. +

+
+ +
+
+ +

Capabilities

+
+
+ {[ + "Circuit design advice", + "IoT protocol selection", + "Component recommendations", + "Troubleshooting guides", + "Home automation logic", + "Power calculations", + ].map((cap, i) => ( +
+ + {cap} +
+ ))} +
+
+
+
+
+ ); +} \ No newline at end of file diff --git a/src/pages/Contact.tsx b/src/pages/Contact.tsx new file mode 100644 index 0000000..4ba12ba --- /dev/null +++ b/src/pages/Contact.tsx @@ -0,0 +1,104 @@ +import React, { useState } from "react"; +import { Mail, Send, ExternalLink } from "lucide-react"; +import PageHeader from "../components/PageHeader"; +import ExternalLinkCard from "../components/ExternalLinkCard"; + +export default function Contact() { + const [submitted, setSubmitted] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + setSubmitted(true); + }; + + return ( +
+ + +
+
+
+ +

Send a Message

+
+ + {submitted ? ( +
+
Message Sent!
+
Thanks for reaching out. I'll respond soon.
+
+ ) : ( +
+
+ + +
+
+ + +
+
+ +