ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

using the logo! started moving ui changes over from mockup

authored by byarielm.fyi and committed by byarielm.fyi d216f8d7 702ca8db

verified
+455 -24
package-lock.json
··· 34 "postcss": "^8.5.6", 35 "tailwindcss": "^3.4.0", 36 "typescript": "^5.3.3", 37 - "vite": "^5.4.0" 38 } 39 }, 40 "node_modules/@alloc/quick-lru": { ··· 55 "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.0.tgz", 56 "integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==", 57 "license": "0BSD", 58 "dependencies": { 59 "@atcute/lexicons": "^1.1.1", 60 "@badrap/valita": "^0.4.5" ··· 407 "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", 408 "dev": true, 409 "license": "MIT", 410 "dependencies": { 411 "@babel/code-frame": "^7.27.1", 412 "@babel/generator": "^7.28.3", ··· 2103 "cpu": [ 2104 "arm" 2105 ], 2106 - "dev": true, 2107 "license": "MIT", 2108 "optional": true, 2109 "os": [ ··· 2117 "cpu": [ 2118 "arm64" 2119 ], 2120 - "dev": true, 2121 "license": "MIT", 2122 "optional": true, 2123 "os": [ ··· 2131 "cpu": [ 2132 "arm64" 2133 ], 2134 - "dev": true, 2135 "license": "MIT", 2136 "optional": true, 2137 "os": [ ··· 2145 "cpu": [ 2146 "x64" 2147 ], 2148 - "dev": true, 2149 "license": "MIT", 2150 "optional": true, 2151 "os": [ ··· 2159 "cpu": [ 2160 "arm64" 2161 ], 2162 - "dev": true, 2163 "license": "MIT", 2164 "optional": true, 2165 "os": [ ··· 2173 "cpu": [ 2174 "x64" 2175 ], 2176 - "dev": true, 2177 "license": "MIT", 2178 "optional": true, 2179 "os": [ ··· 2187 "cpu": [ 2188 "arm" 2189 ], 2190 - "dev": true, 2191 "license": "MIT", 2192 "optional": true, 2193 "os": [ ··· 2201 "cpu": [ 2202 "arm" 2203 ], 2204 - "dev": true, 2205 "license": "MIT", 2206 "optional": true, 2207 "os": [ ··· 2215 "cpu": [ 2216 "arm64" 2217 ], 2218 - "dev": true, 2219 "license": "MIT", 2220 "optional": true, 2221 "os": [ ··· 2229 "cpu": [ 2230 "arm64" 2231 ], 2232 - "dev": true, 2233 "license": "MIT", 2234 "optional": true, 2235 "os": [ ··· 2243 "cpu": [ 2244 "loong64" 2245 ], 2246 - "dev": true, 2247 "license": "MIT", 2248 "optional": true, 2249 "os": [ ··· 2257 "cpu": [ 2258 "ppc64" 2259 ], 2260 - "dev": true, 2261 "license": "MIT", 2262 "optional": true, 2263 "os": [ ··· 2271 "cpu": [ 2272 "riscv64" 2273 ], 2274 - "dev": true, 2275 "license": "MIT", 2276 "optional": true, 2277 "os": [ ··· 2285 "cpu": [ 2286 "riscv64" 2287 ], 2288 - "dev": true, 2289 "license": "MIT", 2290 "optional": true, 2291 "os": [ ··· 2299 "cpu": [ 2300 "s390x" 2301 ], 2302 - "dev": true, 2303 "license": "MIT", 2304 "optional": true, 2305 "os": [ ··· 2313 "cpu": [ 2314 "x64" 2315 ], 2316 - "dev": true, 2317 "license": "MIT", 2318 "optional": true, 2319 "os": [ ··· 2327 "cpu": [ 2328 "x64" 2329 ], 2330 - "dev": true, 2331 "license": "MIT", 2332 "optional": true, 2333 "os": [ ··· 2341 "cpu": [ 2342 "arm64" 2343 ], 2344 - "dev": true, 2345 "license": "MIT", 2346 "optional": true, 2347 "os": [ ··· 2355 "cpu": [ 2356 "arm64" 2357 ], 2358 - "dev": true, 2359 "license": "MIT", 2360 "optional": true, 2361 "os": [ ··· 2369 "cpu": [ 2370 "ia32" 2371 ], 2372 - "dev": true, 2373 "license": "MIT", 2374 "optional": true, 2375 "os": [ ··· 2383 "cpu": [ 2384 "x64" 2385 ], 2386 - "dev": true, 2387 "license": "MIT", 2388 "optional": true, 2389 "os": [ ··· 2397 "cpu": [ 2398 "x64" 2399 ], 2400 - "dev": true, 2401 "license": "MIT", 2402 "optional": true, 2403 "os": [ ··· 2414 "text-hex": "1.0.x" 2415 } 2416 }, 2417 "node_modules/@types/babel__core": { 2418 "version": "7.20.5", 2419 "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", ··· 2507 "integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==", 2508 "dev": true, 2509 "license": "MIT", 2510 "dependencies": { 2511 "csstype": "^3.0.2" 2512 } ··· 2860 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 2861 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 2862 "license": "MIT", 2863 "bin": { 2864 "acorn": "bin/acorn" 2865 }, ··· 3084 "dev": true, 3085 "license": "MIT" 3086 }, 3087 "node_modules/array-union": { 3088 "version": "2.1.0", 3089 "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", ··· 3264 } 3265 ], 3266 "license": "MIT", 3267 "dependencies": { 3268 "baseline-browser-mapping": "^2.8.3", 3269 "caniuse-lite": "^1.0.30001741", ··· 3325 "node": "*" 3326 } 3327 }, 3328 "node_modules/camelcase-css": { 3329 "version": "2.0.1", 3330 "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", ··· 3693 "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 3694 "license": "MIT" 3695 }, 3696 "node_modules/crc-32": { 3697 "version": "1.2.2", 3698 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", ··· 4009 "dev": true, 4010 "license": "MIT" 4011 }, 4012 "node_modules/dot-prop": { 4013 "version": "9.0.0", 4014 "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", ··· 4110 "url": "https://github.com/sponsors/sindresorhus" 4111 } 4112 }, 4113 "node_modules/es-module-lexer": { 4114 "version": "1.7.0", 4115 "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", ··· 4550 "version": "2.3.3", 4551 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 4552 "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 4553 - "dev": true, 4554 "hasInstallScript": true, 4555 "license": "MIT", 4556 "optional": true, ··· 4817 "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", 4818 "license": "MIT" 4819 }, 4820 "node_modules/imurmurhash": { 4821 "version": "0.1.4", 4822 "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", ··· 4852 "engines": { 4853 "node": ">= 10" 4854 } 4855 }, 4856 "node_modules/is-binary-path": { 4857 "version": "2.1.0", ··· 5044 "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 5045 "license": "MIT" 5046 }, 5047 "node_modules/jsesc": { 5048 "version": "3.1.0", 5049 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", ··· 5057 "node": ">=6" 5058 } 5059 }, 5060 "node_modules/json5": { 5061 "version": "2.2.3", 5062 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ··· 5232 }, 5233 "bin": { 5234 "loose-envify": "cli.js" 5235 } 5236 }, 5237 "node_modules/lru-cache": { ··· 5442 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 5443 } 5444 }, 5445 "node_modules/node-fetch": { 5446 "version": "2.7.0", 5447 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", ··· 5722 "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 5723 "license": "(MIT AND Zlib)" 5724 }, 5725 "node_modules/parse-gitignore": { 5726 "version": "2.0.0", 5727 "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz", ··· 5912 } 5913 ], 5914 "license": "MIT", 5915 "dependencies": { 5916 "nanoid": "^3.3.11", 5917 "picocolors": "^1.1.1", ··· 6211 "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 6212 "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 6213 "license": "MIT", 6214 "dependencies": { 6215 "loose-envify": "^1.1.0" 6216 }, ··· 6545 "node": ">=8" 6546 } 6547 }, 6548 "node_modules/source-map": { 6549 "version": "0.6.1", 6550 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", ··· 6800 "url": "https://github.com/sponsors/ljharb" 6801 } 6802 }, 6803 "node_modules/tailwindcss": { 6804 "version": "3.4.0", 6805 "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", ··· 7068 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", 7069 "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", 7070 "license": "Apache-2.0", 7071 "bin": { 7072 "tsc": "bin/tsc", 7073 "tsserver": "bin/tsserver" ··· 7218 "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", 7219 "dev": true, 7220 "license": "MIT", 7221 "dependencies": { 7222 "esbuild": "^0.21.3", 7223 "postcss": "^8.4.43", ··· 7270 "terser": { 7271 "optional": true 7272 } 7273 } 7274 }, 7275 "node_modules/webidl-conversions": {
··· 34 "postcss": "^8.5.6", 35 "tailwindcss": "^3.4.0", 36 "typescript": "^5.3.3", 37 + "vite": "^5.4.0", 38 + "vite-plugin-svgr": "^4.5.0" 39 } 40 }, 41 "node_modules/@alloc/quick-lru": { ··· 56 "resolved": "https://registry.npmjs.org/@atcute/identity/-/identity-1.1.0.tgz", 57 "integrity": "sha512-6vRvRqJatDB+JUQsb+UswYmtBGQnSZcqC3a2y6H5DB/v5KcIh+6nFFtc17G0+3W9rxdk7k9M4KkgkdKf/YDNoQ==", 58 "license": "0BSD", 59 + "peer": true, 60 "dependencies": { 61 "@atcute/lexicons": "^1.1.1", 62 "@badrap/valita": "^0.4.5" ··· 409 "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", 410 "dev": true, 411 "license": "MIT", 412 + "peer": true, 413 "dependencies": { 414 "@babel/code-frame": "^7.27.1", 415 "@babel/generator": "^7.28.3", ··· 2106 "cpu": [ 2107 "arm" 2108 ], 2109 "license": "MIT", 2110 "optional": true, 2111 "os": [ ··· 2119 "cpu": [ 2120 "arm64" 2121 ], 2122 "license": "MIT", 2123 "optional": true, 2124 "os": [ ··· 2132 "cpu": [ 2133 "arm64" 2134 ], 2135 "license": "MIT", 2136 "optional": true, 2137 "os": [ ··· 2145 "cpu": [ 2146 "x64" 2147 ], 2148 "license": "MIT", 2149 "optional": true, 2150 "os": [ ··· 2158 "cpu": [ 2159 "arm64" 2160 ], 2161 "license": "MIT", 2162 "optional": true, 2163 "os": [ ··· 2171 "cpu": [ 2172 "x64" 2173 ], 2174 "license": "MIT", 2175 "optional": true, 2176 "os": [ ··· 2184 "cpu": [ 2185 "arm" 2186 ], 2187 "license": "MIT", 2188 "optional": true, 2189 "os": [ ··· 2197 "cpu": [ 2198 "arm" 2199 ], 2200 "license": "MIT", 2201 "optional": true, 2202 "os": [ ··· 2210 "cpu": [ 2211 "arm64" 2212 ], 2213 "license": "MIT", 2214 "optional": true, 2215 "os": [ ··· 2223 "cpu": [ 2224 "arm64" 2225 ], 2226 "license": "MIT", 2227 "optional": true, 2228 "os": [ ··· 2236 "cpu": [ 2237 "loong64" 2238 ], 2239 "license": "MIT", 2240 "optional": true, 2241 "os": [ ··· 2249 "cpu": [ 2250 "ppc64" 2251 ], 2252 "license": "MIT", 2253 "optional": true, 2254 "os": [ ··· 2262 "cpu": [ 2263 "riscv64" 2264 ], 2265 "license": "MIT", 2266 "optional": true, 2267 "os": [ ··· 2275 "cpu": [ 2276 "riscv64" 2277 ], 2278 "license": "MIT", 2279 "optional": true, 2280 "os": [ ··· 2288 "cpu": [ 2289 "s390x" 2290 ], 2291 "license": "MIT", 2292 "optional": true, 2293 "os": [ ··· 2301 "cpu": [ 2302 "x64" 2303 ], 2304 "license": "MIT", 2305 "optional": true, 2306 "os": [ ··· 2314 "cpu": [ 2315 "x64" 2316 ], 2317 "license": "MIT", 2318 "optional": true, 2319 "os": [ ··· 2327 "cpu": [ 2328 "arm64" 2329 ], 2330 "license": "MIT", 2331 "optional": true, 2332 "os": [ ··· 2340 "cpu": [ 2341 "arm64" 2342 ], 2343 "license": "MIT", 2344 "optional": true, 2345 "os": [ ··· 2353 "cpu": [ 2354 "ia32" 2355 ], 2356 "license": "MIT", 2357 "optional": true, 2358 "os": [ ··· 2366 "cpu": [ 2367 "x64" 2368 ], 2369 "license": "MIT", 2370 "optional": true, 2371 "os": [ ··· 2379 "cpu": [ 2380 "x64" 2381 ], 2382 "license": "MIT", 2383 "optional": true, 2384 "os": [ ··· 2395 "text-hex": "1.0.x" 2396 } 2397 }, 2398 + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { 2399 + "version": "8.0.0", 2400 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", 2401 + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", 2402 + "dev": true, 2403 + "license": "MIT", 2404 + "engines": { 2405 + "node": ">=14" 2406 + }, 2407 + "funding": { 2408 + "type": "github", 2409 + "url": "https://github.com/sponsors/gregberge" 2410 + }, 2411 + "peerDependencies": { 2412 + "@babel/core": "^7.0.0-0" 2413 + } 2414 + }, 2415 + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { 2416 + "version": "8.0.0", 2417 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", 2418 + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", 2419 + "dev": true, 2420 + "license": "MIT", 2421 + "engines": { 2422 + "node": ">=14" 2423 + }, 2424 + "funding": { 2425 + "type": "github", 2426 + "url": "https://github.com/sponsors/gregberge" 2427 + }, 2428 + "peerDependencies": { 2429 + "@babel/core": "^7.0.0-0" 2430 + } 2431 + }, 2432 + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { 2433 + "version": "8.0.0", 2434 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", 2435 + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", 2436 + "dev": true, 2437 + "license": "MIT", 2438 + "engines": { 2439 + "node": ">=14" 2440 + }, 2441 + "funding": { 2442 + "type": "github", 2443 + "url": "https://github.com/sponsors/gregberge" 2444 + }, 2445 + "peerDependencies": { 2446 + "@babel/core": "^7.0.0-0" 2447 + } 2448 + }, 2449 + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { 2450 + "version": "8.0.0", 2451 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", 2452 + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", 2453 + "dev": true, 2454 + "license": "MIT", 2455 + "engines": { 2456 + "node": ">=14" 2457 + }, 2458 + "funding": { 2459 + "type": "github", 2460 + "url": "https://github.com/sponsors/gregberge" 2461 + }, 2462 + "peerDependencies": { 2463 + "@babel/core": "^7.0.0-0" 2464 + } 2465 + }, 2466 + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { 2467 + "version": "8.0.0", 2468 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", 2469 + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", 2470 + "dev": true, 2471 + "license": "MIT", 2472 + "engines": { 2473 + "node": ">=14" 2474 + }, 2475 + "funding": { 2476 + "type": "github", 2477 + "url": "https://github.com/sponsors/gregberge" 2478 + }, 2479 + "peerDependencies": { 2480 + "@babel/core": "^7.0.0-0" 2481 + } 2482 + }, 2483 + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { 2484 + "version": "8.0.0", 2485 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", 2486 + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", 2487 + "dev": true, 2488 + "license": "MIT", 2489 + "engines": { 2490 + "node": ">=14" 2491 + }, 2492 + "funding": { 2493 + "type": "github", 2494 + "url": "https://github.com/sponsors/gregberge" 2495 + }, 2496 + "peerDependencies": { 2497 + "@babel/core": "^7.0.0-0" 2498 + } 2499 + }, 2500 + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { 2501 + "version": "8.1.0", 2502 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", 2503 + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", 2504 + "dev": true, 2505 + "license": "MIT", 2506 + "engines": { 2507 + "node": ">=14" 2508 + }, 2509 + "funding": { 2510 + "type": "github", 2511 + "url": "https://github.com/sponsors/gregberge" 2512 + }, 2513 + "peerDependencies": { 2514 + "@babel/core": "^7.0.0-0" 2515 + } 2516 + }, 2517 + "node_modules/@svgr/babel-plugin-transform-svg-component": { 2518 + "version": "8.0.0", 2519 + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", 2520 + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", 2521 + "dev": true, 2522 + "license": "MIT", 2523 + "engines": { 2524 + "node": ">=12" 2525 + }, 2526 + "funding": { 2527 + "type": "github", 2528 + "url": "https://github.com/sponsors/gregberge" 2529 + }, 2530 + "peerDependencies": { 2531 + "@babel/core": "^7.0.0-0" 2532 + } 2533 + }, 2534 + "node_modules/@svgr/babel-preset": { 2535 + "version": "8.1.0", 2536 + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", 2537 + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", 2538 + "dev": true, 2539 + "license": "MIT", 2540 + "dependencies": { 2541 + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", 2542 + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", 2543 + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", 2544 + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", 2545 + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", 2546 + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", 2547 + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", 2548 + "@svgr/babel-plugin-transform-svg-component": "8.0.0" 2549 + }, 2550 + "engines": { 2551 + "node": ">=14" 2552 + }, 2553 + "funding": { 2554 + "type": "github", 2555 + "url": "https://github.com/sponsors/gregberge" 2556 + }, 2557 + "peerDependencies": { 2558 + "@babel/core": "^7.0.0-0" 2559 + } 2560 + }, 2561 + "node_modules/@svgr/core": { 2562 + "version": "8.1.0", 2563 + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", 2564 + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", 2565 + "dev": true, 2566 + "license": "MIT", 2567 + "peer": true, 2568 + "dependencies": { 2569 + "@babel/core": "^7.21.3", 2570 + "@svgr/babel-preset": "8.1.0", 2571 + "camelcase": "^6.2.0", 2572 + "cosmiconfig": "^8.1.3", 2573 + "snake-case": "^3.0.4" 2574 + }, 2575 + "engines": { 2576 + "node": ">=14" 2577 + }, 2578 + "funding": { 2579 + "type": "github", 2580 + "url": "https://github.com/sponsors/gregberge" 2581 + } 2582 + }, 2583 + "node_modules/@svgr/hast-util-to-babel-ast": { 2584 + "version": "8.0.0", 2585 + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", 2586 + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", 2587 + "dev": true, 2588 + "license": "MIT", 2589 + "dependencies": { 2590 + "@babel/types": "^7.21.3", 2591 + "entities": "^4.4.0" 2592 + }, 2593 + "engines": { 2594 + "node": ">=14" 2595 + }, 2596 + "funding": { 2597 + "type": "github", 2598 + "url": "https://github.com/sponsors/gregberge" 2599 + } 2600 + }, 2601 + "node_modules/@svgr/plugin-jsx": { 2602 + "version": "8.1.0", 2603 + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", 2604 + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", 2605 + "dev": true, 2606 + "license": "MIT", 2607 + "dependencies": { 2608 + "@babel/core": "^7.21.3", 2609 + "@svgr/babel-preset": "8.1.0", 2610 + "@svgr/hast-util-to-babel-ast": "8.0.0", 2611 + "svg-parser": "^2.0.4" 2612 + }, 2613 + "engines": { 2614 + "node": ">=14" 2615 + }, 2616 + "funding": { 2617 + "type": "github", 2618 + "url": "https://github.com/sponsors/gregberge" 2619 + }, 2620 + "peerDependencies": { 2621 + "@svgr/core": "*" 2622 + } 2623 + }, 2624 "node_modules/@types/babel__core": { 2625 "version": "7.20.5", 2626 "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", ··· 2714 "integrity": "sha512-ukd93VGzaNPMAUPy0gRDSC57UuQbnH9Kussp7HBjM06YFi9uZTFhOvMSO2OKqXm1rSgzOE+pVx1k1PYHGwlc8Q==", 2715 "dev": true, 2716 "license": "MIT", 2717 + "peer": true, 2718 "dependencies": { 2719 "csstype": "^3.0.2" 2720 } ··· 3068 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", 3069 "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", 3070 "license": "MIT", 3071 + "peer": true, 3072 "bin": { 3073 "acorn": "bin/acorn" 3074 }, ··· 3293 "dev": true, 3294 "license": "MIT" 3295 }, 3296 + "node_modules/argparse": { 3297 + "version": "2.0.1", 3298 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 3299 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 3300 + "dev": true, 3301 + "license": "Python-2.0" 3302 + }, 3303 "node_modules/array-union": { 3304 "version": "2.1.0", 3305 "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", ··· 3480 } 3481 ], 3482 "license": "MIT", 3483 + "peer": true, 3484 "dependencies": { 3485 "baseline-browser-mapping": "^2.8.3", 3486 "caniuse-lite": "^1.0.30001741", ··· 3542 "node": "*" 3543 } 3544 }, 3545 + "node_modules/callsites": { 3546 + "version": "3.1.0", 3547 + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 3548 + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 3549 + "dev": true, 3550 + "license": "MIT", 3551 + "engines": { 3552 + "node": ">=6" 3553 + } 3554 + }, 3555 + "node_modules/camelcase": { 3556 + "version": "6.3.0", 3557 + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", 3558 + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", 3559 + "dev": true, 3560 + "license": "MIT", 3561 + "engines": { 3562 + "node": ">=10" 3563 + }, 3564 + "funding": { 3565 + "url": "https://github.com/sponsors/sindresorhus" 3566 + } 3567 + }, 3568 "node_modules/camelcase-css": { 3569 "version": "2.0.1", 3570 "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", ··· 3933 "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", 3934 "license": "MIT" 3935 }, 3936 + "node_modules/cosmiconfig": { 3937 + "version": "8.3.6", 3938 + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", 3939 + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", 3940 + "dev": true, 3941 + "license": "MIT", 3942 + "dependencies": { 3943 + "import-fresh": "^3.3.0", 3944 + "js-yaml": "^4.1.0", 3945 + "parse-json": "^5.2.0", 3946 + "path-type": "^4.0.0" 3947 + }, 3948 + "engines": { 3949 + "node": ">=14" 3950 + }, 3951 + "funding": { 3952 + "url": "https://github.com/sponsors/d-fischer" 3953 + }, 3954 + "peerDependencies": { 3955 + "typescript": ">=4.9.5" 3956 + }, 3957 + "peerDependenciesMeta": { 3958 + "typescript": { 3959 + "optional": true 3960 + } 3961 + } 3962 + }, 3963 + "node_modules/cosmiconfig/node_modules/parse-json": { 3964 + "version": "5.2.0", 3965 + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", 3966 + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", 3967 + "dev": true, 3968 + "license": "MIT", 3969 + "dependencies": { 3970 + "@babel/code-frame": "^7.0.0", 3971 + "error-ex": "^1.3.1", 3972 + "json-parse-even-better-errors": "^2.3.0", 3973 + "lines-and-columns": "^1.1.6" 3974 + }, 3975 + "engines": { 3976 + "node": ">=8" 3977 + }, 3978 + "funding": { 3979 + "url": "https://github.com/sponsors/sindresorhus" 3980 + } 3981 + }, 3982 "node_modules/crc-32": { 3983 "version": "1.2.2", 3984 "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", ··· 4295 "dev": true, 4296 "license": "MIT" 4297 }, 4298 + "node_modules/dot-case": { 4299 + "version": "3.0.4", 4300 + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", 4301 + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", 4302 + "dev": true, 4303 + "license": "MIT", 4304 + "dependencies": { 4305 + "no-case": "^3.0.4", 4306 + "tslib": "^2.0.3" 4307 + } 4308 + }, 4309 "node_modules/dot-prop": { 4310 "version": "9.0.0", 4311 "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-9.0.0.tgz", ··· 4407 "url": "https://github.com/sponsors/sindresorhus" 4408 } 4409 }, 4410 + "node_modules/error-ex": { 4411 + "version": "1.3.4", 4412 + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", 4413 + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", 4414 + "dev": true, 4415 + "license": "MIT", 4416 + "dependencies": { 4417 + "is-arrayish": "^0.2.1" 4418 + } 4419 + }, 4420 "node_modules/es-module-lexer": { 4421 "version": "1.7.0", 4422 "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", ··· 4857 "version": "2.3.3", 4858 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 4859 "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 4860 "hasInstallScript": true, 4861 "license": "MIT", 4862 "optional": true, ··· 5123 "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", 5124 "license": "MIT" 5125 }, 5126 + "node_modules/import-fresh": { 5127 + "version": "3.3.1", 5128 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 5129 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 5130 + "dev": true, 5131 + "license": "MIT", 5132 + "dependencies": { 5133 + "parent-module": "^1.0.0", 5134 + "resolve-from": "^4.0.0" 5135 + }, 5136 + "engines": { 5137 + "node": ">=6" 5138 + }, 5139 + "funding": { 5140 + "url": "https://github.com/sponsors/sindresorhus" 5141 + } 5142 + }, 5143 + "node_modules/import-fresh/node_modules/resolve-from": { 5144 + "version": "4.0.0", 5145 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 5146 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 5147 + "dev": true, 5148 + "license": "MIT", 5149 + "engines": { 5150 + "node": ">=4" 5151 + } 5152 + }, 5153 "node_modules/imurmurhash": { 5154 "version": "0.1.4", 5155 "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", ··· 5185 "engines": { 5186 "node": ">= 10" 5187 } 5188 + }, 5189 + "node_modules/is-arrayish": { 5190 + "version": "0.2.1", 5191 + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", 5192 + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", 5193 + "dev": true, 5194 + "license": "MIT" 5195 }, 5196 "node_modules/is-binary-path": { 5197 "version": "2.1.0", ··· 5384 "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 5385 "license": "MIT" 5386 }, 5387 + "node_modules/js-yaml": { 5388 + "version": "4.1.1", 5389 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", 5390 + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", 5391 + "dev": true, 5392 + "license": "MIT", 5393 + "dependencies": { 5394 + "argparse": "^2.0.1" 5395 + }, 5396 + "bin": { 5397 + "js-yaml": "bin/js-yaml.js" 5398 + } 5399 + }, 5400 "node_modules/jsesc": { 5401 "version": "3.1.0", 5402 "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", ··· 5410 "node": ">=6" 5411 } 5412 }, 5413 + "node_modules/json-parse-even-better-errors": { 5414 + "version": "2.3.1", 5415 + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", 5416 + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", 5417 + "dev": true, 5418 + "license": "MIT" 5419 + }, 5420 "node_modules/json5": { 5421 "version": "2.2.3", 5422 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ··· 5592 }, 5593 "bin": { 5594 "loose-envify": "cli.js" 5595 + } 5596 + }, 5597 + "node_modules/lower-case": { 5598 + "version": "2.0.2", 5599 + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", 5600 + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", 5601 + "dev": true, 5602 + "license": "MIT", 5603 + "dependencies": { 5604 + "tslib": "^2.0.3" 5605 } 5606 }, 5607 "node_modules/lru-cache": { ··· 5812 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 5813 } 5814 }, 5815 + "node_modules/no-case": { 5816 + "version": "3.0.4", 5817 + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", 5818 + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", 5819 + "dev": true, 5820 + "license": "MIT", 5821 + "dependencies": { 5822 + "lower-case": "^2.0.2", 5823 + "tslib": "^2.0.3" 5824 + } 5825 + }, 5826 "node_modules/node-fetch": { 5827 "version": "2.7.0", 5828 "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", ··· 6103 "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", 6104 "license": "(MIT AND Zlib)" 6105 }, 6106 + "node_modules/parent-module": { 6107 + "version": "1.0.1", 6108 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 6109 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 6110 + "dev": true, 6111 + "license": "MIT", 6112 + "dependencies": { 6113 + "callsites": "^3.0.0" 6114 + }, 6115 + "engines": { 6116 + "node": ">=6" 6117 + } 6118 + }, 6119 "node_modules/parse-gitignore": { 6120 "version": "2.0.0", 6121 "resolved": "https://registry.npmjs.org/parse-gitignore/-/parse-gitignore-2.0.0.tgz", ··· 6306 } 6307 ], 6308 "license": "MIT", 6309 + "peer": true, 6310 "dependencies": { 6311 "nanoid": "^3.3.11", 6312 "picocolors": "^1.1.1", ··· 6606 "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", 6607 "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", 6608 "license": "MIT", 6609 + "peer": true, 6610 "dependencies": { 6611 "loose-envify": "^1.1.0" 6612 }, ··· 6941 "node": ">=8" 6942 } 6943 }, 6944 + "node_modules/snake-case": { 6945 + "version": "3.0.4", 6946 + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", 6947 + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", 6948 + "dev": true, 6949 + "license": "MIT", 6950 + "dependencies": { 6951 + "dot-case": "^3.0.4", 6952 + "tslib": "^2.0.3" 6953 + } 6954 + }, 6955 "node_modules/source-map": { 6956 "version": "0.6.1", 6957 "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", ··· 7207 "url": "https://github.com/sponsors/ljharb" 7208 } 7209 }, 7210 + "node_modules/svg-parser": { 7211 + "version": "2.0.4", 7212 + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", 7213 + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", 7214 + "dev": true, 7215 + "license": "MIT" 7216 + }, 7217 "node_modules/tailwindcss": { 7218 "version": "3.4.0", 7219 "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.0.tgz", ··· 7482 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", 7483 "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", 7484 "license": "Apache-2.0", 7485 + "peer": true, 7486 "bin": { 7487 "tsc": "bin/tsc", 7488 "tsserver": "bin/tsserver" ··· 7633 "integrity": "sha512-j3lYzGC3P+B5Yfy/pfKNgVEg4+UtcIJcVRt2cDjIOmhLourAqPqf8P7acgxeiSgUB7E3p2P8/3gNIgDLpwzs4g==", 7634 "dev": true, 7635 "license": "MIT", 7636 + "peer": true, 7637 "dependencies": { 7638 "esbuild": "^0.21.3", 7639 "postcss": "^8.4.43", ··· 7686 "terser": { 7687 "optional": true 7688 } 7689 + } 7690 + }, 7691 + "node_modules/vite-plugin-svgr": { 7692 + "version": "4.5.0", 7693 + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.5.0.tgz", 7694 + "integrity": "sha512-W+uoSpmVkSmNOGPSsDCWVW/DDAyv+9fap9AZXBvWiQqrboJ08j2vh0tFxTD/LjwqwAd3yYSVJgm54S/1GhbdnA==", 7695 + "dev": true, 7696 + "license": "MIT", 7697 + "dependencies": { 7698 + "@rollup/pluginutils": "^5.2.0", 7699 + "@svgr/core": "^8.1.0", 7700 + "@svgr/plugin-jsx": "^8.1.0" 7701 + }, 7702 + "peerDependencies": { 7703 + "vite": ">=2.6.0" 7704 } 7705 }, 7706 "node_modules/webidl-conversions": {
+2 -1
package.json
··· 38 "postcss": "^8.5.6", 39 "tailwindcss": "^3.4.0", 40 "typescript": "^5.3.3", 41 - "vite": "^5.4.0" 42 } 43 }
··· 38 "postcss": "^8.5.6", 39 "tailwindcss": "^3.4.0", 40 "typescript": "^5.3.3", 41 + "vite": "^5.4.0", 42 + "vite-plugin-svgr": "^4.5.0" 43 } 44 }
public/favicon/apple-touch-icon.png

This is a binary file and will not be displayed.

public/favicon/favicon-96x96.png

This is a binary file and will not be displayed.

public/favicon/favicon.ico

This is a binary file and will not be displayed.

public/favicon/icon.png

This is a binary file and will not be displayed.

+21
public/favicon/site.webmanifest
···
··· 1 + { 2 + "name": "ATLast - Your favs in the ATmosphere", 3 + "short_name": "ATlast", 4 + "icons": [ 5 + { 6 + "src": "/web-app-manifest-192x192.png", 7 + "sizes": "192x192", 8 + "type": "image/png", 9 + "purpose": "maskable" 10 + }, 11 + { 12 + "src": "/web-app-manifest-512x512.png", 13 + "sizes": "512x512", 14 + "type": "image/png", 15 + "purpose": "maskable" 16 + } 17 + ], 18 + "theme_color": "#0891b2", 19 + "background_color": "#ffffff", 20 + "display": "standalone" 21 + }
public/favicon/web-app-manifest-192x192.png

This is a binary file and will not be displayed.

public/favicon/web-app-manifest-512x512.png

This is a binary file and will not be displayed.

+176
src/assets/at-firefly-logo.svg
···
··· 1 + <?xml version="1.0" encoding="UTF-8" standalone="no"?> 2 + <!-- Created with Inkscape (http://www.inkscape.org/) --> 3 + 4 + <svg 5 + width="149.09583" 6 + height="62.405361" 7 + viewBox="0 0 39.448271 16.511417" 8 + version="1.1" 9 + id="svg1" 10 + xml:space="preserve" 11 + xmlns:xlink="http://www.w3.org/1999/xlink" 12 + xmlns="http://www.w3.org/2000/svg" 13 + xmlns:svg="http://www.w3.org/2000/svg"><defs 14 + id="defs1"><linearGradient 15 + id="linearGradient5"><stop 16 + style="stop-color:#06b6d4;stop-opacity:1;" 17 + offset="0.20974289" 18 + id="stop3" /><stop 19 + style="stop-color:#6d28d9;stop-opacity:1;" 20 + offset="0.38791069" 21 + id="stop4" /><stop 22 + style="stop-color:#ec4899;stop-opacity:1;" 23 + offset="0.60860217" 24 + id="stop6" /><stop 25 + style="stop-color:#f97316;stop-opacity:1;" 26 + offset="0.8159675" 27 + id="stop5" /></linearGradient><linearGradient 28 + id="linearGradient30"><stop 29 + style="stop-color:#fef08a;stop-opacity:1;" 30 + offset="0" 31 + id="stop29" /><stop 32 + style="stop-color:#eab308;stop-opacity:1;" 33 + offset="1" 34 + id="stop30" /></linearGradient><linearGradient 35 + id="linearGradient25"><stop 36 + style="stop-color:#fef08a;stop-opacity:1;" 37 + offset="0" 38 + id="stop25" /><stop 39 + style="stop-color:#ffce00;stop-opacity:1;" 40 + offset="1" 41 + id="stop26" /></linearGradient><linearGradient 42 + xlink:href="#linearGradient6" 43 + id="linearGradient8" 44 + x1="15.083038" 45 + y1="9.7583179" 46 + x2="17.143671" 47 + y2="9.3141584" 48 + gradientUnits="userSpaceOnUse" 49 + gradientTransform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><linearGradient 50 + id="linearGradient6"><stop 51 + style="stop-color:#ea580c;stop-opacity:1;" 52 + offset="0" 53 + id="stop7" /><stop 54 + style="stop-color:#ff8c36;stop-opacity:1;" 55 + offset="1" 56 + id="stop8" /></linearGradient><linearGradient 57 + xlink:href="#linearGradient175" 58 + id="linearGradient176" 59 + x1="1.0581446" 60 + y1="9.2239799" 61 + x2="3.3587396" 62 + y2="9.369256" 63 + gradientUnits="userSpaceOnUse" 64 + gradientTransform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><linearGradient 65 + id="linearGradient175"><stop 66 + style="stop-color:#00c9db;stop-opacity:1;" 67 + offset="0" 68 + id="stop175" /><stop 69 + style="stop-color:#0891b2;stop-opacity:1;" 70 + offset="1" 71 + id="stop176" /></linearGradient><linearGradient 72 + xlink:href="#linearGradient1" 73 + id="linearGradient2" 74 + x1="4.1462159" 75 + y1="10.224313" 76 + x2="5.5784869" 77 + y2="10.895846" 78 + gradientUnits="userSpaceOnUse" /><linearGradient 79 + id="linearGradient1"><stop 80 + style="stop-color:#06b6d4;stop-opacity:1;" 81 + offset="0" 82 + id="stop1" /><stop 83 + style="stop-color:#39e8ff;stop-opacity:1;" 84 + offset="1" 85 + id="stop2" /></linearGradient><filter 86 + style="color-interpolation-filters:sRGB" 87 + id="filter187" 88 + x="0" 89 + y="0" 90 + width="1" 91 + height="1"><feFlood 92 + result="flood1" 93 + flood-color="rgb(198,0,0)" 94 + flood-opacity="0.172549" 95 + id="feFlood187" /><feBlend 96 + result="blend1" 97 + in="flood1" 98 + in2="SourceGraphic" 99 + mode="hard-light" 100 + id="feBlend187" /><feComposite 101 + operator="in" 102 + in="blend1" 103 + in2="SourceGraphic" 104 + id="feComposite187" /></filter><linearGradient 105 + xlink:href="#linearGradient5" 106 + id="linearGradient7" 107 + gradientUnits="userSpaceOnUse" 108 + gradientTransform="matrix(0.90157488,0,0,1,0.94161522,0)" 109 + x1="0.84473455" 110 + y1="8.6206245" 111 + x2="18.150963" 112 + y2="8.6206245" /><radialGradient 113 + xlink:href="#linearGradient30" 114 + id="radialGradient15" 115 + gradientUnits="userSpaceOnUse" 116 + gradientTransform="matrix(2.0224824,0.82216334,-1.2014709,2.9555607,125.44482,81.267746)" 117 + cx="2.9812629" 118 + cy="10.872172" 119 + fx="2.9812629" 120 + fy="10.872172" 121 + r="1.9679179" /><radialGradient 122 + xlink:href="#linearGradient25" 123 + id="radialGradient22" 124 + gradientUnits="userSpaceOnUse" 125 + gradientTransform="matrix(0.89210545,-0.38362324,0.41746133,0.97079503,-2.8830544,6.222786)" 126 + cx="15.283519" 127 + cy="11.701982" 128 + fx="15.283519" 129 + fy="11.701982" 130 + r="1.8067596" /></defs><g 131 + id="layer1" 132 + transform="translate(-112.53832,-102.7461)"><path 133 + id="use5" 134 + clip-path="none" 135 + style="display:inline;vector-effect:none;fill:url(#linearGradient7);fill-rule:evenodd;stroke-width:0.12119;stop-color:#000000" 136 + d="m 9.5464118,5.3948093 c -1.7679049,0 -3.2585419,1.1196005 -3.6993808,2.7341191 C 5.5744869,8.7906914 5.1569858,9.2553125 4.8412936,9.5361086 4.7716625,9.3005651 4.7002206,9.1621229 4.7002205,9.1621229 5.2868814,8.9135379 6.0419642,7.6530245 4.9833135,7.0114688 5.1395775,6.476825 5.5741161,6.150355 5.5741161,6.150355 a 0.1322915,0.1322915 0 0 0 0.029351,-0.1855726 0.1322915,0.1322915 0 0 0 -0.087816,-0.052784 0.1322915,0.1322915 0 0 0 -0.097757,0.025564 c 0,0 -0.4809256,0.3494235 -0.6743577,0.95319 -0.02849,-0.011951 -0.056285,-0.024258 -0.086632,-0.035505 -0.04629,-0.017155 -0.090502,-0.031021 -0.1339721,-0.043789 V 6.8112208 C 4.4384266,6.2290341 4.0220757,5.8012228 4.0220757,5.8012228 a 0.15000001,0.15000001 0 0 0 -0.1036745,-0.046866 0.15000001,0.15000001 0 0 0 -0.107225,0.039055 0.15000001,0.15000001 0 0 0 -0.00781,0.2130298 c 0,0 0.3005764,0.3327274 0.4021529,0.7465511 C 2.9819341,6.6795482 3.19481,8.6965345 3.19481,8.6965345 c 0,0 -1.5666732,0.6527946 -1.6135824,1.9818875 -0.03249,0.920542 0.3289793,1.637733 0.9962694,1.86259 0.7638643,0.2574 1.8081292,0.05383 2.1984675,-1.007157 C 4.9377224,11.094173 4.9807228,10.683434 4.9684014,10.33 5.1571925,10.19256 5.4477957,9.9545824 5.74454,9.6092488 5.9827317,11.52521 7.6333364,12.842568 9.6058234,12.842568 c 0.9976536,0 2.4990766,-0.622169 2.4990766,-1.155569 0,-0.207431 -0.17785,-0.385347 -0.375406,-0.385347 -0.335844,0 -0.642006,0.652108 -2.1236706,0.652108 -1.6001972,0 -2.8250118,-1.067007 -2.8250118,-2.8252484 0,-1.5705638 1.2148375,-2.8446579 2.7360127,-2.8446579 1.5211747,0 2.6176627,0.8593501 2.6176627,2.4595473 0,1.0667982 -0.513563,1.52127 -0.869161,1.52127 -0.138289,0 -0.187703,-0.07911 -0.187703,-0.197645 0,-0.079019 0.03941,-0.2666231 0.05917,-0.3555229 l 0.306289,-1.7189137 c 0.02963,-0.1382887 0.03953,-0.2271401 0.03953,-0.2864068 0,-0.2765773 -0.197515,-0.4246394 -0.434581,-0.4246394 -0.1778,0 -0.405201,0.049313 -0.474346,0.3555231 l -0.03929,0.1777615 C 10.247941,7.3999618 9.9120187,7.2024855 9.3094753,7.2024855 c -0.9778982,0 -1.8767925,0.9977126 -1.8767925,2.3608436 0,0.9778989 0.6123067,1.7383229 1.4519164,1.7383229 0.4543769,0 0.7902106,-0.227119 1.027277,-0.503697 h 0.019883 c 0.00988,0.335845 0.2074648,0.503697 0.5926958,0.503697 0.259292,0 0.770004,-0.115173 1.262557,-0.432687 1.45e-4,-9.4e-5 3.28e-4,-1.43e-4 4.73e-4,-2.37e-4 a 0.25,0.25 0 0 0 0.0095,-0.0036 c 0,0 0.717037,-0.3987 1.433927,-0.9823042 -0.04094,0.3418032 -0.02,0.7710522 0.141074,1.2947482 0.373127,1.213185 1.498051,2.045055 2.707845,1.702581 0.95218,-0.269547 1.218765,-1.27536 1.131425,-1.997036 -0.202999,-1.6773421 -1.819985,-2.4697254 -1.819985,-2.4697254 0,0 0.48517,-1.4969048 -0.720987,-1.5629287 0.01314,-0.6264143 0.327829,-1.0166255 0.327829,-1.0166255 a 0.1322915,0.1322915 0 0 0 -0.02154,-0.1853359 0.1322915,0.1322915 0 0 0 -0.09563,-0.029351 0.1322915,0.1322915 0 0 0 -0.08995,0.04876 c 0,0 -0.372292,0.4671488 -0.385584,1.1894163 -0.02876,0.00276 -0.05663,0.00492 -0.08687,0.00899 -0.05471,0.00737 -0.106452,0.01695 -0.155749,0.027931 C 14.08742,6.7190018 13.984099,6.5462677 13.884882,6.3924989 13.714131,6.1278667 13.548768,5.9257269 13.548768,5.9257269 a 0.1322915,0.1322915 0 0 0 -0.185572,-0.019409 0.1322915,0.1322915 0 0 0 -0.01941,0.1874663 c 0,0 0.156273,0.1902354 0.318361,0.441445 0.08882,0.1376604 0.177279,0.2954109 0.242618,0.4419185 -0.962673,0.4198663 -0.489726,1.7135552 -0.319072,1.8957285 -0.218753,0.2391489 -0.479826,0.468665 -0.74087,0.6753045 0.111973,-0.311095 0.178708,-0.6725034 0.178708,-1.091186 0,-1.8866522 -1.590467,-3.062185 -3.4771192,-3.0621854 z m -0.059175,2.8744821 c 0.493888,0 0.7605162,0.3457497 0.7605162,0.8495155 0,0.6321767 -0.3951282,1.1160391 -0.9581605,1.1160391 -0.4543769,0 -0.7901039,-0.3160703 -0.7901039,-0.8791026 0,-0.582788 0.4148382,-1.086452 0.9877482,-1.086452 z" 137 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 138 + id="use15" 139 + clip-path="none" 140 + style="display:inline;fill:url(#radialGradient15);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" 141 + d="m 115.82132,112.56617 c -0.27625,0.49935 -0.46301,1.0707 -0.48575,1.71514 -0.0709,2.00974 0.71823,3.57551 2.17506,4.06642 1.65648,0.55819 3.9162,0.12254 4.78162,-2.15336 -1.36863,-0.43607 -4.93494,-1.70122 -6.39548,-3.54241 -0.0265,-0.0334 -0.0503,-0.0569 -0.0755,-0.0858 z" /><path 142 + id="use22" 143 + clip-path="none" 144 + style="display:inline;fill:url(#radialGradient22);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.12119px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 145 + d="m 17.070151,10.283606 c -0.0309,0.02427 -0.06368,0.05336 -0.100361,0.09279 -0.882008,0.947916 -3.175453,1.419561 -3.307879,1.446235 0.514178,0.841867 1.438112,1.334934 2.417888,1.057575 0.952181,-0.269547 1.218765,-1.275359 1.131425,-1.997036 -0.02594,-0.214294 -0.0753,-0.413961 -0.141073,-0.599561 z" 146 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 147 + id="use26" 148 + mask="none" 149 + style="display:inline;fill:#ffffff;fill-opacity:0.3;fill-rule:nonzero;stroke:none;stroke-width:0.12119px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 150 + d="m 16.85144,10.150581 c -0.508591,0.461156 -1.35481,0.813378 -2.071833,1.043136 -0.666634,0.213612 -1.199253,0.320379 -1.273681,0.33493 0.028,0.0612 0.05779,0.121292 0.08971,0.179655 0.173364,-0.03571 0.656809,-0.141604 1.243147,-0.329486 0.729056,-0.233619 1.59345,-0.584877 2.144263,-1.084321 z" 151 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 152 + id="use24" 153 + mask="none" 154 + style="display:inline;fill:#f7c800;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.121381;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" 155 + d="m 16.989436,10.244551 -0.05018,0.04639 c -0.51324,0.478709 -1.339021,0.831881 -2.035382,1.060178 -0.696356,0.228298 -1.261373,0.333747 -1.261373,0.333747 l -0.05373,0.01254 c 0.02313,0.04283 0.04895,0.08542 0.07408,0.126634 l 0.0047,-0.0017 c 0,0 0.573109,-0.109383 1.278415,-0.340611 0.705306,-0.231233 1.545195,-0.584212 2.087219,-1.089766 l 0.04994,-0.04734 z" 156 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 157 + id="use27" 158 + mask="none" 159 + style="display:inline;fill:#ffffff;fill-opacity:0.3;fill-rule:nonzero;stroke:none;stroke-width:0.121381;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" 160 + d="m 2.7273279,10.277689 -0.1162196,0.155512 c 0,0 0.5251165,0.391878 1.2900138,0.686192 l 0.069826,-0.181548 C 3.2321275,10.653561 2.7273279,10.277689 2.7273279,10.277689 Z" 161 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 162 + id="use28" 163 + mask="none" 164 + style="display:inline;fill:#f7c800;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.121381;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" 165 + d="m 2.5081439,10.365268 -0.076927,0.113616 c 0,0 0.3023891,0.20541 0.6710439,0.40807 0.3686547,0.202656 0.794261,0.408616 1.0940263,0.393395 l -0.0071,-0.136812 C 3.9659775,11.15485 3.5305678,10.965962 3.1685363,10.766947 2.8065048,10.567933 2.5081439,10.365268 2.5081439,10.365268 Z" 166 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /><path 167 + style="fill:url(#linearGradient8);fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" 168 + d="m 144.63508,109.85058 c -0.33259,1.91237 3.00927,7.85057 6.59868,4.76675 3.48964,-2.99811 -6.2661,-6.67912 -6.59868,-4.76675 z" 169 + id="path119" /><path 170 + style="fill:url(#linearGradient176);fill-opacity:1;stroke:none;stroke-width:0.265;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1" 171 + d="m 120.19851,109.84357 c -2.04144,-0.5332 -9.22765,0.28065 -7.35052,3.89918 2.20679,4.254 9.39196,-3.36597 7.35052,-3.89918 z" 172 + id="path117" /><path 173 + style="display:inline;fill:url(#linearGradient2);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.12119px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;filter:url(#filter187)" 174 + d="M 4.6747412,8.8087186 C 3.871122,8.4952424 2.6835491,12.528444 5.3401882,12.520739 6.3667683,12.517762 5.4783605,9.1221948 4.6747412,8.8087186 Z" 175 + id="path118" 176 + transform="matrix(2.1832058,0,0,2.1832058,111.88342,90.968121)" /></g></svg>
+68 -40
src/components/AppHeader.tsx
··· 1 import { useState, useEffect, useRef } from "react"; 2 import { Heart, Home, LogOut, ChevronDown } from "lucide-react"; 3 import ThemeControls from "./ThemeControls"; 4 5 interface atprotoSession { 6 did: string; ··· 13 interface AppHeaderProps { 14 session: atprotoSession | null; 15 onLogout: () => void; 16 - onNavigate: (step: 'home' | 'login') => void; 17 currentStep: string; 18 isDark?: boolean; 19 reducedMotion?: boolean; ··· 21 onToggleMotion?: () => void; 22 } 23 24 - export default function AppHeader({ 25 - session, 26 - onLogout, 27 - onNavigate, 28 currentStep, 29 isDark = false, 30 reducedMotion = false, 31 onToggleTheme, 32 - onToggleMotion 33 }: AppHeaderProps) { 34 const [showMenu, setShowMenu] = useState(false); 35 const menuRef = useRef<HTMLDivElement>(null); ··· 40 setShowMenu(false); 41 } 42 } 43 - document.addEventListener('mousedown', handleClickOutside); 44 - return () => document.removeEventListener('mousedown', handleClickOutside); 45 }, []); 46 47 return ( 48 - <div className="bg-white/95 dark:bg-slate-800/95 border-b-2 border-slate-200 dark:border-slate-700 backdrop-blur-sm relative z-[100]"> 49 - <div className="max-w-6xl mx-auto px-4 py-3"> 50 <div className="flex items-center justify-between"> 51 - <button 52 - onClick={() => onNavigate(session ? 'home' : 'login')} 53 - className="flex items-center space-x-3 hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-firefly-orange rounded-lg px-2 py-1" 54 > 55 - <div className="w-10 h-10 bg-gradient-to-br from-firefly-amber via-firefly-orange to-firefly-pink rounded-xl flex items-center justify-center shadow-md"> 56 - <Heart className="w-5 h-5 text-slate-900" /> 57 - </div> 58 - <h1 className="text-xl font-bold text-slate-900 dark:text-slate-100">ATlast</h1> 59 </button> 60 61 <div className="flex items-center space-x-4"> ··· 69 )} 70 {session && ( 71 <div className="relative z-[9999]" ref={menuRef}> 72 - <button 73 - onClick={() => setShowMenu(!showMenu)} 74 - className="flex items-center space-x-3 px-3 py-2 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors focus:outline-none focus:ring-2 focus:ring-firefly-orange" 75 > 76 {session?.avatar ? ( 77 - <img src={session.avatar} alt="" className="w-8 h-8 rounded-full object-cover" /> 78 ) : ( 79 - <div className="w-8 h-8 bg-gradient-to-br from-firefly-cyan to-blue-500 rounded-full flex items-center justify-center shadow-sm"> 80 - <span className="text-white font-bold text-sm">{session?.handle?.charAt(0).toUpperCase()}</span> 81 </div> 82 )} 83 - <span className="text-sm font-medium text-slate-900 dark:text-slate-100 hidden sm:inline">@{session?.handle}</span> 84 - <ChevronDown className={`w-4 h-4 text-slate-600 dark:text-slate-400 transition-transform ${showMenu ? 'rotate-180' : ''}`} /> 85 </button> 86 87 {showMenu && ( 88 - <div className="absolute right-0 mt-2 w-64 bg-white dark:bg-slate-800 rounded-lg shadow-lg border-2 border-slate-200 dark:border-slate-700 py-2 z-[9999]"> 89 - <div className="px-4 py-3 border-b-2 border-slate-200 dark:border-slate-700"> 90 - <div className="font-semibold text-slate-900 dark:text-slate-100">{session?.displayName || session.handle}</div> 91 - <div className="text-sm text-slate-600 dark:text-slate-400">@{session?.handle}</div> 92 </div> 93 - <button 94 - onClick={() => { setShowMenu(false); onNavigate('home'); }} 95 className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 96 > 97 <Home className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 98 - <span className="text-slate-900 dark:text-slate-100">Dashboard</span> 99 </button> 100 - <button 101 - onClick={() => { setShowMenu(false); onNavigate('login'); }} 102 className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 103 > 104 <Heart className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 105 - <span className="text-slate-900 dark:text-slate-100">About</span> 106 </button> 107 - <div className="border-t-2 border-slate-200 dark:border-slate-700 my-2"></div> 108 - <button 109 - onClick={() => { setShowMenu(false); onLogout(); }} 110 - className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-left text-red-600 dark:text-red-400" 111 > 112 <LogOut className="w-4 h-4" /> 113 <span>Log out</span> ··· 121 </div> 122 </div> 123 ); 124 - }
··· 1 import { useState, useEffect, useRef } from "react"; 2 import { Heart, Home, LogOut, ChevronDown } from "lucide-react"; 3 import ThemeControls from "./ThemeControls"; 4 + import FireflyLogo from "../assets/at-firefly-logo.svg?react"; 5 6 interface atprotoSession { 7 did: string; ··· 14 interface AppHeaderProps { 15 session: atprotoSession | null; 16 onLogout: () => void; 17 + onNavigate: (step: "home" | "login") => void; 18 currentStep: string; 19 isDark?: boolean; 20 reducedMotion?: boolean; ··· 22 onToggleMotion?: () => void; 23 } 24 25 + export default function AppHeader({ 26 + session, 27 + onLogout, 28 + onNavigate, 29 currentStep, 30 isDark = false, 31 reducedMotion = false, 32 onToggleTheme, 33 + onToggleMotion, 34 }: AppHeaderProps) { 35 const [showMenu, setShowMenu] = useState(false); 36 const menuRef = useRef<HTMLDivElement>(null); ··· 41 setShowMenu(false); 42 } 43 } 44 + document.addEventListener("mousedown", handleClickOutside); 45 + return () => document.removeEventListener("mousedown", handleClickOutside); 46 }, []); 47 48 return ( 49 + <div className="bg-white/50 dark:bg-slate-900/50 backdrop-blur-xl relative z-[100]"> 50 + <div className="max-w-6xl mx-auto px-4 py-1"> 51 <div className="flex items-center justify-between"> 52 + <button 53 + onClick={() => onNavigate(session ? "home" : "login")} 54 + className="flex items-center space-x-3 hover:opacity-80 transition-opacity focus:outline-none focus:ring-2 focus:ring-orange-500 rounded-lg px-2 py-1" 55 > 56 + <FireflyLogo className="w-12 h-12" /> 57 + <h1 className="font-display text-2xl font-bold text-purple-950 dark:text-cyan-50"> 58 + ATlast 59 + </h1> 60 </button> 61 62 <div className="flex items-center space-x-4"> ··· 70 )} 71 {session && ( 72 <div className="relative z-[9999]" ref={menuRef}> 73 + <button 74 + onClick={() => setShowMenu(!showMenu)} 75 + className="flex items-center space-x-3 px-3 py-1 rounded-lg hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors focus:outline-none focus:ring-2 focus:ring-firefly-orange" 76 > 77 {session?.avatar ? ( 78 + <img 79 + src={session.avatar} 80 + alt="" 81 + className="w-8 h-8 rounded-full object-cover" 82 + /> 83 ) : ( 84 + <div className="w-8 h-8 bg-gradient-to-br from-cyan-500 to-purple-500 rounded-full flex items-center justify-center shadow-sm"> 85 + <span className="text-white font-bold text-sm"> 86 + {session?.handle?.charAt(0).toUpperCase()} 87 + </span> 88 </div> 89 )} 90 + <span className="text-sm font-medium text-purple-950 dark:text-cyan-50 hidden sm:inline"> 91 + @{session?.handle} 92 + </span> 93 + <ChevronDown 94 + className={`w-4 h-4 text-slate-600 dark:text-slate-400 transition-transform ${showMenu ? "rotate-180" : ""}`} 95 + /> 96 </button> 97 98 {showMenu && ( 99 + <div className="absolute right-0 mt-2 w-64 bg-white dark:bg-slate-800 rounded-lg shadow-lg border-2 border-cyan-500/30 dark:border-purple-500/30 py-2 z-[9999]"> 100 + <div className="px-4 py-3"> 101 + <div className="font-semibold text-purple-950 dark:text-cyan-50"> 102 + {session?.displayName || session.handle} 103 + </div> 104 + <div className="text-sm text-slate-600 dark:text-slate-400"> 105 + @{session?.handle} 106 + </div> 107 </div> 108 + <button 109 + onClick={() => { 110 + setShowMenu(false); 111 + onNavigate("home"); 112 + }} 113 className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 114 > 115 <Home className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 116 + <span className="text-slate-900 dark:text-slate-100"> 117 + Dashboard 118 + </span> 119 </button> 120 + <button 121 + onClick={() => { 122 + setShowMenu(false); 123 + onNavigate("login"); 124 + }} 125 className="w-full flex items-center space-x-3 px-4 py-2 hover:bg-slate-100 dark:hover:bg-slate-700 transition-colors text-left" 126 > 127 <Heart className="w-4 h-4 text-slate-600 dark:text-slate-400" /> 128 + <span className="text-slate-900 dark:text-slate-100"> 129 + About 130 + </span> 131 </button> 132 + <div className="my-2"></div> 133 + <button 134 + onClick={() => { 135 + setShowMenu(false); 136 + onLogout(); 137 + }} 138 + className="w-full flex items-center space-x-3 px-4 hover:bg-red-50 dark:hover:bg-red-900/20 transition-colors text-left text-red-600 dark:text-red-400" 139 > 140 <LogOut className="w-4 h-4" /> 141 <span>Log out</span> ··· 149 </div> 150 </div> 151 ); 152 + }
+110 -36
src/index.css
··· 3 @tailwind utilities; 4 5 @layer base { 6 - body { 7 - font-family: system-ui, sans-serif; 8 - @apply bg-gradient-to-br from-amber-50 via-orange-50 to-pink-50 9 - dark:from-indigo-950 dark:via-purple-900 dark:to-slate-900 10 - text-slate-900 dark:text-slate-100 11 transition-colors duration-300; 12 - } 13 - 14 - button { 15 - cursor: pointer; 16 - } 17 } 18 19 /* Hide scrollbar but allow scrolling */ 20 .scrollbar-hide { 21 - -ms-overflow-style: none; 22 - scrollbar-width: none; 23 } 24 25 .scrollbar-hide::-webkit-scrollbar { 26 - display: none; 27 } 28 29 /* Firefly animation keyframes */ 30 @keyframes float { 31 - 0%, 100% { 32 - transform: translate(0, 0) scale(1); 33 - opacity: 0.3; 34 - } 35 - 25% { 36 - transform: translate(10px, -20px) scale(1.2); 37 - opacity: 0.8; 38 - } 39 - 50% { 40 - transform: translate(-5px, -40px) scale(1); 41 - opacity: 0.5; 42 - } 43 - 75% { 44 - transform: translate(15px, -25px) scale(1.1); 45 - opacity: 0.9; 46 - } 47 } 48 49 @keyframes glow-pulse { 50 - 0%, 100% { 51 - box-shadow: 0 0 20px rgba(251, 191, 36, 0.3); 52 - } 53 - 50% { 54 - box-shadow: 0 0 40px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3); 55 - } 56 - }
··· 3 @tailwind utilities; 4 5 @layer base { 6 + body { 7 + font-family: system-ui, sans-serif; 8 + @apply bg-gradient-to-br from-amber-50 via-orange-50 to-pink-50 9 + dark:from-indigo-950 dark:via-purple-900 dark:to-slate-900 10 + text-slate-900 dark:text-slate-100 11 transition-colors duration-300; 12 + } 13 + 14 + button { 15 + cursor: pointer; 16 + } 17 } 18 19 /* Hide scrollbar but allow scrolling */ 20 .scrollbar-hide { 21 + -ms-overflow-style: none; 22 + scrollbar-width: none; 23 } 24 25 .scrollbar-hide::-webkit-scrollbar { 26 + display: none; 27 } 28 29 /* Firefly animation keyframes */ 30 @keyframes float { 31 + 0%, 32 + 100% { 33 + transform: translate(0, 0) scale(1); 34 + opacity: 0.3; 35 + } 36 + 25% { 37 + transform: translate(10px, -20px) scale(1.2); 38 + opacity: 0.8; 39 + } 40 + 50% { 41 + transform: translate(-5px, -40px) scale(1); 42 + opacity: 0.5; 43 + } 44 + 75% { 45 + transform: translate(15px, -25px) scale(1.1); 46 + opacity: 0.9; 47 + } 48 } 49 50 @keyframes glow-pulse { 51 + 0%, 52 + 100% { 53 + box-shadow: 0 0 20px rgba(251, 191, 36, 0.3); 54 + } 55 + 50% { 56 + box-shadow: 57 + 0 0 40px rgba(251, 191, 36, 0.6), 58 + 0 0 60px rgba(251, 191, 36, 0.3); 59 + } 60 + } 61 + 62 + @keyframes svg-glow-left { 63 + 0%, 64 + 100% { 65 + opacity: 0.5; 66 + filter: blur(10px); 67 + transform: scale(1); 68 + } 69 + 50% { 70 + opacity: 1; 71 + filter: blur(15px); 72 + transform: scale(1.2); 73 + } 74 + } 75 + 76 + @keyframes svg-glow-right { 77 + 0%, 78 + 100% { 79 + opacity: 0.5; 80 + filter: blur(10px); 81 + transform: scale(1); 82 + } 83 + 50% { 84 + opacity: 1; 85 + filter: blur(15px); 86 + transform: scale(1.2); 87 + } 88 + } 89 + 90 + .logo-glow-container { 91 + position: relative; 92 + display: inline-block; 93 + } 94 + 95 + .logo-glow-container::before, 96 + .logo-glow-container::after { 97 + content: ""; 98 + position: absolute; 99 + width: 30px; 100 + height: 30px; 101 + border-radius: 50%; 102 + pointer-events: none; 103 + z-index: -1; /* Places glow behind the SVG */ 104 + } 105 + 106 + /* Left firefly glow - cyan tint */ 107 + .logo-glow-container::before { 108 + bottom: 0; 109 + left: 6%; 110 + background: radial-gradient( 111 + circle, 112 + rgba(253, 224, 71, 1) 0%, 113 + rgba(245, 158, 11, 0.8) 30%, 114 + transparent 80% 115 + ); 116 + animation: svg-glow-left 3s ease-in-out infinite; 117 + } 118 + 119 + /* Right firefly glow - orange/amber tint */ 120 + .logo-glow-container::after { 121 + bottom: 0; 122 + right: 4%; 123 + background: radial-gradient( 124 + circle, 125 + rgba(253, 224, 71, 1) 0%, 126 + rgba(245, 158, 11, 0.8) 30%, 127 + transparent 80% 128 + ); 129 + animation: svg-glow-right 3s ease-in-out infinite 0.5s; /* offset timing */ 130 + }
+151 -109
src/pages/Home.tsx
··· 1 - import { Upload, History, Settings, BookOpen, Grid3x3, ChevronRight, Sparkles } from "lucide-react"; 2 import { useState, useEffect, useRef } from "react"; 3 import AppHeader from "../components/AppHeader"; 4 import PlatformSelector from "../components/PlatformSelector"; ··· 20 interface HomePageProps { 21 session: atprotoSession | null; 22 onLogout: () => void; 23 - onNavigate: (step: 'home' | 'login') => void; 24 - onFileUpload: (e: React.ChangeEvent<HTMLInputElement>, platform: string) => void; 25 onLoadUpload: (uploadId: string) => void; 26 currentStep: string; 27 userSettings: UserSettings; ··· 33 onToggleMotion?: () => void; 34 } 35 36 - type TabId = 'upload' | 'history' | 'settings' | 'guides' | 'apps'; 37 38 - export default function HomePage({ 39 - session, 40 - onLogout, 41 - onNavigate, 42 - onFileUpload, 43 onLoadUpload, 44 currentStep, 45 userSettings, 46 onSettingsUpdate, 47 - // New props 48 reducedMotion = false, 49 isDark = false, 50 onToggleTheme, 51 - onToggleMotion 52 }: HomePageProps) { 53 - const [activeTab, setActiveTab] = useState<TabId>('upload'); 54 const [uploads, setUploads] = useState<UploadType[]>([]); 55 const [isLoading, setIsLoading] = useState(true); 56 - const [selectedPlatform, setSelectedPlatform] = useState<string>(''); 57 const [showWizard, setShowWizard] = useState(false); 58 const fileInputRef = useRef<HTMLInputElement>(null); 59 ··· 61 if (session) { 62 loadUploads(); 63 } 64 - 65 // Show wizard on first visit 66 if (!userSettings.wizardCompleted) { 67 setShowWizard(true); ··· 74 const data = await apiClient.getUploads(); 75 setUploads(data.uploads); 76 } catch (error) { 77 - console.error('Failed to load uploads:', error); 78 } finally { 79 setIsLoading(false); 80 } ··· 87 88 const formatDate = (dateString: string) => { 89 const date = new Date(dateString); 90 - return date.toLocaleDateString('en-US', { 91 - month: 'short', 92 - day: 'numeric', 93 - year: 'numeric', 94 - hour: '2-digit', 95 - minute: '2-digit' 96 }); 97 }; 98 99 const getPlatformColor = (platform: string) => { 100 const colors: Record<string, string> = { 101 - tiktok: 'from-black via-gray-800 to-cyan-400', 102 - twitter: 'from-blue-400 to-blue-600', 103 - instagram: 'from-pink-500 via-purple-500 to-orange-500', 104 }; 105 - return colors[platform] || 'from-gray-400 to-gray-600'; 106 }; 107 108 const tabs = [ 109 - { id: 'upload' as TabId, icon: Upload, label: 'Upload' }, 110 - { id: 'history' as TabId, icon: History, label: 'History' }, 111 - { id: 'settings' as TabId, icon: Settings, label: 'Settings' }, 112 - { id: 'guides' as TabId, icon: BookOpen, label: 'Guides' }, 113 - { id: 'apps' as TabId, icon: Grid3x3, label: 'Apps' }, 114 ]; 115 116 return ( ··· 123 /> 124 125 {/* Header */} 126 - <div className="bg-white dark:bg-gray-800 border-b border-gray-200 dark:border-gray-700"> 127 - <AppHeader 128 - session={session} 129 - onLogout={onLogout} 130 - onNavigate={onNavigate} 131 currentStep={currentStep} 132 isDark={isDark} 133 reducedMotion={reducedMotion} ··· 139 <div className="max-w-6xl mx-auto"> 140 <div className="overflow-x-auto scrollbar-hide px-4"> 141 <div className="flex space-x-1 border-b border-gray-200 dark:border-gray-700 min-w-max"> 142 - {tabs.map(tab => { 143 const Icon = tab.icon; 144 return ( 145 <button ··· 147 onClick={() => setActiveTab(tab.id)} 148 className={`flex items-center space-x-2 px-4 py-3 border-b-2 transition-all whitespace-nowrap ${ 149 activeTab === tab.id 150 - ? 'border-blue-500 text-blue-600 dark:text-blue-400' 151 - : 'border-transparent text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100' 152 }`} 153 > 154 <Icon className="w-4 h-4" /> ··· 163 164 {/* Tab Content */} 165 <div className="max-w-6xl mx-auto px-4 py-8"> 166 - {activeTab === 'upload' && ( 167 <div className="space-y-6"> 168 {/* Setup Assistant Banner - Only show if wizard not completed */} 169 {!userSettings.wizardCompleted && ( 170 <div className="bg-firefly-banner dark:bg-firefly-banner-dark rounded-2xl p-6 text-white"> 171 <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4"> 172 <div className="flex-1"> 173 - <h2 className="text-2xl font-bold mb-2">Need help getting started?</h2> 174 - <p className="text-white/90">Run the setup assistant to configure your preferences in minutes.</p> 175 </div> 176 <button 177 onClick={() => setShowWizard(true)} ··· 210 </button> 211 )} 212 </div> 213 - 214 <PlatformSelector onPlatformSelect={handlePlatformSelect} /> 215 - 216 <input 217 id="file-upload" 218 ref={fileInputRef} 219 type="file" 220 accept=".txt,.json,.html,.zip" 221 - onChange={(e) => onFileUpload(e, selectedPlatform || 'tiktok')} 222 className="sr-only" 223 aria-label="Upload following data file" 224 /> ··· 227 )} 228 229 {/* History Tab */} 230 - {activeTab === 'history' && ( 231 <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 232 <div className="flex items-center space-x-3 mb-6"> 233 <Sparkles className="w-6 h-6 text-firefly-amber" /> ··· 236 </h2> 237 </div> 238 239 - {isLoading ? ( 240 - <div className="space-y-3"> 241 - {[...Array(3)].map((_, i) => ( 242 - <div key={i} className="animate-pulse flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-700 rounded-xl"> 243 - <div className="w-12 h-12 bg-slate-200 dark:bg-slate-600 rounded-xl" /> 244 - <div className="flex-1 space-y-2"> 245 - <div className="h-4 bg-slate-200 dark:bg-slate-600 rounded w-3/4" /> 246 - <div className="h-3 bg-slate-200 dark:bg-slate-600 rounded w-1/2" /> 247 - </div> 248 - </div> 249 - ))} 250 - </div> 251 - ) : uploads.length === 0 ? ( 252 - <div className="text-center py-12"> 253 - <Upload className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-4" /> 254 - <p className="text-slate-600 dark:text-slate-400 font-medium">No previous uploads yet</p> 255 - <p className="text-sm text-slate-500 dark:text-slate-500 mt-2"> 256 - Upload your first file to get started 257 - </p> 258 - </div> 259 - ) : ( 260 - <div className="space-y-3"> 261 - {uploads.map((upload) => { 262 - const destApp = ATPROTO_APPS[userSettings.platformDestinations[upload.sourcePlatform as keyof typeof userSettings.platformDestinations]]; 263 - return ( 264 - <button 265 - key={upload.uploadId} 266 - onClick={() => onLoadUpload(upload.uploadId)} 267 - className="w-full flex items-start space-x-4 p-4 bg-slate-50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-900/70 rounded-xl transition-all text-left border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange shadow-md hover:shadow-lg" 268 > 269 - <div className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0 shadow-md`}> 270 - <Sparkles className="w-6 h-6 text-white" /> 271 </div> 272 - <div className="flex-1 min-w-0"> 273 - <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2 mb-1"> 274 - <div className="font-semibold text-slate-900 dark:text-slate-100 capitalize"> 275 - {upload.sourcePlatform} 276 - </div> 277 - <div className="flex items-center gap-2 flex-shrink-0"> 278 - <span className="text-xs px-2 py-0.5 bg-firefly-amber/20 dark:bg-firefly-amber/30 text-amber-900 dark:text-firefly-glow rounded-full font-medium border border-firefly-amber/20 dark:border-firefly-amber/50 whitespace-nowrap"> 279 - {upload.matchedUsers} {upload.matchedUsers === 1 ? 'firefly' : 'fireflies'} 280 - </span> 281 - <div className="text-sm text-slate-600 dark:text-slate-400 font-medium whitespace-nowrap"> 282 - {Math.round((upload.matchedUsers / upload.totalUsers) * 100)}% 283 </div> 284 </div> 285 - </div> 286 - <div className="text-sm text-slate-700 dark:text-slate-300"> 287 - {upload.totalUsers} users • {formatDate(upload.createdAt)} 288 </div> 289 - {destApp && ( 290 - <div className="text-xs text-gray-500 dark:text-gray-400 mt-1"> 291 - Sent to {destApp.icon} {destApp.name} 292 - </div> 293 - )} 294 - </div> 295 - </button> 296 - ); 297 - })} 298 - </div> 299 - )} 300 </div> 301 )} 302 303 {/* Settings Tab */} 304 - {activeTab === 'settings' && ( 305 <SettingsPage 306 userSettings={userSettings} 307 onSettingsUpdate={onSettingsUpdate} ··· 310 )} 311 312 {/* Guides Tab - Placeholder */} 313 - {activeTab === 'guides' && ( 314 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 315 <div className="flex items-center space-x-3 mb-6"> 316 <BookOpen className="w-6 h-6 text-gray-600 dark:text-gray-400" /> 317 - <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">Platform Guides</h2> 318 </div> 319 - <p className="text-gray-600 dark:text-gray-400">Export guides coming soon...</p> 320 </div> 321 )} 322 323 {/* Apps Tab - Placeholder */} 324 - {activeTab === 'apps' && ( 325 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 326 <div className="flex items-center space-x-3 mb-6"> 327 <Grid3x3 className="w-6 h-6 text-gray-600 dark:text-gray-400" /> 328 - <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100">ATmosphere Apps</h2> 329 </div> 330 - <p className="text-gray-600 dark:text-gray-400">Apps directory coming soon...</p> 331 </div> 332 )} 333 </div> 334 </div> 335 ); 336 - }
··· 1 + import { 2 + Upload, 3 + History, 4 + Settings, 5 + BookOpen, 6 + Grid3x3, 7 + ChevronRight, 8 + Sparkles, 9 + } from "lucide-react"; 10 import { useState, useEffect, useRef } from "react"; 11 import AppHeader from "../components/AppHeader"; 12 import PlatformSelector from "../components/PlatformSelector"; ··· 28 interface HomePageProps { 29 session: atprotoSession | null; 30 onLogout: () => void; 31 + onNavigate: (step: "home" | "login") => void; 32 + onFileUpload: ( 33 + e: React.ChangeEvent<HTMLInputElement>, 34 + platform: string, 35 + ) => void; 36 onLoadUpload: (uploadId: string) => void; 37 currentStep: string; 38 userSettings: UserSettings; ··· 44 onToggleMotion?: () => void; 45 } 46 47 + type TabId = "upload" | "history" | "settings" | "guides" | "apps"; 48 49 + export default function HomePage({ 50 + session, 51 + onLogout, 52 + onNavigate, 53 + onFileUpload, 54 onLoadUpload, 55 currentStep, 56 userSettings, 57 onSettingsUpdate, 58 reducedMotion = false, 59 isDark = false, 60 onToggleTheme, 61 + onToggleMotion, 62 }: HomePageProps) { 63 + const [activeTab, setActiveTab] = useState<TabId>("upload"); 64 const [uploads, setUploads] = useState<UploadType[]>([]); 65 const [isLoading, setIsLoading] = useState(true); 66 + const [selectedPlatform, setSelectedPlatform] = useState<string>(""); 67 const [showWizard, setShowWizard] = useState(false); 68 const fileInputRef = useRef<HTMLInputElement>(null); 69 ··· 71 if (session) { 72 loadUploads(); 73 } 74 + 75 // Show wizard on first visit 76 if (!userSettings.wizardCompleted) { 77 setShowWizard(true); ··· 84 const data = await apiClient.getUploads(); 85 setUploads(data.uploads); 86 } catch (error) { 87 + console.error("Failed to load uploads:", error); 88 } finally { 89 setIsLoading(false); 90 } ··· 97 98 const formatDate = (dateString: string) => { 99 const date = new Date(dateString); 100 + return date.toLocaleDateString("en-US", { 101 + month: "short", 102 + day: "numeric", 103 + year: "numeric", 104 + hour: "2-digit", 105 + minute: "2-digit", 106 }); 107 }; 108 109 const getPlatformColor = (platform: string) => { 110 const colors: Record<string, string> = { 111 + tiktok: "from-black via-gray-800 to-cyan-400", 112 + twitter: "from-blue-400 to-blue-600", 113 + instagram: "from-pink-500 via-purple-500 to-orange-500", 114 }; 115 + return colors[platform] || "from-gray-400 to-gray-600"; 116 }; 117 118 const tabs = [ 119 + { id: "upload" as TabId, icon: Upload, label: "Upload" }, 120 + { id: "history" as TabId, icon: History, label: "History" }, 121 + { id: "settings" as TabId, icon: Settings, label: "Settings" }, 122 + { id: "guides" as TabId, icon: BookOpen, label: "Guides" }, 123 + { id: "apps" as TabId, icon: Grid3x3, label: "Apps" }, 124 ]; 125 126 return ( ··· 133 /> 134 135 {/* Header */} 136 + <div className="bg-white dark:bg-slate-900 border-b-2 border-cyan-500/50 dark:border-purple-500/50 overflow-x-auto"> 137 + <AppHeader 138 + session={session} 139 + onLogout={onLogout} 140 + onNavigate={onNavigate} 141 currentStep={currentStep} 142 isDark={isDark} 143 reducedMotion={reducedMotion} ··· 149 <div className="max-w-6xl mx-auto"> 150 <div className="overflow-x-auto scrollbar-hide px-4"> 151 <div className="flex space-x-1 border-b border-gray-200 dark:border-gray-700 min-w-max"> 152 + {tabs.map((tab) => { 153 const Icon = tab.icon; 154 return ( 155 <button ··· 157 onClick={() => setActiveTab(tab.id)} 158 className={`flex items-center space-x-2 px-4 py-3 border-b-2 transition-all whitespace-nowrap ${ 159 activeTab === tab.id 160 + ? "border-orange-500 dark:border-amber-500 text-orange-650 dark:text-amber-400" 161 + : "border-transparent text-purple-750 dark:text-cyan-250 hover:text-purple-900 dark:hover:text-cyan-100" 162 }`} 163 > 164 <Icon className="w-4 h-4" /> ··· 173 174 {/* Tab Content */} 175 <div className="max-w-6xl mx-auto px-4 py-8"> 176 + {activeTab === "upload" && ( 177 <div className="space-y-6"> 178 {/* Setup Assistant Banner - Only show if wizard not completed */} 179 {!userSettings.wizardCompleted && ( 180 <div className="bg-firefly-banner dark:bg-firefly-banner-dark rounded-2xl p-6 text-white"> 181 <div className="flex flex-col md:flex-row items-start md:items-center justify-between gap-4"> 182 <div className="flex-1"> 183 + <h2 className="text-2xl font-bold mb-2"> 184 + Need help getting started? 185 + </h2> 186 + <p className="text-white/90"> 187 + Run the setup assistant to configure your preferences in 188 + minutes. 189 + </p> 190 </div> 191 <button 192 onClick={() => setShowWizard(true)} ··· 225 </button> 226 )} 227 </div> 228 + 229 <PlatformSelector onPlatformSelect={handlePlatformSelect} /> 230 + 231 <input 232 id="file-upload" 233 ref={fileInputRef} 234 type="file" 235 accept=".txt,.json,.html,.zip" 236 + onChange={(e) => onFileUpload(e, selectedPlatform || "tiktok")} 237 className="sr-only" 238 aria-label="Upload following data file" 239 /> ··· 242 )} 243 244 {/* History Tab */} 245 + {activeTab === "history" && ( 246 <div className="bg-white/95 dark:bg-slate-800/95 backdrop-blur-xl rounded-2xl shadow-lg p-6 border-2 border-slate-200 dark:border-slate-700"> 247 <div className="flex items-center space-x-3 mb-6"> 248 <Sparkles className="w-6 h-6 text-firefly-amber" /> ··· 251 </h2> 252 </div> 253 254 + {isLoading ? ( 255 + <div className="space-y-3"> 256 + {[...Array(3)].map((_, i) => ( 257 + <div 258 + key={i} 259 + className="animate-pulse flex items-center space-x-4 p-4 bg-slate-50 dark:bg-slate-700 rounded-xl" 260 > 261 + <div className="w-12 h-12 bg-slate-200 dark:bg-slate-600 rounded-xl" /> 262 + <div className="flex-1 space-y-2"> 263 + <div className="h-4 bg-slate-200 dark:bg-slate-600 rounded w-3/4" /> 264 + <div className="h-3 bg-slate-200 dark:bg-slate-600 rounded w-1/2" /> 265 </div> 266 + </div> 267 + ))} 268 + </div> 269 + ) : uploads.length === 0 ? ( 270 + <div className="text-center py-12"> 271 + <Upload className="w-16 h-16 text-slate-300 dark:text-slate-600 mx-auto mb-4" /> 272 + <p className="text-slate-600 dark:text-slate-400 font-medium"> 273 + No previous uploads yet 274 + </p> 275 + <p className="text-sm text-slate-500 dark:text-slate-500 mt-2"> 276 + Upload your first file to get started 277 + </p> 278 + </div> 279 + ) : ( 280 + <div className="space-y-3"> 281 + {uploads.map((upload) => { 282 + const destApp = 283 + ATPROTO_APPS[ 284 + userSettings.platformDestinations[ 285 + upload.sourcePlatform as keyof typeof userSettings.platformDestinations 286 + ] 287 + ]; 288 + return ( 289 + <button 290 + key={upload.uploadId} 291 + onClick={() => onLoadUpload(upload.uploadId)} 292 + className="w-full flex items-start space-x-4 p-4 bg-slate-50 dark:bg-slate-900/50 hover:bg-slate-100 dark:hover:bg-slate-900/70 rounded-xl transition-all text-left border-2 border-slate-200 dark:border-slate-700 hover:border-firefly-orange dark:hover:border-firefly-orange shadow-md hover:shadow-lg" 293 + > 294 + <div 295 + className={`w-12 h-12 bg-gradient-to-r ${getPlatformColor(upload.sourcePlatform)} rounded-xl flex items-center justify-center flex-shrink-0 shadow-md`} 296 + > 297 + <Sparkles className="w-6 h-6 text-white" /> 298 + </div> 299 + <div className="flex-1 min-w-0"> 300 + <div className="flex flex-wrap items-start justify-between gap-x-4 gap-y-2 mb-1"> 301 + <div className="font-semibold text-slate-900 dark:text-slate-100 capitalize"> 302 + {upload.sourcePlatform} 303 </div> 304 + <div className="flex items-center gap-2 flex-shrink-0"> 305 + <span className="text-xs px-2 py-0.5 bg-firefly-amber/20 dark:bg-firefly-amber/30 text-amber-900 dark:text-firefly-glow rounded-full font-medium border border-firefly-amber/20 dark:border-firefly-amber/50 whitespace-nowrap"> 306 + {upload.matchedUsers}{" "} 307 + {upload.matchedUsers === 1 308 + ? "firefly" 309 + : "fireflies"} 310 + </span> 311 + <div className="text-sm text-slate-600 dark:text-slate-400 font-medium whitespace-nowrap"> 312 + {Math.round( 313 + (upload.matchedUsers / upload.totalUsers) * 100, 314 + )} 315 + % 316 + </div> 317 + </div> 318 + </div> 319 + <div className="text-sm text-slate-700 dark:text-slate-300"> 320 + {upload.totalUsers} users •{" "} 321 + {formatDate(upload.createdAt)} 322 </div> 323 + {destApp && ( 324 + <div className="text-xs text-gray-500 dark:text-gray-400 mt-1"> 325 + Sent to {destApp.icon} {destApp.name} 326 + </div> 327 + )} 328 </div> 329 + </button> 330 + ); 331 + })} 332 + </div> 333 + )} 334 </div> 335 )} 336 337 {/* Settings Tab */} 338 + {activeTab === "settings" && ( 339 <SettingsPage 340 userSettings={userSettings} 341 onSettingsUpdate={onSettingsUpdate} ··· 344 )} 345 346 {/* Guides Tab - Placeholder */} 347 + {activeTab === "guides" && ( 348 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 349 <div className="flex items-center space-x-3 mb-6"> 350 <BookOpen className="w-6 h-6 text-gray-600 dark:text-gray-400" /> 351 + <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100"> 352 + Platform Guides 353 + </h2> 354 </div> 355 + <p className="text-gray-600 dark:text-gray-400"> 356 + Export guides coming soon... 357 + </p> 358 </div> 359 )} 360 361 {/* Apps Tab - Placeholder */} 362 + {activeTab === "apps" && ( 363 <div className="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6"> 364 <div className="flex items-center space-x-3 mb-6"> 365 <Grid3x3 className="w-6 h-6 text-gray-600 dark:text-gray-400" /> 366 + <h2 className="text-xl font-bold text-gray-900 dark:text-gray-100"> 367 + ATmosphere Apps 368 + </h2> 369 </div> 370 + <p className="text-gray-600 dark:text-gray-400"> 371 + Apps directory coming soon... 372 + </p> 373 </div> 374 )} 375 </div> 376 </div> 377 ); 378 + }
+110 -53
src/pages/Login.tsx
··· 1 import { useState } from "react"; 2 import { Heart, Upload, Search, ArrowRight } from "lucide-react"; 3 4 interface LoginPageProps { 5 onSubmit: (handle: string) => void; 6 session?: { handle: string } | null; 7 - onNavigate?: (step: 'home') => void; 8 reducedMotion?: boolean; 9 } 10 11 - export default function LoginPage({ onSubmit, session, onNavigate, reducedMotion = false }: LoginPageProps) { 12 const [handle, setHandle] = useState(""); 13 - 14 const handleSubmit = (e: React.FormEvent) => { 15 e.preventDefault(); 16 onSubmit(handle); 17 }; 18 - 19 return ( 20 <div className="min-h-screen"> 21 <div className="max-w-6xl mx-auto px-4 py-8 md:py-12"> 22 - 23 {/* Hero Section - Side by side on desktop */} 24 <div className="grid md:grid-cols-2 gap-8 md:gap-12 items-start mb-12 md:mb-16"> 25 {/* Left: Welcome */} 26 <div className="text-center md:text-left"> 27 - <div className="inline-flex items-center justify-center mb-6 relative"> 28 - <div 29 - className={`w-20 h-20 md:w-24 md:h-24 bg-gradient-to-br from-firefly-amber via-firefly-orange to-firefly-pink rounded-3xl flex items-center justify-center relative shadow-xl ${ 30 - reducedMotion ? '' : 'animate-glow-pulse' 31 - }`} 32 - > 33 - <Heart className="w-10 h-10 md:w-12 md:h-12 text-slate-900" aria-hidden="true" /> 34 - {/* Firefly mascot hint */} 35 - <div 36 - className={`absolute -top-2 -right-2 w-8 h-8 bg-firefly-glow rounded-full flex items-center justify-center shadow-lg ${ 37 - reducedMotion ? '' : 'animate-bounce' 38 - }`} 39 - aria-hidden="true" 40 - > 41 - <div className="w-4 h-4 bg-firefly-amber rounded-full" /> 42 - </div> 43 </div> 44 </div> 45 - 46 <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-slate-900 dark:text-white mb-3 md:mb-4"> 47 ATlast 48 </h1> ··· 52 <p className="text-slate-700 dark:text-slate-300 mb-6"> 53 Reconnect with your internet, one firefly at a time ✨ 54 </p> 55 - 56 {/* Decorative firefly trail - only show if motion enabled */} 57 {!reducedMotion && ( 58 - <div className="mt-8 flex justify-center md:justify-start space-x-2" aria-hidden="true"> 59 {[...Array(5)].map((_, i) => ( 60 <div 61 key={i} ··· 63 style={{ 64 opacity: 1 - i * 0.15, 65 animation: `float ${2 + i * 0.3}s ease-in-out infinite`, 66 - animationDelay: `${i * 0.2}s` 67 }} 68 /> 69 ))} 70 </div> 71 )} 72 - 73 {/* Privacy Notice - visible on mobile */} 74 <div className="md:hidden mt-6"> 75 <p className="text-sm text-slate-600 dark:text-slate-400"> 76 - Your data is processed and stored by our servers. This helps you find matches and reconnect with your community. 77 </p> 78 </div> 79 </div> ··· 95 </div> 96 97 <button 98 - onClick={() => onNavigate?.('home')} 99 className="w-full bg-gradient-to-r from-firefly-amber via-firefly-orange to-firefly-pink hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800 focus:outline-none flex items-center justify-center space-x-2" 100 > 101 <span>Go to Dashboard</span> ··· 111 Connect your ATmosphere account to begin 112 </p> 113 114 - <form onSubmit={handleSubmit} className="space-y-4" method="post"> 115 <div> 116 - <label htmlFor="atproto-handle" className="block text-sm font-semibold text-slate-900 dark:text-slate-100 mb-2"> 117 Your ATmosphere Handle 118 </label> 119 <input ··· 126 aria-required="true" 127 aria-describedby="handle-description" 128 /> 129 - <p id="handle-description" className="text-xs text-slate-600 dark:text-slate-400 mt-2"> 130 - Enter your full ATmosphere handle (e.g., username.bsky.social or yourname.com) 131 </p> 132 </div> 133 ··· 142 143 <div className="mt-6 pt-6 border-t-2 border-slate-200 dark:border-slate-700"> 144 <div className="flex items-start space-x-2 text-sm text-slate-700 dark:text-slate-300"> 145 - <svg className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"> 146 - <path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" /> 147 </svg> 148 <div> 149 - <p className="font-semibold text-slate-900 dark:text-slate-100">Secure OAuth Connection</p> 150 - <p className="text-xs mt-1">We use official AT Protocol OAuth. We never see your password and you can revoke access anytime.</p> 151 </div> 152 </div> 153 </div> ··· 166 Share Your Light 167 </h3> 168 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 169 - Import your following lists. Your data stays private, your connections shine bright. 170 </p> 171 </div> 172 ··· 178 Find Your Swarm 179 </h3> 180 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 181 - Watch as fireflies light up - discover which friends have already migrated to the ATmosphere. 182 </p> 183 </div> 184 ··· 190 Sync Your Glow 191 </h3> 192 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 193 - Reconnect instantly. Follow everyone at once or pick and choose - light up together. 194 </p> 195 </div> 196 </div> ··· 198 {/* Privacy Notice - desktop only */} 199 <div className="hidden md:block text-center mb-8"> 200 <p className="text-sm text-slate-600 dark:text-slate-400 max-w-2xl mx-auto"> 201 - Your data is processed and stored by our servers. This helps you find matches and reconnect with your community. 202 </p> 203 </div> 204 ··· 209 </h2> 210 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 211 <div className="text-center"> 212 - <div className="w-12 h-12 bg-firefly-orange text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 213 1 214 </div> 215 - <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Connect</h3> 216 - <p className="text-sm text-slate-700 dark:text-slate-300">Sign in with your ATmosphere account</p> 217 </div> 218 <div className="text-center"> 219 - <div className="w-12 h-12 bg-firefly-pink text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 220 2 221 </div> 222 - <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Upload</h3> 223 - <p className="text-sm text-slate-700 dark:text-slate-300">Import your following data from other platforms</p> 224 </div> 225 <div className="text-center"> 226 - <div className="w-12 h-12 bg-firefly-cyan text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 227 3 228 </div> 229 - <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Match</h3> 230 - <p className="text-sm text-slate-700 dark:text-slate-300">We find your fireflies in the ATmosphere</p> 231 </div> 232 <div className="text-center"> 233 - <div className="w-12 h-12 bg-firefly-amber text-slate-900 rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" aria-hidden="true"> 234 4 235 </div> 236 - <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Follow</h3> 237 - <p className="text-sm text-slate-700 dark:text-slate-300">Reconnect with your community</p> 238 </div> 239 </div> 240 </div> 241 </div> 242 </div> 243 ); 244 - }
··· 1 import { useState } from "react"; 2 import { Heart, Upload, Search, ArrowRight } from "lucide-react"; 3 + import FireflyLogo from "../assets/at-firefly-logo.svg?react"; 4 5 interface LoginPageProps { 6 onSubmit: (handle: string) => void; 7 session?: { handle: string } | null; 8 + onNavigate?: (step: "home") => void; 9 reducedMotion?: boolean; 10 } 11 12 + export default function LoginPage({ 13 + onSubmit, 14 + session, 15 + onNavigate, 16 + reducedMotion = false, 17 + }: LoginPageProps) { 18 const [handle, setHandle] = useState(""); 19 + 20 const handleSubmit = (e: React.FormEvent) => { 21 e.preventDefault(); 22 onSubmit(handle); 23 }; 24 + 25 return ( 26 <div className="min-h-screen"> 27 <div className="max-w-6xl mx-auto px-4 py-8 md:py-12"> 28 {/* Hero Section - Side by side on desktop */} 29 <div className="grid md:grid-cols-2 gap-8 md:gap-12 items-start mb-12 md:mb-16"> 30 {/* Left: Welcome */} 31 <div className="text-center md:text-left"> 32 + <div className="justify-center md:justify-start mb-4"> 33 + <div className="logo-glow-container"> 34 + <img 35 + src="src/assets/at-firefly-logo.svg" 36 + className="w-50 h-15" 37 + alt="ATlast logo" 38 + /> 39 </div> 40 </div> 41 + 42 <h1 className="text-4xl md:text-5xl lg:text-6xl font-bold text-slate-900 dark:text-white mb-3 md:mb-4"> 43 ATlast 44 </h1> ··· 48 <p className="text-slate-700 dark:text-slate-300 mb-6"> 49 Reconnect with your internet, one firefly at a time ✨ 50 </p> 51 + 52 {/* Decorative firefly trail - only show if motion enabled */} 53 {!reducedMotion && ( 54 + <div 55 + className="mt-8 flex justify-center md:justify-start space-x-2" 56 + aria-hidden="true" 57 + > 58 {[...Array(5)].map((_, i) => ( 59 <div 60 key={i} ··· 62 style={{ 63 opacity: 1 - i * 0.15, 64 animation: `float ${2 + i * 0.3}s ease-in-out infinite`, 65 + animationDelay: `${i * 0.2}s`, 66 }} 67 /> 68 ))} 69 </div> 70 )} 71 + 72 {/* Privacy Notice - visible on mobile */} 73 <div className="md:hidden mt-6"> 74 <p className="text-sm text-slate-600 dark:text-slate-400"> 75 + Your data is processed and stored by our servers. This helps you 76 + find matches and reconnect with your community. 77 </p> 78 </div> 79 </div> ··· 95 </div> 96 97 <button 98 + onClick={() => onNavigate?.("home")} 99 className="w-full bg-gradient-to-r from-firefly-amber via-firefly-orange to-firefly-pink hover:from-amber-600 hover:via-orange-600 hover:to-pink-600 text-white py-4 rounded-xl font-bold text-lg transition-all shadow-lg hover:shadow-xl focus:ring-4 focus:ring-orange-300 dark:focus:ring-orange-800 focus:outline-none flex items-center justify-center space-x-2" 100 > 101 <span>Go to Dashboard</span> ··· 111 Connect your ATmosphere account to begin 112 </p> 113 114 + <form 115 + onSubmit={handleSubmit} 116 + className="space-y-4" 117 + method="post" 118 + > 119 <div> 120 + <label 121 + htmlFor="atproto-handle" 122 + className="block text-sm font-semibold text-slate-900 dark:text-slate-100 mb-2" 123 + > 124 Your ATmosphere Handle 125 </label> 126 <input ··· 133 aria-required="true" 134 aria-describedby="handle-description" 135 /> 136 + <p 137 + id="handle-description" 138 + className="text-xs text-slate-600 dark:text-slate-400 mt-2" 139 + > 140 + Enter your full ATmosphere handle (e.g., 141 + username.bsky.social or yourname.com) 142 </p> 143 </div> 144 ··· 153 154 <div className="mt-6 pt-6 border-t-2 border-slate-200 dark:border-slate-700"> 155 <div className="flex items-start space-x-2 text-sm text-slate-700 dark:text-slate-300"> 156 + <svg 157 + className="w-5 h-5 text-green-500 flex-shrink-0 mt-0.5" 158 + fill="currentColor" 159 + viewBox="0 0 20 20" 160 + aria-hidden="true" 161 + > 162 + <path 163 + fillRule="evenodd" 164 + d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" 165 + clipRule="evenodd" 166 + /> 167 </svg> 168 <div> 169 + <p className="font-semibold text-slate-900 dark:text-slate-100"> 170 + Secure OAuth Connection 171 + </p> 172 + <p className="text-xs mt-1"> 173 + We use official AT Protocol OAuth. We never see your 174 + password and you can revoke access anytime. 175 + </p> 176 </div> 177 </div> 178 </div> ··· 191 Share Your Light 192 </h3> 193 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 194 + Import your following lists. Your data stays private, your 195 + connections shine bright. 196 </p> 197 </div> 198 ··· 204 Find Your Swarm 205 </h3> 206 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 207 + Watch as fireflies light up - discover which friends have already 208 + migrated to the ATmosphere. 209 </p> 210 </div> 211 ··· 217 Sync Your Glow 218 </h3> 219 <p className="text-slate-700 dark:text-slate-300 text-sm leading-relaxed"> 220 + Reconnect instantly. Follow everyone at once or pick and choose - 221 + light up together. 222 </p> 223 </div> 224 </div> ··· 226 {/* Privacy Notice - desktop only */} 227 <div className="hidden md:block text-center mb-8"> 228 <p className="text-sm text-slate-600 dark:text-slate-400 max-w-2xl mx-auto"> 229 + Your data is processed and stored by our servers. This helps you 230 + find matches and reconnect with your community. 231 </p> 232 </div> 233 ··· 238 </h2> 239 <div className="grid grid-cols-2 md:grid-cols-4 gap-4"> 240 <div className="text-center"> 241 + <div 242 + className="w-12 h-12 bg-firefly-orange text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" 243 + aria-hidden="true" 244 + > 245 1 246 </div> 247 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1"> 248 + Connect 249 + </h3> 250 + <p className="text-sm text-slate-700 dark:text-slate-300"> 251 + Sign in with your ATmosphere account 252 + </p> 253 </div> 254 <div className="text-center"> 255 + <div 256 + className="w-12 h-12 bg-firefly-pink text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" 257 + aria-hidden="true" 258 + > 259 2 260 </div> 261 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1"> 262 + Upload 263 + </h3> 264 + <p className="text-sm text-slate-700 dark:text-slate-300"> 265 + Import your following data from other platforms 266 + </p> 267 </div> 268 <div className="text-center"> 269 + <div 270 + className="w-12 h-12 bg-firefly-cyan text-white rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" 271 + aria-hidden="true" 272 + > 273 3 274 </div> 275 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1"> 276 + Match 277 + </h3> 278 + <p className="text-sm text-slate-700 dark:text-slate-300"> 279 + We find your fireflies in the ATmosphere 280 + </p> 281 </div> 282 <div className="text-center"> 283 + <div 284 + className="w-12 h-12 bg-firefly-amber text-slate-900 rounded-full flex items-center justify-center mx-auto mb-3 font-bold text-lg shadow-md" 285 + aria-hidden="true" 286 + > 287 4 288 </div> 289 + <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-1"> 290 + Follow 291 + </h3> 292 + <p className="text-sm text-slate-700 dark:text-slate-300"> 293 + Reconnect with your community 294 + </p> 295 </div> 296 </div> 297 </div> 298 </div> 299 </div> 300 ); 301 + }
+5
src/svg.d.ts
···
··· 1 + declare module "*.svg?react" { 2 + import * as React from "react"; 3 + const Component: React.FC<React.SVGProps<SVGSVGElement>>; 4 + export default Component; 5 + }
+34 -28
tailwind.config.js
··· 1 /** @type {import('tailwindcss').Config} */ 2 export default { 3 - darkMode: 'class', // Changed from 'media' to 'class' for manual control 4 - content: [ 5 - "./index.html", 6 - "./src/**/*.{js,ts,jsx,tsx}", 7 - ], 8 theme: { 9 extend: { 10 colors: { 11 firefly: { 12 - glow: '#FCD34D', // close to amber-300 13 - amber: '#F59E0B', // close to amber-500 14 - orange: '#F97316', // close to orange-500 15 - pink: '#EC4899', // close to tailwind pink-500 16 - cyan: '#10D2F4', // close to tailwind cyan-300 17 - } 18 - }, 19 - backgroundImage: { 20 - 'firefly-banner': 21 - 'linear-gradient(90deg, rgba(9,163,190,1) 0%, rgba(91,33,182,1) 33%, rgba(236,72,153,1) 67%, rgba(244,105,6,1) 100%)', 22 - 'firefly-banner-dark': 23 - 'linear-gradient(90deg, rgba(24,21,60,1) 0%, rgba(55,20,94,1) 33%, rgba(104,25,98,1) 67%, rgba(36,16,54,1) 100%)', 24 }, 25 animation: { 26 - 'float': 'float 3s ease-in-out infinite', 27 - 'glow-pulse': 'glow-pulse 3s ease-in-out infinite', 28 }, 29 keyframes: { 30 float: { 31 - '0%, 100%': { transform: 'translate(0, 0) scale(1)', opacity: '0.3' }, 32 - '25%': { transform: 'translate(10px, -20px) scale(1.2)', opacity: '0.8' }, 33 - '50%': { transform: 'translate(-5px, -40px) scale(1)', opacity: '0.5' }, 34 - '75%': { transform: 'translate(15px, -25px) scale(1.1)', opacity: '0.9' }, 35 - }, 36 - 'glow-pulse': { 37 - '0%, 100%': { boxShadow: '0 0 20px rgba(251, 191, 36, 0.3)' }, 38 - '50%': { boxShadow: '0 0 40px rgba(251, 191, 36, 0.6), 0 0 60px rgba(251, 191, 36, 0.3)' }, 39 }, 40 }, 41 }, 42 }, 43 plugins: [], 44 - }
··· 1 /** @type {import('tailwindcss').Config} */ 2 export default { 3 + darkMode: "class", // Changed from 'media' to 'class' for manual control 4 + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 5 theme: { 6 extend: { 7 colors: { 8 firefly: { 9 + glow: "#FCD34D", // close to amber-300 10 + amber: "#F59E0B", // close to amber-500 11 + orange: "#F97316", // close to orange-500 12 + pink: "#EC4899", // close to tailwind pink-500 13 + cyan: "#10D2F4", // close to tailwind cyan-300 14 + }, 15 + cyan: { 250: "#72EEFD" }, 16 + purple: { 750: "#6A1DD1" }, 17 + orange: { 650: "#DF3F00" }, 18 + yellow: { 650: "#C56508" }, 19 + orange: { 650: "#F26611" }, 20 + pink: { 650: "#CD206A" }, 21 }, 22 + backgroundImage: ({ theme }) => ({ 23 + "firefly-banner": `linear-gradient(to right, ${theme("colors.yellow.400")}, ${theme("colors.orange.500")}, ${theme("colors.pink.600")})`, 24 + "firefly-banner-dark": `linear-gradient(to right, ${theme("colors.yellow.600")}, ${theme("colors.orange.600")}, ${theme("colors.pink.700")})`, 25 + }), 26 animation: { 27 + float: "float 3s ease-in-out infinite", 28 }, 29 keyframes: { 30 float: { 31 + "0%, 100%": { transform: "translate(0, 0) scale(1)", opacity: "0.3" }, 32 + "25%": { 33 + transform: "translate(10px, -20px) scale(1.2)", 34 + opacity: "0.8", 35 + }, 36 + "50%": { 37 + transform: "translate(-5px, -40px) scale(1)", 38 + opacity: "0.5", 39 + }, 40 + "75%": { 41 + transform: "translate(15px, -25px) scale(1.1)", 42 + opacity: "0.9", 43 + }, 44 + } 45 }, 46 }, 47 }, 48 }, 49 plugins: [], 50 + };
+6 -5
vite.config.ts
··· 1 - import { defineConfig } from 'vite' 2 - import react from '@vitejs/plugin-react' 3 4 export default defineConfig({ 5 - base: '/', 6 - plugins: [react()], 7 - })
··· 1 + import { defineConfig } from "vite"; 2 + import react from "@vitejs/plugin-react"; 3 + import svgr from "vite-plugin-svgr"; 4 5 export default defineConfig({ 6 + base: "/", 7 + plugins: [react(), svgr()], 8 + });