diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..4534b9e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,28 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [Unreleased] + +### Added +- Added `.no-scrollbar` utility class in `src/index.css` to enable smooth horizontal scrolling without visible scrollbars for mobile tab navigation. +- Added `Fredoka` font (a modern groovy font) to replace the previous display font on Theme card titles. +- Added a "Team Members" toggle button with a spring-animated chevron that smoothly reveals the "Operating Partners" grid underneath the "Sub Heads" section. + +### Changed +- **Themes Section**: + - Overhauled interaction: Replaced "click-to-view" modal system with an inline smooth dropdown (hover on desktop, tap on mobile/tablet). + - Cleaned up titles: Removed legacy "2026", "Theme XX" numbering labels. + - Renamed "IOT & Smart Cities" theme to **"IOT & Sustainability"** and updated tags (added GreenTech). + - Renamed "Web3 / Sustain" theme to **"Blockchain & Fintech"** with an updated description, updated emoji to `🔗`, and tags to `Smart Contracts`, `DeFi`, `Fintech`, `Crypto`. + - Updated card titles font-weight to `normal` (400) mapping to the `Fredoka` groovy font design. +- **Team Section Header**: Replaced the slide-in (`x: ±300`) animation with a bulletproof fade-up (`y: 30 → 0`) animation to prevent the "Meet The Team" heading from clipping completely off-screen on constrained mobile displays due to `overflow-hidden`. +- **Team Section Layout**: Integrated proper CSS grid configuration (`grid-cols-2` mobile, `grid-cols-3` tablet, `grid-cols-4` desktop) over the previous fixed-width wrapping implementation. +- **Team Section Responsiveness**: + - Adjusted container to `w-[92%]` on tablet and correctly incremented padding (`px-5`, `px-6`) so the retro shadow offsets (`10px_10px`, `16px_16px`) no longer clip via the responsive bounds. + - Sub-headings now safely `clamp` sizing alongside smaller card text metrics to avoid horizontal scrollbar lock. + - Active Tab Button CSS explicitly utilizes inline style coloring (`#f3ecd2`) overriding systemic tailwind specificity cascading rendering the default black rectangle bug. + +### Removed +- Removed the standalone "Operating Partners" tab grouping, integrating it as a sub-collapse element under "Sub Heads". +- Removed hardcoded margins and large scale fonts causing overflow clipping from mobile versions (e.g., swapped `text-6xl` to `clamp(2rem, 7vw, 6rem)`). diff --git a/lint_errors.txt b/lint_errors.txt deleted file mode 100644 index f83aa98..0000000 Binary files a/lint_errors.txt and /dev/null differ diff --git a/optimize.cjs b/optimize.cjs new file mode 100644 index 0000000..48a3f63 --- /dev/null +++ b/optimize.cjs @@ -0,0 +1,31 @@ +const fs = require('fs'); +const path = require('path'); +const sharp = require('sharp'); + +const dir = path.join(__dirname, 'public/assets'); +const files = fs.readdirSync(dir).filter(f => f.startsWith('hero') && f.endsWith('.jpg')); + +(async () => { + console.log(`Found ${files.length} images to optimize.`); + let totalSaved = 0; + + for (const file of files) { + const input = path.join(dir, file); + const output = path.join(dir, file.replace('.jpg', '.webp')); + + const inputStats = fs.statSync(input); + + await sharp(input) + .resize({ width: 800, withoutEnlargement: true }) // Downscale large images + .webp({ quality: 75 }) // High quality WebP + .toFile(output); + + const outputStats = fs.statSync(output); + const saved = inputStats.size - outputStats.size; + totalSaved += saved; + + console.log(`Converted ${file} to WebP. Saved ${(saved / 1024 / 1024).toFixed(2)} MB`); + } + + console.log(`\nAll done! Total space saved: ${(totalSaved / 1024 / 1024).toFixed(2)} MB`); +})(); diff --git a/package-lock.json b/package-lock.json index 4f36767..62543c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-icons": "^5.6.0", + "react-photo-album": "^3.6.0", "react-router-dom": "^6.22.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2", @@ -37,6 +38,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "sharp": "^0.34.5", "typescript": "~5.9.3", "typescript-eslint": "^8.57.0", "vite": "^8.0.1" @@ -72,6 +74,7 @@ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -295,27 +298,6 @@ "integrity": "sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==", "license": "Apache-2.0" }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@emnapi/wasi-threads": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", @@ -535,6 +517,496 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -705,6 +1177,7 @@ "resolved": "https://registry.npmjs.org/@react-three/fiber/-/fiber-9.5.0.tgz", "integrity": "sha512-FiUzfYW4wB1+PpmsE47UM+mCads7j2+giRBltfwH7SNhah95rqJs3ltEs9V3pP8rYdS0QlNne+9Aj8dS/SiaIA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.17.8", "@types/webxr": "*", @@ -967,6 +1440,27 @@ "node": ">=14.0.0" } }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@rolldown/binding-win32-arm64-msvc": { "version": "1.0.0-rc.15", "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", @@ -1466,6 +1960,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1530,6 +2025,7 @@ "resolved": "https://registry.npmjs.org/@types/three/-/three-0.183.1.tgz", "integrity": "sha512-f2Pu5Hrepfgavttdye3PsH5RWyY/AvdZQwIVhrc4uNtvF7nOWJacQKcoVJn0S4f0yYbmAE6AR+ve7xDcuYtMGw==", "license": "MIT", + "peer": true, "dependencies": { "@dimforge/rapier3d-compat": "~0.12.0", "@tweenjs/tween.js": "~23.1.3", @@ -1600,6 +2096,7 @@ "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.58.0", "@typescript-eslint/types": "8.58.0", @@ -1919,6 +2416,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2150,6 +2648,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", @@ -2742,6 +3241,7 @@ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4780,6 +5280,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4938,6 +5439,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4947,6 +5449,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -4963,6 +5466,27 @@ "react": "*" } }, + "node_modules/react-photo-album": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/react-photo-album/-/react-photo-album-3.6.0.tgz", + "integrity": "sha512-W9NgI+0XxOYF/FLQJ/ZiKsizNQtGtgDdiFgojmTmpBKDGeGiWKfSzmbw3v9WAqzimPeiFJ2sEb9DO0nHRHP/OA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/igordanchenko" + }, + "peerDependencies": { + "@types/react": "^18 || ^19", + "react": "^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-router": { "version": "6.30.3", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.3.tgz", @@ -5263,6 +5787,64 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5629,7 +6211,8 @@ "version": "0.183.2", "resolved": "https://registry.npmjs.org/three/-/three-0.183.2.tgz", "integrity": "sha512-di3BsL2FEQ1PA7Hcvn4fyJOlxRRgFYBpMTcyOgkwJIaDOdJMebEFPA+t98EvjuljDx4hNulAGwF6KIjtwI5jgQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/three-mesh-bvh": { "version": "0.8.3", @@ -5827,6 +6410,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5978,6 +6562,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", "license": "MIT", + "peer": true, "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", @@ -6201,6 +6786,7 @@ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/package.json b/package.json index 0735241..664cfc2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "react": "^19.2.4", "react-dom": "^19.2.4", "react-icons": "^5.6.0", + "react-photo-album": "^3.6.0", "react-router-dom": "^6.22.0", "tailwind-merge": "^3.5.0", "tailwindcss": "^4.2.2", @@ -39,6 +40,7 @@ "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", + "sharp": "^0.34.5", "typescript": "~5.9.3", "typescript-eslint": "^8.57.0", "vite": "^8.0.1" diff --git a/public/assets/Jeelh-Cursor-Light/Normal Select Light.png b/public/assets/Jeelh-Cursor-Light/Normal Select Light.png new file mode 100644 index 0000000..1534946 Binary files /dev/null and b/public/assets/Jeelh-Cursor-Light/Normal Select Light.png differ diff --git a/public/assets/Jeelh-Cursor-Light/Select Light.png b/public/assets/Jeelh-Cursor-Light/Select Light.png new file mode 100644 index 0000000..9ea1fff Binary files /dev/null and b/public/assets/Jeelh-Cursor-Light/Select Light.png differ diff --git a/public/assets/Jeelh-Cursor-Light/Text Select Light.png b/public/assets/Jeelh-Cursor-Light/Text Select Light.png new file mode 100644 index 0000000..41ed853 Binary files /dev/null and b/public/assets/Jeelh-Cursor-Light/Text Select Light.png differ diff --git a/public/assets/Sugar Peachy DEMO.otf b/public/assets/Sugar Peachy DEMO.otf new file mode 100644 index 0000000..a2d3811 Binary files /dev/null and b/public/assets/Sugar Peachy DEMO.otf differ diff --git a/public/assets/hero1.jpg b/public/assets/hero1.jpg deleted file mode 100644 index 75b5ba4..0000000 Binary files a/public/assets/hero1.jpg and /dev/null differ diff --git a/public/assets/hero1.webp b/public/assets/hero1.webp new file mode 100644 index 0000000..5e7b22c Binary files /dev/null and b/public/assets/hero1.webp differ diff --git a/public/assets/hero10.webp b/public/assets/hero10.webp new file mode 100644 index 0000000..85a84a6 Binary files /dev/null and b/public/assets/hero10.webp differ diff --git a/public/assets/hero11.webp b/public/assets/hero11.webp new file mode 100644 index 0000000..b34e549 Binary files /dev/null and b/public/assets/hero11.webp differ diff --git a/public/assets/hero12.webp b/public/assets/hero12.webp new file mode 100644 index 0000000..ac171a9 Binary files /dev/null and b/public/assets/hero12.webp differ diff --git a/public/assets/hero13.webp b/public/assets/hero13.webp new file mode 100644 index 0000000..aee4196 Binary files /dev/null and b/public/assets/hero13.webp differ diff --git a/public/assets/hero14.webp b/public/assets/hero14.webp new file mode 100644 index 0000000..e6b3506 Binary files /dev/null and b/public/assets/hero14.webp differ diff --git a/public/assets/hero15.webp b/public/assets/hero15.webp new file mode 100644 index 0000000..4d2d4bf Binary files /dev/null and b/public/assets/hero15.webp differ diff --git a/public/assets/hero16.webp b/public/assets/hero16.webp new file mode 100644 index 0000000..9d11a35 Binary files /dev/null and b/public/assets/hero16.webp differ diff --git a/public/assets/hero17.webp b/public/assets/hero17.webp new file mode 100644 index 0000000..98d8ea9 Binary files /dev/null and b/public/assets/hero17.webp differ diff --git a/public/assets/hero18.webp b/public/assets/hero18.webp new file mode 100644 index 0000000..1fa1832 Binary files /dev/null and b/public/assets/hero18.webp differ diff --git a/public/assets/hero19.webp b/public/assets/hero19.webp new file mode 100644 index 0000000..7974ba6 Binary files /dev/null and b/public/assets/hero19.webp differ diff --git a/public/assets/hero2.jpg b/public/assets/hero2.jpg deleted file mode 100644 index 95df845..0000000 Binary files a/public/assets/hero2.jpg and /dev/null differ diff --git a/public/assets/hero2.webp b/public/assets/hero2.webp new file mode 100644 index 0000000..52a3d0e Binary files /dev/null and b/public/assets/hero2.webp differ diff --git a/public/assets/hero20.webp b/public/assets/hero20.webp new file mode 100644 index 0000000..760d3e2 Binary files /dev/null and b/public/assets/hero20.webp differ diff --git a/public/assets/hero21.webp b/public/assets/hero21.webp new file mode 100644 index 0000000..e780a13 Binary files /dev/null and b/public/assets/hero21.webp differ diff --git a/public/assets/hero22.webp b/public/assets/hero22.webp new file mode 100644 index 0000000..71d0f43 Binary files /dev/null and b/public/assets/hero22.webp differ diff --git a/public/assets/hero23.webp b/public/assets/hero23.webp new file mode 100644 index 0000000..5d8f8cf Binary files /dev/null and b/public/assets/hero23.webp differ diff --git a/public/assets/hero3.jpg b/public/assets/hero3.jpg deleted file mode 100644 index 90b3e10..0000000 Binary files a/public/assets/hero3.jpg and /dev/null differ diff --git a/public/assets/hero3.webp b/public/assets/hero3.webp new file mode 100644 index 0000000..b88f243 Binary files /dev/null and b/public/assets/hero3.webp differ diff --git a/public/assets/hero4.jpg b/public/assets/hero4.jpg deleted file mode 100644 index a943644..0000000 Binary files a/public/assets/hero4.jpg and /dev/null differ diff --git a/public/assets/hero4.webp b/public/assets/hero4.webp new file mode 100644 index 0000000..7628c00 Binary files /dev/null and b/public/assets/hero4.webp differ diff --git a/public/assets/hero5.jpg b/public/assets/hero5.jpg deleted file mode 100644 index feb1bfd..0000000 Binary files a/public/assets/hero5.jpg and /dev/null differ diff --git a/public/assets/hero5.webp b/public/assets/hero5.webp new file mode 100644 index 0000000..703b352 Binary files /dev/null and b/public/assets/hero5.webp differ diff --git a/public/assets/hero6.jpg b/public/assets/hero6.jpg deleted file mode 100644 index d92f129..0000000 Binary files a/public/assets/hero6.jpg and /dev/null differ diff --git a/public/assets/hero6.webp b/public/assets/hero6.webp new file mode 100644 index 0000000..c117c15 Binary files /dev/null and b/public/assets/hero6.webp differ diff --git a/public/assets/hero7.webp b/public/assets/hero7.webp new file mode 100644 index 0000000..21c74ea Binary files /dev/null and b/public/assets/hero7.webp differ diff --git a/public/assets/hero8.webp b/public/assets/hero8.webp new file mode 100644 index 0000000..3f89528 Binary files /dev/null and b/public/assets/hero8.webp differ diff --git a/public/assets/hero9.webp b/public/assets/hero9.webp new file mode 100644 index 0000000..e4891eb Binary files /dev/null and b/public/assets/hero9.webp differ diff --git a/src/App.tsx b/src/App.tsx index f8c4e9b..e33c370 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,10 +5,11 @@ import Hero from './sections/Hero' import Themes from './components/Themes' import Timeline from './sections/Timeline' import FAQ from './sections/FAQ' -// import Team from './sections/Team' +import Team from './sections/Team' import Prizes from './sections/Prizes' import Sponsors from './sections/Sponsors' import Footer from './sections/Footer' +import ClickSpark from './components/ClickSpark' import { motion, AnimatePresence } from 'framer-motion' @@ -89,7 +90,7 @@ function Navbar({ onNavClick }: { onNavClick: (e: React.MouseEvent - + DEVHACK @@ -100,12 +101,13 @@ function Navbar({ onNavClick }: { onNavClick: (e: React.MouseEvent - Main - Timeline - Themes - Prizes - Sponsors - FAQ + Main + Themes + Prizes + Sponsors + Timeline + Team + FAQ )} @@ -116,7 +118,7 @@ function Navbar({ onNavClick }: { onNavClick: (e: React.MouseEvent {isOpen ? ( @@ -138,12 +140,13 @@ function Navbar({ onNavClick }: { onNavClick: (e: React.MouseEvent
- Main - Timeline - Themes - Prizes - Sponsors - FAQ + Main + Themes + Prizes + Sponsors + Timeline + Team + FAQ )} @@ -243,19 +246,21 @@ export default function App() { )} -
- -
- - - - - - {/* */} - -
-
-
+ +
+ +
+ + + + + + + +
+
+
+
) } diff --git a/src/components/BlurText.tsx b/src/components/BlurText.tsx new file mode 100644 index 0000000..655db9b --- /dev/null +++ b/src/components/BlurText.tsx @@ -0,0 +1,129 @@ +import { motion } from 'framer-motion'; +import { useEffect, useRef, useState, useMemo } from 'react'; + +const buildKeyframes = (from: any, steps: any[]) => { + const keys = new Set([...Object.keys(from), ...steps.flatMap(s => Object.keys(s))]); + + const keyframes: any = {}; + keys.forEach(k => { + keyframes[k] = [from[k], ...steps.map(s => s[k])]; + }); + return keyframes; +}; + +interface BlurTextProps { + text?: string; + delay?: number; + className?: string; + animateBy?: 'words' | 'letters'; + direction?: 'top' | 'bottom'; + threshold?: number; + rootMargin?: string; + animationFrom?: any; + animationTo?: any[]; + easing?: any; + onAnimationComplete?: () => void; + stepDuration?: number; + animateTrigger?: boolean; // Custom prop to trigger animation externally +} + +const BlurText = ({ + text = '', + delay = 200, + className = '', + animateBy = 'words', + direction = 'top', + threshold = 0.1, + rootMargin = '0px', + animationFrom, + animationTo, + easing = (t: any) => t, + onAnimationComplete, + stepDuration = 0.35, + animateTrigger +}: BlurTextProps) => { + const elements = animateBy === 'words' ? text.split(' ') : text.split(''); + const [inView, setInView] = useState(false); + const ref = useRef(null); + + useEffect(() => { + if (animateTrigger !== undefined) { + // If controlled externally, don't use IntersectionObserver + if (animateTrigger) { + setInView(true); + } else { + setInView(false); // Reset if needed, though usually one-way + } + return; + } + + if (!ref.current) return; + const observer = new IntersectionObserver( + ([entry]) => { + if (entry.isIntersecting) { + setInView(true); + observer.unobserve(ref.current!); + } + }, + { threshold, rootMargin } + ); + observer.observe(ref.current); + return () => observer.disconnect(); + }, [threshold, rootMargin, animateTrigger]); + + const defaultFrom = useMemo( + () => + direction === 'top' ? { filter: 'blur(10px)', opacity: 0, y: -50 } : { filter: 'blur(10px)', opacity: 0, y: 50 }, + [direction] + ); + + const defaultTo = useMemo( + () => [ + { + filter: 'blur(5px)', + opacity: 0.5, + y: direction === 'top' ? 5 : -5 + }, + { filter: 'blur(0px)', opacity: 1, y: 0 } + ], + [direction] + ); + + const fromSnapshot = animationFrom ?? defaultFrom; + const toSnapshots = animationTo ?? defaultTo; + + const stepCount = toSnapshots.length + 1; + const totalDuration = stepDuration * (stepCount - 1); + const times = Array.from({ length: stepCount }, (_, i) => (stepCount === 1 ? 0 : i / (stepCount - 1))); + + return ( +

+ {elements.map((segment, index) => { + const animateKeyframes = buildKeyframes(fromSnapshot, toSnapshots); + + const spanTransition: any = { + duration: totalDuration, + times, + delay: (index * delay) / 1000 + }; + spanTransition.ease = easing; + + return ( + + {segment === ' ' ? '\u00A0' : segment} + {animateBy === 'words' && index < elements.length - 1 && '\u00A0'} + + ); + })} +

+ ); +}; + +export default BlurText; diff --git a/src/components/ClickSpark.tsx b/src/components/ClickSpark.tsx new file mode 100644 index 0000000..a20a856 --- /dev/null +++ b/src/components/ClickSpark.tsx @@ -0,0 +1,174 @@ +import React, { useRef, useEffect, useCallback } from 'react'; + +interface ClickSparkProps { + sparkColor?: string; + sparkSize?: number; + sparkRadius?: number; + sparkCount?: number; + duration?: number; + easing?: 'linear' | 'ease-in' | 'ease-out' | 'ease-in-out'; + extraScale?: number; + children?: React.ReactNode; +} + +const ClickSpark: React.FC = ({ + sparkColor = '#fff', + sparkSize = 10, + sparkRadius = 15, + sparkCount = 8, + duration = 400, + easing = 'ease-out', + extraScale = 1.0, + children +}) => { + const canvasRef = useRef(null); + const sparksRef = useRef<{ x: number; y: number; angle: number; startTime: number }[]>([]); + const startTimeRef = useRef(null); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + + const parent = canvas.parentElement; + if (!parent) return; + + let resizeTimeout: ReturnType; + + const resizeCanvas = () => { + const { width, height } = parent.getBoundingClientRect(); + if (canvas.width !== width || canvas.height !== height) { + canvas.width = width; + canvas.height = height; + } + }; + + const handleResize = () => { + clearTimeout(resizeTimeout); + resizeTimeout = setTimeout(resizeCanvas, 100); + }; + + const ro = new ResizeObserver(handleResize); + ro.observe(parent); + + resizeCanvas(); + + return () => { + ro.disconnect(); + clearTimeout(resizeTimeout); + }; + }, []); + + const easeFunc = useCallback( + (t: number) => { + switch (easing) { + case 'linear': + return t; + case 'ease-in': + return t * t; + case 'ease-in-out': + return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; + default: + return t * (2 - t); + } + }, + [easing] + ); + + useEffect(() => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + + let animationId: number; + + const draw = (timestamp: number) => { + if (!startTimeRef.current) { + startTimeRef.current = timestamp; + } + ctx.clearRect(0, 0, canvas.width, canvas.height); + + sparksRef.current = sparksRef.current.filter(spark => { + const elapsed = timestamp - spark.startTime; + if (elapsed >= duration) { + return false; + } + + const progress = elapsed / duration; + const eased = easeFunc(progress); + + const distance = eased * sparkRadius * extraScale; + const lineLength = sparkSize * (1 - eased); + + const x1 = spark.x + distance * Math.cos(spark.angle); + const y1 = spark.y + distance * Math.sin(spark.angle); + const x2 = spark.x + (distance + lineLength) * Math.cos(spark.angle); + const y2 = spark.y + (distance + lineLength) * Math.sin(spark.angle); + + ctx.strokeStyle = sparkColor; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + + return true; + }); + + animationId = requestAnimationFrame(draw); + }; + + animationId = requestAnimationFrame(draw); + + return () => { + cancelAnimationFrame(animationId); + }; + }, [sparkColor, sparkSize, sparkRadius, sparkCount, duration, easeFunc, extraScale]); + + const handleClick = (e: React.MouseEvent) => { + const canvas = canvasRef.current; + if (!canvas) return; + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const now = performance.now(); + const newSparks = Array.from({ length: sparkCount }, (_, i) => ({ + x, + y, + angle: (2 * Math.PI * i) / sparkCount, + startTime: now + })); + + sparksRef.current.push(...newSparks); + }; + + return ( +
+ + {children} +
+ ); +}; + +export default ClickSpark; diff --git a/src/components/DotGrid.css b/src/components/DotGrid.css new file mode 100644 index 0000000..dac88cb --- /dev/null +++ b/src/components/DotGrid.css @@ -0,0 +1,22 @@ +.dot-grid { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + width: 100%; + position: relative; +} + +.dot-grid__wrap { + width: 100%; + height: 100%; + position: relative; +} + +.dot-grid__canvas { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + pointer-events: none; +} diff --git a/src/components/DotGrid.tsx b/src/components/DotGrid.tsx new file mode 100644 index 0000000..92abdd1 --- /dev/null +++ b/src/components/DotGrid.tsx @@ -0,0 +1,291 @@ +import React, { useRef, useEffect, useCallback, useMemo } from 'react'; +import { gsap } from 'gsap'; + +import './DotGrid.css'; + +const throttle = (func: (...args: any[]) => void, limit: number) => { + let lastCall = 0; + return function (this: any, ...args: any[]) { + const now = performance.now(); + if (now - lastCall >= limit) { + lastCall = now; + func.apply(this, args); + } + }; +}; + +function hexToRgb(hex: string) { + const m = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i); + if (!m) return { r: 0, g: 0, b: 0 }; + return { + r: parseInt(m[1], 16), + g: parseInt(m[2], 16), + b: parseInt(m[3], 16) + }; +} + +interface DotGridProps { + dotSize?: number; + gap?: number; + baseColor?: string; + activeColor?: string; + proximity?: number; + speedTrigger?: number; + shockRadius?: number; + shockStrength?: number; + maxSpeed?: number; + resistance?: number; + returnDuration?: number; + className?: string; + style?: React.CSSProperties; +} + +const DotGrid: React.FC = ({ + dotSize = 16, + gap = 32, + baseColor = '#5227FF', + activeColor = '#5227FF', + proximity = 150, + speedTrigger = 100, + shockRadius = 250, + shockStrength = 5, + maxSpeed = 5000, + resistance = 750, // Not used strictly without inertia plugin, but kept for compatibility + returnDuration = 1.5, + className = '', + style +}) => { + const wrapperRef = useRef(null); + const canvasRef = useRef(null); + const dotsRef = useRef([]); + const pointerRef = useRef({ + x: 0, + y: 0, + vx: 0, + vy: 0, + speed: 0, + lastTime: 0, + lastX: 0, + lastY: 0 + }); + + const baseRgb = useMemo(() => hexToRgb(baseColor), [baseColor]); + const activeRgb = useMemo(() => hexToRgb(activeColor), [activeColor]); + + const circlePath = useMemo(() => { + if (typeof window === 'undefined' || !window.Path2D) return null; + + const p = new window.Path2D(); + p.arc(0, 0, dotSize / 2, 0, Math.PI * 2); + return p; + }, [dotSize]); + + const buildGrid = useCallback(() => { + const wrap = wrapperRef.current; + const canvas = canvasRef.current; + if (!wrap || !canvas) return; + + const { width, height } = wrap.getBoundingClientRect(); + const dpr = window.devicePixelRatio || 1; + + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + const ctx = canvas.getContext('2d'); + if (ctx) ctx.scale(dpr, dpr); + + const cols = Math.floor((width + gap) / (dotSize + gap)); + const rows = Math.floor((height + gap) / (dotSize + gap)); + const cell = dotSize + gap; + + const gridW = cell * cols - gap; + const gridH = cell * rows - gap; + + const extraX = width - gridW; + const extraY = height - gridH; + + const startX = extraX / 2 + dotSize / 2; + const startY = extraY / 2 + dotSize / 2; + + const dots = []; + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + const cx = startX + x * cell; + const cy = startY + y * cell; + dots.push({ cx, cy, xOffset: 0, yOffset: 0, _inertiaApplied: false }); + } + } + dotsRef.current = dots; + }, [dotSize, gap]); + + useEffect(() => { + if (!circlePath) return; + + let rafId: number; + const proxSq = proximity * proximity; + + const draw = () => { + const canvas = canvasRef.current; + if (!canvas) return; + const ctx = canvas.getContext('2d'); + if (!ctx) return; + ctx.clearRect(0, 0, canvas.width, canvas.height); + + const { x: px, y: py } = pointerRef.current; + + for (const dot of dotsRef.current) { + const ox = dot.cx + dot.xOffset; + const oy = dot.cy + dot.yOffset; + const dx = dot.cx - px; + const dy = dot.cy - py; + const dsq = dx * dx + dy * dy; + + let style = baseColor; + if (dsq <= proxSq) { + const dist = Math.sqrt(dsq); + const t = 1 - dist / proximity; + const r = Math.round(baseRgb.r + (activeRgb.r - baseRgb.r) * t); + const g = Math.round(baseRgb.g + (activeRgb.g - baseRgb.g) * t); + const b = Math.round(baseRgb.b + (activeRgb.b - baseRgb.b) * t); + style = `rgb(${r},${g},${b})`; + } + + ctx.save(); + ctx.translate(ox, oy); + ctx.fillStyle = style; + ctx.fill(circlePath); + ctx.restore(); + } + + rafId = requestAnimationFrame(draw); + }; + + draw(); + return () => cancelAnimationFrame(rafId); + }, [proximity, baseColor, activeRgb, baseRgb, circlePath]); + + useEffect(() => { + buildGrid(); + let ro: ResizeObserver | null = null; + if (typeof ResizeObserver !== 'undefined') { + ro = new ResizeObserver(buildGrid); + if (wrapperRef.current) ro.observe(wrapperRef.current); + } else { + window.addEventListener('resize', buildGrid); + } + return () => { + if (ro) ro.disconnect(); + else window.removeEventListener('resize', buildGrid); + }; + }, [buildGrid]); + + useEffect(() => { + const onMove = (e: MouseEvent) => { + const now = performance.now(); + const pr = pointerRef.current; + const dt = pr.lastTime ? now - pr.lastTime : 16; + const dx = e.clientX - pr.lastX; + const dy = e.clientY - pr.lastY; + let vx = (dx / dt) * 1000; + let vy = (dy / dt) * 1000; + let speed = Math.hypot(vx, vy); + if (speed > maxSpeed) { + const scale = maxSpeed / speed; + vx *= scale; + vy *= scale; + speed = maxSpeed; + } + pr.lastTime = now; + pr.lastX = e.clientX; + pr.lastY = e.clientY; + pr.vx = vx; + pr.vy = vy; + pr.speed = speed; + + if (!canvasRef.current) return; + const rect = canvasRef.current.getBoundingClientRect(); + pr.x = e.clientX - rect.left; + pr.y = e.clientY - rect.top; + + for (const dot of dotsRef.current) { + const dist = Math.hypot(dot.cx - pr.x, dot.cy - pr.y); + if (speed > speedTrigger && dist < proximity && !dot._inertiaApplied) { + dot._inertiaApplied = true; + gsap.killTweensOf(dot); + const pushX = dot.cx - pr.x + vx * 0.005; + const pushY = dot.cy - pr.y + vy * 0.005; + + // Replaced premium InertiaPlugin with a standard gsap.to ease to avoid crash + gsap.to(dot, { + xOffset: pushX, + yOffset: pushY, + duration: 0.3, + ease: "power2.out", + onComplete: () => { + gsap.to(dot, { + xOffset: 0, + yOffset: 0, + duration: returnDuration, + ease: 'elastic.out(1,0.75)' + }); + dot._inertiaApplied = false; + } + }); + } + } + }; + + const onClick = (e: MouseEvent) => { + if (!canvasRef.current) return; + const rect = canvasRef.current.getBoundingClientRect(); + const cx = e.clientX - rect.left; + const cy = e.clientY - rect.top; + for (const dot of dotsRef.current) { + const dist = Math.hypot(dot.cx - cx, dot.cy - cy); + if (dist < shockRadius && !dot._inertiaApplied) { + dot._inertiaApplied = true; + gsap.killTweensOf(dot); + const falloff = Math.max(0, 1 - dist / shockRadius); + const pushX = (dot.cx - cx) * shockStrength * falloff; + const pushY = (dot.cy - cy) * shockStrength * falloff; + + gsap.to(dot, { + xOffset: pushX, + yOffset: pushY, + duration: 0.2, + ease: "power2.out", + onComplete: () => { + gsap.to(dot, { + xOffset: 0, + yOffset: 0, + duration: returnDuration, + ease: 'elastic.out(1,0.75)' + }); + dot._inertiaApplied = false; + } + }); + } + } + }; + + const throttledMove = throttle(onMove, 50); + window.addEventListener('mousemove', throttledMove, { passive: true }); + window.addEventListener('click', onClick); + + return () => { + window.removeEventListener('mousemove', throttledMove); + window.removeEventListener('click', onClick); + }; + }, [maxSpeed, speedTrigger, proximity, resistance, returnDuration, shockRadius, shockStrength]); + + return ( +
+
+ +
+
+ ); +}; + +export default DotGrid; diff --git a/src/components/Marquee.tsx b/src/components/Marquee.tsx deleted file mode 100644 index b39fd80..0000000 --- a/src/components/Marquee.tsx +++ /dev/null @@ -1,13 +0,0 @@ -export default function Marquee() { - return ( -
-
- {[...Array(5)].map((_, i) => ( - - 🌟 EARLY BIRD TICKETS ON SALE NOW! 🚀 DEVHACK 3.0 🌟 - - ))} -
-
- ) -} diff --git a/src/components/ParallaxWrapper.tsx b/src/components/ParallaxWrapper.tsx deleted file mode 100644 index 86bf6a4..0000000 --- a/src/components/ParallaxWrapper.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { useRef } from 'react'; -import type { ReactNode } from 'react'; -import { motion, useScroll, useTransform } from 'framer-motion'; - -interface ParallaxWrapperProps { - children: ReactNode; - speed?: number; - className?: string; - rotate?: number; - scale?: number; -} - -export default function ParallaxWrapper({ - children, - speed = 40, - className = '', - rotate = 0, - scale = 0 -}: ParallaxWrapperProps) { - const ref = useRef(null); - const { scrollYProgress } = useScroll({ - target: ref, - offset: ['start end', 'end start'] - }); - - const y = useTransform(scrollYProgress, [0, 1], [speed, -speed]); - const r = useTransform(scrollYProgress, [0, 1], [-rotate, rotate]); - const s = useTransform(scrollYProgress, [0, 1], [1 - scale, 1 + scale]); - - return ( - - {children} - - ); -} diff --git a/src/components/SponsorCard.tsx b/src/components/SponsorCard.tsx index 9014b38..b44826e 100644 --- a/src/components/SponsorCard.tsx +++ b/src/components/SponsorCard.tsx @@ -8,53 +8,72 @@ interface SponsorCardProps { } const SponsorCard: React.FC = ({ logo, name, tier }) => { - const sizeClasses = { - platinum: 'h-16 sm:h-24 md:h-28', - gold: 'h-12 sm:h-16 md:h-20', - stream: 'h-10 sm:h-14 md:h-18', - community: 'h-8 sm:h-12 md:h-14', + const logoSizeClasses = { + platinum: 'h-20 sm:h-24 md:h-32', + gold: 'h-16 sm:h-20 md:h-24', + stream: 'h-14 sm:h-16 md:h-20', + community: 'h-10 sm:h-12 md:h-16', + }; + + const nameSizeClasses = { + platinum: 'text-4xl sm:text-5xl md:text-6xl', + gold: 'text-3xl sm:text-4xl md:text-5xl', + stream: 'text-2xl sm:text-3xl md:text-4xl', + community: 'text-xl sm:text-2xl md:text-3xl', }; return ( - {/* Animated Box Background */} - - {/* Sponsor Logo */} -
- - - {/* Optional: Name reveal */} - + - {name} - -
+ {/* Black Outer Outline */} + + {name} + + {/* White Inner "Background" Outline */} + + {name} + + {/* Main Black Text */} + + {name} + + +
); }; diff --git a/src/components/TeamCarousel.tsx b/src/components/TeamCarousel.tsx deleted file mode 100644 index edd450e..0000000 --- a/src/components/TeamCarousel.tsx +++ /dev/null @@ -1,520 +0,0 @@ -"use client"; - -import React, { useState, useCallback, useEffect } from 'react'; -import { ChevronLeft, ChevronRight } from 'lucide-react'; -import { motion, AnimatePresence, type TargetAndTransition} from 'framer-motion'; -import { cn } from '../lib/utils'; // Assuming this utility correctly merges class names - -export interface TeamMember { - id: string; - name: string; - role: string; - image: string; - bio?: string; - linkedin?: string; -} - -export interface TeamCarouselProps { - /** Array of team members */ - members: TeamMember[]; - /** Title displayed above the carousel */ - title?: string; - /** Title font size */ - titleSize?: 'sm' | 'md' | 'lg' | 'xl' | '2xl'; - /** Title color */ - titleColor?: string; - /** Background color or gradient. Overrides the default 'bg-background' class. */ - background?: string; - /** Card width in pixels */ - cardWidth?: number; - /** Card height in pixels */ - cardHeight?: number; - /** Card border radius */ - cardRadius?: number; - /** Enable/disable navigation arrows */ - showArrows?: boolean; - /** Enable/disable dots indicator */ - showDots?: boolean; - /** Enable/disable keyboard navigation */ - keyboardNavigation?: boolean; - /** Enable/disable touch/swipe navigation */ - touchNavigation?: boolean; - /** Animation duration in milliseconds */ - animationDuration?: number; - /** Auto-play interval in milliseconds (0 to disable) */ - autoPlay?: number; - /** Pause auto-play on hover */ - pauseOnHover?: boolean; - /** Number of visible cards on each side */ - visibleCards?: number; - /** Scale factor for side cards */ - sideCardScale?: number; - /** Opacity for side cards */ - sideCardOpacity?: number; - /** Apply grayscale filter to side cards */ - grayscaleEffect?: boolean; - /** Custom className for container */ - className?: string; - /** Custom className for cards */ - cardClassName?: string; - /** Custom className for title */ - titleClassName?: string; - /** Member info position */ - infoPosition?: 'bottom' | 'overlay' | 'none'; - /** Info text color */ - infoTextColor?: string; - /** Info background */ - infoBackground?: string; - /** Callback when active member changes */ - onMemberChange?: (member: TeamMember, index: number) => void; - /** Callback when card is clicked */ - onCardClick?: (member: TeamMember, index: number) => void; - /** Initial active index */ - initialIndex?: number; -} - -export const TeamCarousel: React.FC = ({ - members, - title = "OUR TEAM", - titleSize = "2xl", - titleColor = "rgba(0, 76, 255, 1)", - background, - cardWidth = 280, - cardHeight = 380, - cardRadius = 20, - showArrows = true, - showDots = true, - keyboardNavigation = true, - touchNavigation = true, - animationDuration = 800, - autoPlay = 0, - pauseOnHover = true, - visibleCards = 2, - sideCardScale = 0.9, - sideCardOpacity = 0.8, - grayscaleEffect = true, - className, - cardClassName, - titleClassName, - infoPosition = "bottom", - infoTextColor = "rgb(8, 42, 123)", - infoBackground = "transparent", - onMemberChange, - onCardClick, - initialIndex = 0, -}) => { - const [currentIndex, setCurrentIndex] = useState(initialIndex); - const [direction, setDirection] = useState(0); // 0: no movement, 1: next, -1: prev - const [touchStart, setTouchStart] = useState(0); - const [touchEnd, setTouchEnd] = useState(0); - - const totalMembers = members.length; - - const paginate = useCallback( - (newDirection: number) => { - if (totalMembers === 0) return; - setDirection(newDirection); - const nextIndex = (currentIndex + newDirection + totalMembers) % totalMembers; - setCurrentIndex(nextIndex); - onMemberChange?.(members[nextIndex], nextIndex); - }, - [currentIndex, totalMembers, members, onMemberChange] - ); - - const wrapIndex = (index: number) => { - return (index + totalMembers) % totalMembers; - }; - - const calculatePosition = (index: number) => { - const activeIndex = currentIndex; - const diff = wrapIndex(index - activeIndex); - - if (diff === 0) return 'center'; - - const half = Math.floor(totalMembers / 2); - - if (diff <= half) { - if (diff <= visibleCards) return `right-${diff}`; - return 'hidden'; - } else { - const leftDiff = totalMembers - diff; - if (leftDiff <= visibleCards) return `left-${leftDiff}`; - return 'hidden'; - } - }; - - // Explicitly type the return of getVariantStyles to match framer-motion's expectations - const getVariantStyles = (position: string): TargetAndTransition => { - // FIX: Changed ease from number[] to an array of string presets or a valid CubicBezier type - // Using string presets for simplicity and type compatibility. - // If you need the exact cubic-bezier values, ensure they are compatible with framer-motion's Easing type. - // For custom cubic-bezier, you might need to use a type assertion like `as [number, number, number, number]` - // or import CubicBezier from 'framer-motion/types/value/types'. - const transition = { - duration: animationDuration / 1000, - // You can use a string preset like 'easeInOut' or a valid cubic-bezier array if framer-motion's types support it directly - // For the given numbers, 'easeInOut' is a close approximation or 'cubic-bezier(0.25, 0.46, 0.45, 0.94)' if framer-motion accepted it directly as string - // To strictly match [0.25, 0.46, 0.45, 0.94], framer-motion expects it as a CubicBezier tuple: - ease: [0.25, 0.46, 0.45, 0.94] as [number, number, number, number], - }; - - switch (position) { - case 'center': - return { - zIndex: 10, - opacity: 1, - scale: 1.1, - x: 0, - filter: 'grayscale(0%)', - pointerEvents: 'auto', - transition, - }; - case 'right-1': - return { - zIndex: 5, - opacity: sideCardOpacity, - scale: sideCardScale, - x: cardWidth * 0.7, - filter: grayscaleEffect ? 'grayscale(100%)' : 'grayscale(0%)', - pointerEvents: 'auto', - transition, - }; - case 'right-2': - return { - zIndex: 1, - opacity: sideCardOpacity * 0.7, - scale: sideCardScale * 0.9, - x: cardWidth * 1.4, - filter: grayscaleEffect ? 'grayscale(100%)' : 'grayscale(0%)', - pointerEvents: 'auto', - transition, - }; - case 'left-1': - return { - zIndex: 5, - opacity: sideCardOpacity, - scale: sideCardScale, - x: -cardWidth * 0.7, - filter: grayscaleEffect ? 'grayscale(100%)' : 'grayscale(0%)', - pointerEvents: 'auto', - transition, - }; - case 'left-2': - return { - zIndex: 1, - opacity: sideCardOpacity * 0.7, - scale: sideCardScale * 0.9, - x: -cardWidth * 1.4, - filter: grayscaleEffect ? 'grayscale(100%)' : 'grayscale(0%)', - pointerEvents: 'auto', - transition, - }; - default: - return { - zIndex: 0, - opacity: 0, - scale: 0.8, - x: direction > 0 ? cardWidth * (visibleCards + 1) : -cardWidth * (visibleCards + 1), - pointerEvents: 'none', - filter: grayscaleEffect ? 'grayscale(100%)' : 'grayscale(0%)', - transition, - }; - } - }; - - // Auto-play functionality - useEffect(() => { - let interval: ReturnType; - if (autoPlay > 0) { - interval = setInterval(() => { - paginate(1); - }, autoPlay); - } - - const carouselContainer = document.getElementById('team-carousel-container'); - - const handleMouseEnter = () => { - if (pauseOnHover && autoPlay > 0) clearInterval(interval); - }; - - const handleMouseLeave = () => { - if (pauseOnHover && autoPlay > 0) { - interval = setInterval(() => { - paginate(1); - }, autoPlay); - } - }; - - if (carouselContainer && pauseOnHover && autoPlay > 0) { - carouselContainer.addEventListener('mouseenter', handleMouseEnter); - carouselContainer.addEventListener('mouseleave', handleMouseLeave); - } - - return () => { - clearInterval(interval); - if (carouselContainer && pauseOnHover && autoPlay > 0) { - carouselContainer.removeEventListener('mouseenter', handleMouseEnter); - carouselContainer.removeEventListener('mouseleave', handleMouseLeave); - } - }; - }, [autoPlay, paginate, pauseOnHover]); - - // Keyboard navigation - useEffect(() => { - if (!keyboardNavigation) return; - - const handleKeyDown = (e: KeyboardEvent) => { - if (e.key === 'ArrowLeft') { - paginate(-1); - } else if (e.key === 'ArrowRight') { - paginate(1); - } - }; - - document.addEventListener('keydown', handleKeyDown); - return () => document.removeEventListener('keydown', handleKeyDown); - }, [keyboardNavigation, paginate]); - - // Touch navigation - const handleTouchStart = (e: React.TouchEvent) => { - if (!touchNavigation) return; - setTouchStart(e.targetTouches[0].clientX); - }; - - const handleTouchMove = (e: React.TouchEvent) => { - if (!touchNavigation) return; - setTouchEnd(e.targetTouches[0].clientX); - }; - - const handleTouchEnd = () => { - if (!touchNavigation) return; - - const swipeThreshold = 50; - const diff = touchStart - touchEnd; - - if (Math.abs(diff) > swipeThreshold) { - if (diff > 0) { - paginate(1); - } else { - paginate(-1); - } - } - }; - - const titleSizeClasses = { - sm: 'text-4xl', - md: 'text-5xl', - lg: 'text-6xl', - xl: 'text-7xl', - '2xl': 'text-8xl', - }; - - return ( - - ); -}; - -export default TeamCarousel; diff --git a/src/components/Themes.tsx b/src/components/Themes.tsx index 8c0f293..20bc842 100644 --- a/src/components/Themes.tsx +++ b/src/components/Themes.tsx @@ -17,76 +17,62 @@ export type Theme = { title: string; desc: string; tags: string[]; - rotate: string; - year: string; }; const themes: Theme[] = [ { id: 1, emoji: '🤖', - color: '#f97028', // Orange - bg: '#fffdf9', // Very light cream - title: 'AI ML', + color: '#f97028', + bg: '#fffdf9', + title: 'AI / ML', desc: 'Build intelligent systems, chatbots, predictive models, and generative AI tools that think, learn, and adapt.', tags: ['LLMs', 'Computer Vision', 'NLP', 'Deep Learning'], - rotate: '-3deg', - year: '2026', }, { id: 2, emoji: '🧠', - color: '#f489a3', // Pink + color: '#f489a3', bg: '#fffdf9', title: 'Agentic AI', desc: 'Develop autonomous agents that can plan, reason, and execute complex workflows without human intervention.', tags: ['Autonomous Agents', 'Reasoning', 'Workflows', 'Multi-Agent'], - rotate: '2deg', - year: '2026', }, { id: 3, emoji: '📡', - color: '#f3a20f', // Yellow/Gold + color: '#f3a20f', bg: '#fffdf9', - title: 'IOT & Smart Cities', - desc: 'Bridge physical and digital worlds with embedded systems, sensor networks, edge intelligence, and urban tech.', - tags: ['Embedded', 'Edge AI', 'Sensors', 'Urban Tech'], - rotate: '-2deg', - year: '2026', + title: 'IOT & Sustainability', + desc: 'Bridge physical and digital worlds with embedded systems, sensor networks, edge intelligence, and sustainable tech solutions.', + tags: ['Embedded', 'Edge AI', 'Sensors', 'GreenTech'], }, { id: 4, emoji: '⚕️', - color: '#4ade80', // Green + color: '#4ade80', bg: '#fffdf9', title: 'Healthcare', desc: 'Revolutionise patient care and medical research with modern health-tech, accessibility tools, and data analysis.', tags: ['HealthAI', 'Telemed', 'Accessibility', 'Bioinformatics'], - rotate: '3deg', - year: '2026', }, { id: 5, - emoji: '🌐', - color: '#22d3ee', // Cyan + emoji: '🔗', + color: '#22d3ee', bg: '#fffdf9', - title: 'Web3 / Sustain', - desc: 'Decentralise the future and protect the planet — DeFi protocols, DAOs, green-tech, and sustainable innovation.', - tags: ['Smart Contracts', 'DeFi', 'GreenTech', 'NFTs'], - rotate: '-1deg', - year: '2026', + title: 'Blockchain & Fintech', + desc: 'Build the future of finance and trust — decentralised protocols, smart contracts, digital payments, and financial innovation.', + tags: ['Smart Contracts', 'DeFi', 'Fintech', 'Crypto'], }, { id: 6, emoji: '💡', - color: '#a855f7', // Purple + color: '#a855f7', bg: '#fffdf9', title: 'Open Innovation', - desc: 'No boundaries. Build whatever crazy, groundbreaking idea you have that doesn’t fit into a box.', + desc: 'No boundaries. Build whatever crazy, groundbreaking idea you have that doesn\'t fit into a box.', tags: ['Moonshots', 'Creative', 'Anything Goes', 'Wildcard'], - rotate: '2deg', - year: '2026', }, ]; @@ -94,202 +80,192 @@ const containerVariants: Variants = { hidden: {}, visible: { transition: { - staggerChildren: 0.1, + staggerChildren: 0.12, }, }, }; const cardVariants: Variants = { - hidden: { opacity: 0, y: 40, scale: 0.95 }, + hidden: { opacity: 0, y: 50, scale: 0.92 }, visible: { opacity: 1, y: 0, scale: 1, transition: { type: 'spring', - stiffness: 90, - damping: 15, + stiffness: 80, + damping: 16, }, }, }; -function ThemeCard({ theme, onClick }: { theme: Theme; onClick: () => void }) { +/** Returns true when viewport is below the given px width */ +function useIsMobile(breakpoint = 1024) { + const [isMobile, setIsMobile] = useState(false); + useEffect(() => { + const mql = window.matchMedia(`(max-width: ${breakpoint}px)`); + const update = () => setIsMobile(mql.matches); + update(); + mql.addEventListener('change', update); + return () => mql.removeEventListener('change', update); + }, [breakpoint]); + return isMobile; +} + +function ThemeCard({ theme, isMobile }: { theme: Theme; isMobile: boolean }) { + const [isOpen, setIsOpen] = useState(false); + + // On desktop: hover controls reveal; on mobile: tap toggles + const showContent = isMobile ? isOpen : isOpen; + return ( { if (!isMobile) setIsOpen(true); }} + onHoverEnd={() => { if (!isMobile) setIsOpen(false); }} + onClick={() => { if (isMobile) setIsOpen((v) => !v); }} + className="relative w-full cursor-pointer" > - {/* Outer wrapper provides the thick colorful border */} -
- {/* Top-left subtle branding / corner decoration if desired */} -
- - Theme {theme.id < 10 ? `0${theme.id}` : theme.id} -
- - {/* Central Graphic (Emoji) */} -
- - {theme.emoji} - -
- - {/* The bottom-right Cutout/Tab mimicking Lando Norris helmet view */} + {/* Accent stripe at top */}
- - {theme.title} {theme.year} - -
+ className="w-full h-[6px]" + style={{ backgroundColor: theme.color }} + /> - {/* Hover overlay hint */} -
- - Click to View - -
-
-
- ); -} - -// ── Shared Modal component ── -function ThemeModal({ theme, onClose }: { theme: Theme; onClose: () => void }) { - // Prevent scrolling when modal is open - useEffect(() => { - document.body.style.overflow = 'hidden'; - return () => { - document.body.style.overflow = 'unset'; - }; - }, []); - - return ( -
- {/* Backdrop */} - - - {/* Modal Content Wrapper */} - - {/* Sticky Header for Close Button on Mobile */} -
- -
- - {/* Scrollable Content Container */} -
- {/* Left Side (Visuals) */} -
+
+ {/* Emoji + Title row — always visible */} +
- {theme.emoji} + {theme.emoji} -
- 0{theme.id} +
+

+ {theme.title} +

-
- {/* Right Side (Details) */} -
-
- - Domain -
+ {/* Tap indicator on mobile/tablet */} + {isMobile && ( + + + + + + )} +
-

- {theme.title} -

+ {/* Dropdown content — smooth reveal */} + + {showContent && ( + + {/* Divider */} +
-

- {theme.desc} -

+ {/* Description */} +

+ {theme.desc} +

-
-

Key Focus Areas

-
- {theme.tags.map((tag) => ( - - {tag} - - ))} -
-
-
+ {/* Tags */} +
+ {theme.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} +
+ + {/* Bottom accent bar */} + -
+ ); } export default function Themes() { const sectionRef = useRef(null); const headingRef = useRef(null); - - const [selectedTheme, setSelectedTheme] = useState(null); + const isMobile = useIsMobile(1024); const isInView = useInView(sectionRef, { once: true, margin: '-10% 0px' }); @@ -304,90 +280,90 @@ export default function Themes() { const blobY2 = useTransform(smoothY, [0, 1], [0, 80]); return ( - <> -
+ {/* Decorative background blobs */} + - {/* Decorative background blobs */} - -
- +
+ + + +
+ +
+ {/* Section heading — more prominent */} -
- - -
- {/* Section heading */} + {/* Decorative top line */} - -
- - - - - Themes - - - + initial={{ opacity: 0, scaleX: 0 }} + whileInView={{ opacity: 1, scaleX: 1 }} + transition={{ type: "spring", stiffness: 200, delay: 0.1 }} + className="mx-auto mb-5 h-[3px] w-24 md:w-32 bg-[#1a1a1a]" + /> - {/* Theme cards grid */} - - {themes.map((theme) => ( - setSelectedTheme(theme)} - /> - ))} - + Themes + - {/* Bottom CTA */} - -
-
+ + Choose your domain. Build your vision. + - {/* Modal Overlay */} - - {selectedTheme && ( - setSelectedTheme(null)} + {/* Decorative bottom line */} + - )} - - + + + {/* Theme cards grid */} + + {themes.map((theme) => ( + + ))} + +
+ ); } diff --git a/src/index.css b/src/index.css index 8e5b979..5b76d2e 100644 --- a/src/index.css +++ b/src/index.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Shrikhand&family=Outfit:wght@400;500;600;700&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Shrikhand&family=Outfit:wght@400;500;600;700&family=Fredoka:wght@400;500;600;700&display=swap'); @font-face { font-family: 'Wonderful Snowflake'; @@ -7,6 +7,13 @@ font-style: normal; } +@font-face { + font-family: 'Sugar Peachy'; + src: url('/assets/Sugar Peachy DEMO.otf') format('opentype'); + font-weight: normal; + font-style: normal; +} + @import "tailwindcss"; @theme { @@ -28,11 +35,14 @@ --font-display: "Wonderful Snowflake", "Shrikhand", serif; --font-sans: "Outfit", sans-serif; + --font-groovy: "Fredoka", "Outfit", sans-serif; + --font-peachy: "Sugar Peachy", serif; } @layer base { html { scroll-behavior: smooth; + cursor: url('/assets/Jeelh-Cursor-Light/Normal%20Select%20Light.png') 0 0, auto; } body { @@ -42,6 +52,15 @@ margin: 0; -webkit-font-smoothing: antialiased; overflow-x: hidden; + cursor: url('/assets/Jeelh-Cursor-Light/Normal%20Select%20Light.png') 0 0, auto; + } + + a, button, [role="button"], .cursor-pointer { + cursor: url('/assets/Jeelh-Cursor-Light/Select%20Light.png') 18 2, pointer !important; + } + + input[type="text"], input[type="email"], textarea, [contenteditable="true"], .cursor-text { + cursor: url('/assets/Jeelh-Cursor-Light/Text%20Select%20Light.png') 32 32, text !important; } h1, @@ -126,6 +145,14 @@ .glow-effect { filter: drop-shadow(0 0 10px rgba(243, 162, 15, 0.6)); } + + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } + .no-scrollbar::-webkit-scrollbar { + display: none; + } } @keyframes marquee { @@ -215,8 +242,27 @@ .hero-images-overlay { background: - linear-gradient(to bottom, rgba(0, 0, 0, 0.35), rgba(0, 0, 0, 0.15) 32%, rgba(0, 0, 0, 0.2) 70%, rgba(0, 0, 0, 0.45)), - radial-gradient(circle at center, rgba(0, 0, 0, 0.05), rgba(0, 0, 0, 0.45)); + linear-gradient(to bottom, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0.05) 25%, rgba(0, 0, 0, 0.05) 75%, rgba(0, 0, 0, 0.3)); +} + +@keyframes scroll-y { + 0% { transform: translateY(0); } + 100% { transform: translateY(-50%); } +} + +@keyframes scroll-y-reverse { + 0% { transform: translateY(-50%); } + 100% { transform: translateY(0); } +} + +.animate-scroll-y { + animation: scroll-y 40s linear infinite; + will-change: transform; +} + +.animate-scroll-y-reverse { + animation: scroll-y-reverse 40s linear infinite; + will-change: transform; } .hero-title-layer { @@ -252,7 +298,7 @@ 0 0 0 2px rgba(243, 236, 210, 0.3), 0 4px 0 #1a1a1a, 0 0 15px rgba(249, 112, 40, 0.2); - transform: rotate(-1deg); + transform: rotate(0deg); animation: datePulse 2.5s ease-in-out infinite; } @@ -339,12 +385,12 @@ 0%, 100% { - transform: rotate(-1deg) scale(1); + transform: rotate(0deg) scale(1); opacity: 1; } 50% { - transform: rotate(-1deg) scale(1.05); + transform: rotate(0deg) scale(1.05); opacity: 0.9; } } @@ -428,4 +474,28 @@ 100% { background-size: 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100%, 1ch 100% } +} + +@keyframes spinY { + from { + transform: rotateY(0deg); + } + to { + transform: rotateY(360deg); + } +} + +.animate-spin-y { + animation: spinY 3s linear infinite; +} + +.coin { + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid #1a1a1a; + background: linear-gradient(135deg, #f3a20f, #f97028); + box-shadow: inset -2px -2px 0px rgba(0,0,0,0.2), 2px 2px 0px #1a1a1a; + display: inline-block; + margin: 0 8px; } \ No newline at end of file diff --git a/src/sections/Hero.tsx b/src/sections/Hero.tsx index 7de0348..3e2c11d 100644 --- a/src/sections/Hero.tsx +++ b/src/sections/Hero.tsx @@ -1,70 +1,54 @@ -import { useEffect, useRef } from "react"; - +import { useEffect, useRef, useState } from "react"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; -const images: string[] = [ - "/assets/hero1.jpg", - "/assets/hero2.jpg", - "/assets/hero3.jpg", - "/assets/hero4.jpg", - "/assets/hero5.jpg", - "/assets/hero6.jpg", -]; - -type RowProps = { - reverse?: boolean; - speed?: string; - imageClassName?: string; -}; +import photos from "./photos"; +import BlurText from "../components/BlurText"; -const speedClassMap: Record = { - "18s": "hero-scroll-speed-18", - "20s": "hero-scroll-speed-20", - "24s": "hero-scroll-speed-24", - "25s": "hero-scroll-speed-25", -}; +const offsetCount = Math.floor(photos.length / 4); +const col1Photos = photos; +const col2Photos = [...photos.slice(offsetCount), ...photos.slice(0, offsetCount)]; +const col3Photos = [...photos.slice(offsetCount * 2), ...photos.slice(0, offsetCount * 2)]; +const col4Photos = [...photos.slice(offsetCount * 3), ...photos.slice(0, offsetCount * 3)]; -const Row = ({ reverse = false, speed = "25s" }: RowProps) => { - const speedClass = speedClassMap[speed] ?? "hero-scroll-speed-25"; +const Column = ({ reverse = false, speed = 40, offset = false, className = "", photoSubset }: { reverse?: boolean; speed?: number; offset?: boolean; className?: string; photoSubset: typeof photos }) => { + const colPhotos = [...photoSubset, ...photoSubset]; return ( -
+
- {[...images, ...images].map((src, i) => { - const sizeVariants = [ - "h-[120px] sm:h-[110px] md:h-[140px] lg:h-[170px]", - "h-[140px] sm:h-[130px] md:h-[160px] lg:h-[200px]", - "h-[130px] sm:h-[120px] md:h-[150px] lg:h-[180px]", - ]; - - const randomSize = sizeVariants[i % sizeVariants.length]; - - return ( - - ); - })} + {[1, 2].map(key => ( +
+ {colPhotos.map((p, i) => ( + + ))} +
+ ))}
); }; + export default function DevHackHeroCompact() { + const [triggerBlur, setTriggerBlur] = useState(false); + const sectionRef = useRef(null); const baseLayerRef = useRef(null); const imagesFrameRef = useRef(null); const revealRef = useRef(null); - const card1Ref = useRef(null); - const card2Ref = useRef(null); const videoFrameRef = useRef(null); + const dateStickerRef = useRef(null); + const memoryTitleRef = useRef(null); useEffect(() => { gsap.registerPlugin(ScrollTrigger); @@ -73,15 +57,13 @@ export default function DevHackHeroCompact() { const baseLayer = baseLayerRef.current; const imagesFrame = imagesFrameRef.current; const reveal = revealRef.current; - const card1 = card1Ref.current; - const card2 = card2Ref.current; const videoFrame = videoFrameRef.current; + const memoryTitle = memoryTitleRef.current; - if (!section || !baseLayer || !imagesFrame || !reveal || !card1 || !card2 || !videoFrame) { + if (!section || !baseLayer || !imagesFrame || !reveal || !videoFrame || !dateStickerRef.current || !memoryTitle) { return; } - - + const sticker = dateStickerRef.current; const initialize = async () => { const context = gsap.context(() => { @@ -96,19 +78,33 @@ export default function DevHackHeroCompact() { gsap.set(videoFrame, { opacity: 1, scale: 1, - top: isMobile ? "12vh" : "0", - height: isMobile ? "50vh" : "100vh", - width: isMobile ? "90vw" : "100vw", - left: isMobile ? "50%" : "0", - xPercent: isMobile ? -50 : 0, + top: isMobile ? "75px" : "9px", + height: isMobile ? "auto" : "100vh", + width: "100vw", + left: 0, + xPercent: 0, + borderRadius: 0, }); gsap.set(baseLayer, { opacity: 1 }); - gsap.set(reveal, { - opacity: isMobile ? 1 : 0, - top: isMobile ? "100vh" : "0" + gsap.set(reveal, { + opacity: 1, + top: isMobile ? "calc(75px + 56vw + 60px)" : "100vh" + }); + gsap.set(imagesFrame, { + opacity: 0, + }); + gsap.set(memoryTitle, { + opacity: 0, + y: 40, + scale: 0.95, + }); + gsap.set(sticker, { + opacity: 1, + y: 0, + top: isMobile ? "calc(75px + 56vw)" : "auto", + bottom: isMobile ? "auto" : "2rem", }); - gsap.set([card1, card2], { opacity: 0, y: 50 }); const timeline = gsap.timeline({ defaults: { ease: "none" }, @@ -120,13 +116,11 @@ export default function DevHackHeroCompact() { }, }); - - timeline .to( videoFrame, { - scale: isMobile ? 1.2 : 1.8, + scale: isMobile ? 1 : 1.8, opacity: isMobile ? 1 : 0, duration: 2.0, }, @@ -135,38 +129,45 @@ export default function DevHackHeroCompact() { .to( reveal, { - top: isMobile ? "65vh" : "0", - opacity: 1, - duration: isMobile ? 3.0 : 1.5, - ease: "power2.out", + top: isMobile ? "75px" : "0", + duration: isMobile ? 4.0 : 1.5, + ease: "power2.inOut", }, 0.5 ) .to( imagesFrame, { - opacity: isMobile ? 0.7 : 0.8, + opacity: 1, duration: 1.5, }, isMobile ? 0.8 : 3.0 ) .to( - card1, + memoryTitle, { opacity: 1, y: 0, + scale: 1, duration: 1.5, + ease: "power2.out", + onStart: () => setTriggerBlur(true), + onReverseComplete: () => setTriggerBlur(false), }, - isMobile ? 1.2 : 3.2 + isMobile ? 1.0 : 3.2 ) .to( - card2, + {}, + { duration: 1 } + ) + .to( + sticker, { - opacity: 1, - y: 0, - duration: 1.5, + opacity: 0, + y: 20, + duration: 0.5, }, - isMobile ? 1.5 : 3.4 + 0 ); }, section); @@ -189,52 +190,57 @@ export default function DevHackHeroCompact() { }, []); return ( -
+
-
+
-
-
- - - -
- +
+ + {/* Vertical Scrolling Gallery Background */} +
+
+ + + +
+
-
-
- -
-
-
01
-

What is DevHack?

-

- DSU DEVHACK 2026 is a national-level hackathon pushing the boundaries of innovation in AI, ML, IoT, Blockchain, Cybersecurity, and Cloud -

-
-
-
02
-

Why Participate?

-

- Connect with creative minds, build cutting-edge solutions, win exciting prizes, and showcase your skills while transforming ideas into impactful innovations. -

+ {/* Memories Title Overlay — positioned near top */} +
+
+ {/* Date Sticker */} +
+
+ SEPTEMBER 18TH & 19TH, 2026 +
+
diff --git a/src/sections/Sponsors.tsx b/src/sections/Sponsors.tsx index 1a3635d..3a86b9d 100644 --- a/src/sections/Sponsors.tsx +++ b/src/sections/Sponsors.tsx @@ -1,40 +1,39 @@ import React from 'react'; -// import SponsorCard from '../components/SponsorCard'; +import SponsorCard from '../components/SponsorCard'; +import DotGrid from '../components/DotGrid'; import { motion } from 'framer-motion'; const Sponsors: React.FC = () => { -/* + const platinumSponsors = [ - { name: "Webflow", logo: "/logos/logoo 1(main).png" }, // Mock data - { name: "Finsweet", logo: "/logos/logoo 2.png" }, + { name: "Webflow", logo: "/logos/logoo 1(main).png" }, + { name: "Finsweet", logo: "/logos/logoo 2.png" }, ]; const goldSponsors = [ - { name: "Weglot", logo: "/logos/logoo 3.png" }, - { name: "LottieFiles", logo: "/logos/logoo 4.png" }, - { name: "Refokus", logo: "/logos/logoo 5.png" }, - { name: "Edgar Allan", logo: "/logos/logoo6.png" }, - { name: "Jetboost", logo: "/logos/logoo7.png" }, + { name: "Weglot", logo: "/logos/logoo 3.png" }, + { name: "LottieFiles", logo: "/logos/logoo 4.png" }, + { name: "Refokus", logo: "/logos/logoo 5.png" }, + { name: "Edgar Allan", logo: "/logos/logoo6.png" }, + { name: "Jetboost", logo: "/logos/logoo7.png" }, ]; const streamSponsors = [ - { name: "Stream A", logo: "/logos/logoo8.png" }, - { name: "Stream B", logo: "/logos/logoo9(main).png" }, - { name: "Stream C", logo: "/logos/logoo 3.png" }, + { name: "Stream A", logo: "/logos/logoo8.png" }, + { name: "Stream B", logo: "/logos/logoo9(main).png" }, + { name: "Stream C", logo: "/logos/logoo 3.png" }, ]; const communitySponsors = [ - { name: "Community A", logo: "/logos/logoo 4.png" }, - { name: "Community B", logo: "/logos/logoo 5.png" }, - { name: "Community C", logo: "/logos/logoo6.png" }, - { name: "Community D", logo: "/logos/logoo7.png" }, - { name: "Community E", logo: "/logos/logoo8.png" }, - { name: "Community F", logo: "/logos/logoo9(main).png" }, - { name: "Community G", logo: "/logos/logoo 1(main).png" }, + { name: "Community A", logo: "/logos/logoo 4.png" }, + { name: "Community B", logo: "/logos/logoo 5.png" }, + { name: "Community C", logo: "/logos/logoo6.png" }, + { name: "Community D", logo: "/logos/logoo7.png" }, + { name: "Community E", logo: "/logos/logoo8.png" }, + { name: "Community F", logo: "/logos/logoo9(main).png" }, + { name: "Community G", logo: "/logos/logoo 1(main).png" }, ]; -*/ -/* const containerVariants = { hidden: { opacity: 0 }, visible: { @@ -45,101 +44,97 @@ const Sponsors: React.FC = () => { const itemVariants = { hidden: { opacity: 0, y: 10 }, - visible: { - opacity: 1, + visible: { + opacity: 1, y: 0, transition: { type: "spring" as const, stiffness: 100 } }, }; -*/ - - // Gradient Wavy Divider Component - /* - const WavyLine = () => ( -
- - - - - - - - - - -
+ + const TierLabel = ({ label }: { label: string }) => ( + +
+ + {label} + +
+ ); - */ return ( -
-
- +
+ {/* Interactive DotGrid Background */} +
+ +
+ +
+ {/* Sponsors Header */} -
+
-
- - + initial={{ opacity: 0, scaleX: 0 }} + whileInView={{ opacity: 1, scaleX: 1 }} + transition={{ duration: 0.6, delay: 0.1 }} + className="h-[2px] w-20 bg-[#f97028] mx-auto mb-6" + /> Sponsors + + The teams making DevHack 3.0 possible +
- {/* Platinum Content (Coming Soon) */} -
- + + - - Coming Soon - - {/* - - {platinumSponsors.map((s, i) => ( - - - - ))} - - */} + {platinumSponsors.map((s, i) => ( + + + + ))}
- {/* - - -
-

Gold Sponsors

- + + {goldSponsors.map((s, i) => ( @@ -149,15 +144,14 @@ const Sponsors: React.FC = () => {
- - -
-

Stream Sponsors

- + + {streamSponsors.map((s, i) => ( @@ -167,15 +161,14 @@ const Sponsors: React.FC = () => {
- - -
-

Community Sponsors

- + + {communitySponsors.map((s, i) => ( @@ -184,7 +177,6 @@ const Sponsors: React.FC = () => { ))}
- */}
diff --git a/src/sections/Team.tsx b/src/sections/Team.tsx index a18c19b..205f1c7 100644 --- a/src/sections/Team.tsx +++ b/src/sections/Team.tsx @@ -1,348 +1,384 @@ import { useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; -import TeamCarousel from '../components/TeamCarousel'; +import { FaLinkedin, FaInstagram } from 'react-icons/fa'; +// --- Types --- interface TeamMember { id: string; name: string; role: string; - bio?: string; image: string; + twitter?: string; linkedin?: string; + instagram?: string; } -// ── Main tabs ── -const mainTabs = ['Organizing Team', 'Patrons', 'Faculty', 'Judges'] as const; -type MainTab = (typeof mainTabs)[number]; - -// ── Sub-tabs (under "Meet the Entire Team") ── -const subTabs = ['Web Team', 'Design Team', 'Marketing Team', 'Sponsorship Team'] as const; -type SubTab = (typeof subTabs)[number]; - -// ── Data ── -const organizingTeam: TeamMember[] = [ - { - id: 'org-1', - name: 'Dr. Bipin Kumar Rai', - role: 'Faculty Coordinator', - bio: 'Professor, CSE', - image: - 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'org-2', - name: 'Utkarsh Priye Jha', - role: 'Student Coordinator', - image: - 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'org-3', - name: 'Ritvik Vasundh', - role: 'Student Coordinator', - image: - 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80', - }, +// --- Data --- + +const chiefPatrons: TeamMember[] = [ + { id: 'cp1', name: 'Dr. D. Hemachandra Sagar', role: 'Chancellor, DSU', image: 'https://images.unsplash.com/photo-1556157382-97eda2d62296?auto=format&fit=crop&w=400&q=80' }, + { id: 'cp2', name: 'Dr. D. Premachandra Sagar', role: 'Pro Chancellor, DSU', image: 'https://images.unsplash.com/photo-1557862921-37829c790f19?auto=format&fit=crop&w=400&q=80' }, ]; const patrons: TeamMember[] = [ - { - id: 'pat-1', - name: 'Prof. Ramesh Gupta', - role: 'Patron', - bio: 'Director', - image: - 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'pat-2', - name: 'Dr. Meena Sharma', - role: 'Co-Patron', - bio: 'Dean, Engineering', - image: - 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=400&q=80', - }, + { id: 'p1', name: 'Dr. B. S. Satyanarayana', role: 'Vice Chancellor, DSU', image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&w=400&q=80' }, + { id: 'p2', name: 'Prof. R Janardhan', role: 'Pro Vice Chancellor, DSU', image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80' }, + { id: 'p3', name: 'Dr. Prakash S', role: 'Pro Vice Chancellor, DSU', image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=400&q=80' }, + { id: 'p4', name: 'Dr. C.Puttamadapappa', role: 'Registrar, DSU', image: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80' }, + { id: 'fac1', name: 'Dr.Udaya Kumar Reddy K.R', role: 'Professor & Dean-SoE', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80' }, + { id: 'fac2', name: 'Dr. Girisha G S', role: 'Chairperson, CSE', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80' } ]; -const faculty: TeamMember[] = [ - { - id: 'fac-1', - name: 'Dr. Anjali Verma', - role: 'Faculty Advisor', - bio: 'HoD, CSE', - image: - 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'fac-2', - name: 'Dr. Vikram Patel', - role: 'Faculty Advisor', - bio: 'Associate Professor', - image: - 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80', - }, +const judges: TeamMember[] = [ + { id: 'j1', name: 'Arjun Mehta', role: 'CTO, TechCorp', image: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80' }, + { id: 'j2', name: 'Sneha Iyer', role: 'VP Engineering, InnoSoft', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80' }, ]; -const judges: TeamMember[] = [ - { - id: 'jud-1', - name: 'Arjun Mehta', - role: 'Industry Expert', - bio: 'CTO, TechCorp', - image: - 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'jud-2', - name: 'Sneha Iyer', - role: 'Industry Expert', - bio: 'VP Engineering, InnoSoft', - image: - 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80', - }, +const coreTeam: TeamMember[] = [ + { id: '0', name: 'Dr. Bipin Kumar Rai', role: 'Faculty Coordinator', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80' }, + { id: '1', name: 'Trisha', role: 'Student Co ordinator', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Trisha' }, + { id: '2', name: 'S Shreenidhi', role: 'Student Co ordinator', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Shreenidhi' }, ]; -const subTeams: Record = { - 'Web Team': [ - { - id: 'web-1', - name: 'Sachin Baluragi', - role: 'Web Team Co-Head', - image: - 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'web-2', - name: 'S Shreenidhi', - role: 'Web Dev', - image: - 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'web-3', - name: 'G Nithesh', - role: 'Web Dev', - image: - 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'web-4', - name: 'Manavi P', - role: 'Web Dev', - image: - 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=400&q=80', - }, - ], - 'Design Team': [ - { - id: 'des-1', - name: 'Priya Nair', - role: 'Design Lead', - image: - 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'des-2', - name: 'Raj Kulkarni', - role: 'UI Designer', - image: - 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=400&q=80', - }, - ], - 'Marketing Team': [ - { - id: 'mkt-1', - name: 'Ananya Das', - role: 'Marketing Lead', - image: - 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'mkt-2', - name: 'Karthik Rao', - role: 'Content Writer', - image: - 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80', - }, - ], - 'Sponsorship Team': [ - { - id: 'spo-1', - name: 'Divya Hegde', - role: 'Sponsorship Lead', - image: - 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80', - }, - { - id: 'spo-2', - name: 'Nihal Khan', - role: 'Sponsorship Associate', - image: - 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80', - }, - ], -}; - -// ── Helpers ── -function getMembersForMainTab(tab: MainTab): TeamMember[] { - switch (tab) { - case 'Organizing Team': - return organizingTeam; - case 'Patrons': - return patrons; - case 'Faculty': - return faculty; - case 'Judges': - return judges; - } -} +const subHeads: TeamMember[] = [ + { id: '3', name: 'Manavi P', role: 'Web Team Lead', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Manavi' }, + { id: '4', name: 'Raksha', role: 'Design Team Lead', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Raksha' }, + { id: '5', name: 'Aastha', role: 'Sponsorship Team Co Lead', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Aastha' }, + { id: '6', name: 'Nishchal Gowda R', role: 'Marketing and Media Lead', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Nishchal' }, + { id: '7', name: 'G Nithesh', role: 'Web Co Lead & Ops Lead', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Nithesh' }, +]; -function getMainTabTitle(tab: MainTab): string { - switch (tab) { - case 'Organizing Team': - return 'Organising Team'; - case 'Patrons': - return 'Patrons'; - case 'Faculty': - return 'Faculty'; - case 'Judges': - return 'Judges'; - } -} +const theOperators: TeamMember[] = [ + { id: 'w1', name: 'Omkar G K', role: 'Web Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Omkar' }, + { id: 'w2', name: 'Supraj U Sivajji', role: 'Web Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Supraj' }, + { id: 'd1', name: 'Moulika', role: 'Design Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Moulika' }, + { id: 'd2', name: 'Moulya', role: 'Design Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Moulya' }, + { id: 'd3', name: 'Hridya', role: 'Design Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Hridya' }, + { id: 'm1', name: 'Pranay', role: 'Marketing Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Pranay' }, + { id: 's1', name: 'Naman Sharaff', role: 'Sponsorship Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Naman' }, + { id: 's2', name: 'Thathagath', role: 'Sponsorship Team', image: 'https://api.dicebear.com/9.x/pixel-art/svg?seed=Thathagath' }, +]; + +const facultyCoordinator: TeamMember[] = [ + { id: 'fc1', name: 'Dr. Bipin Kumar Rai', role: 'Faculty Coordinator - Professor, CSE', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80' } +]; + +const facultyOrganizers: TeamMember[] = [ + { id: 'fo1', name: 'Dr. Meenakshi Malhotra', role: 'Associate Professor', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo2', name: 'Dr. Sivananda Reddy', role: 'Associate Professor', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo3', name: 'Prof. Bharath M B', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo4', name: 'Prof. Dharmendra D P', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo5', name: 'Dr. Kumar Dilip', role: 'Associate Professor', image: 'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo6', name: 'Prof. Yashaswini H C', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1599566150163-29194dcaad36?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo7', name: 'Dr. Shreekant Salotagi', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo8', name: 'Prof. Smriti Bharti', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo9', name: 'Prof. Shivani', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1556157382-97eda2d62296?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo10', name: 'Dr Naitik ST', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?auto=format&fit=crop&w=400&q=80' }, + { id: 'fo11', name: 'Dr. Pannangi Naresh', role: 'Assistant Professor', image: 'https://images.unsplash.com/photo-1560250097-0b93528c311a?auto=format&fit=crop&w=400&q=80' }, +]; + +// --- Sub-components --- -// ── Pill Tab Bar ── -function TabBar({ - tabs, - active, - onChange, - accentColor, -}: { - tabs: readonly T[]; - active: T; - onChange: (tab: T) => void; - accentColor?: string; -}) { - const accent = accentColor ?? '#f97028'; +function CoinDivider() { return ( -
- {tabs.map((tab) => { - const isActive = tab === active; - return ( - - ); - })} +
+
+
+ {[...Array(5)].map((_, i) => ( +
+ ))} +
); } -// ── Main Section ── -export default function Team() { - const [activeMainTab, setActiveMainTab] = useState('Organizing Team'); - const [activeSubTab, setActiveSubTab] = useState('Web Team'); +/** Reusable section sub-heading */ +function SectionSubHeading({ children }: { children: React.ReactNode }) { + return ( +

+ {children} +

+ ); +} - const mainMembers = getMembersForMainTab(activeMainTab); - const sectionTitle = getMainTabTitle(activeMainTab); +function TeamCard({ member }: { member: TeamMember }) { + const [isHovered, setIsHovered] = useState(false); return ( -
- {/* ── Main Tab Bar ── */} -
- { - setActiveMainTab(t); - // Reset sub-tab when switching - setActiveSubTab('Web Team'); + setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > + {/* Corner Rivets */} + {[ + { top: 4, left: 4 } as React.CSSProperties, + { top: 4, right: 4 } as React.CSSProperties, + { bottom: 4, left: 4 } as React.CSSProperties, + { bottom: 4, right: 4 } as React.CSSProperties, + ].map((pos, ri) => ( +
+ ))} + + {/* Image Box */} +
+ {/* Scanline overlay */} +
+ {member.name}
- {/* ── Section Title ── */} -

- {sectionTitle} -

- - {/* ── Main Tab Carousel ── */} - - - - - - - {/* ── "Meet the Entire Team" — only under Organizing Team ── */} - {activeMainTab === 'Organizing Team' && ( - +

-

- Meet the Entire Team -

- - {/* Sub-tab bar */} -
- + {member.name} + +

{member.role}

+ + +
+
+ ); +} + +/** Responsive grid wrapper for team cards */ +function TeamGrid({ members }: { members: TeamMember[] }) { + return ( +
+ {members.map((member) => ( + + ))} +
+ ); +} + +/** Small grid for 1-3 items — centers them */ +function TeamGridSmall({ members }: { members: TeamMember[] }) { + return ( +
+ {members.map((member) => ( +
+ +
+ ))} +
+ ); +} - {/* Sub-tab Carousel */} - +export default function Team() { + const [activeTab, setActiveTab] = useState('Organising Team'); + const [activeSubTeam, setActiveSubTeam] = useState('Web Team'); + + const TABS = ['Organising Team', 'The Board', 'Faculty', 'Jury & Experts']; + + return ( +
+
+ + {/* Header block conditionally rendered */} + + {activeTab === 'Organising Team' && ( - +
+ + Meet The + Team + +
-
- - )} + )} + + + {/* Tab Navigation — scrollable on mobile */} +
+
+ {TABS.map((tab) => ( + + ))} +
+
+ + {/* Tab Content */} + + + {activeTab === 'Organising Team' && ( +
+
+ Core Team + +
+ +
+

+ Meet the Entire Team +

+ +
+
+ {['Web Team', 'Design Team', 'Marketing Team', 'Sponsorship Team'].map(team => ( + + ))} +
+
+ + + + {(() => { + const allMembers = [...coreTeam, ...subHeads, ...theOperators]; + const keyword = activeSubTeam.split(' ')[0]; + const membersToRender = allMembers.filter(m => m.role.includes(keyword)); + + return membersToRender.length > 0 ? ( + + ) : ( +
+ Team members incoming... +
+ ); + })()} +
+
+
+
+ )} + + {activeTab === 'The Board' && ( +
+
+ Chief Patrons + +
+ + + +
+ Patrons + +
+
+ )} + + {activeTab === 'Faculty' && ( +
+
+ Faculty Coordinator + +
+ + + +
+ Organising Team Members + +
+
+ )} + + {activeTab === 'Jury & Experts' && ( +
+ Jury & Experts + +
+ )} +
+
+ + {/* Decorative Divider */} +
+ +
+ +
); } diff --git a/src/sections/Timeline.tsx b/src/sections/Timeline.tsx index 3ce9bf9..eba3db1 100644 --- a/src/sections/Timeline.tsx +++ b/src/sections/Timeline.tsx @@ -101,6 +101,42 @@ function TrainSVG({ color }: { color: string }) { ); } +/* ───────────────────────────────────── + SVG SUBMARINE ICON (FOR MOBILE) + ───────────────────────────────────── */ +function SubmarineSVG({ color }: { color: string }) { + return ( + + {/* Submarine Body (Oval/Capsule) */} + + + {/* Conning Tower */} + + {/* Periscope */} + + + {/* Portholes */} + + + + + {/* Accent Stripe */} + + + {/* Rear Propeller Handle/Base */} + + {/* Propeller Blades */} + + + ); +} + /* ───────────────────────────────────── TRACK COMPONENT ───────────────────────────────────── */ @@ -772,46 +808,48 @@ export default function Timeline() { zIndex: 40, }} > - {/* Smoke puff animation */} + {/* Puffs or Bubbles animation */}
- {/* Train body with bounce */} + {/* Vehicle body with bounce */} - + {isMobile ? : } {/* Active station name label on train */} diff --git a/src/sections/photos.ts b/src/sections/photos.ts new file mode 100644 index 0000000..b5b60ea --- /dev/null +++ b/src/sections/photos.ts @@ -0,0 +1,27 @@ +export const photos = [ + { src: "/assets/hero1.webp", width: 800, height: 600 }, + { src: "/assets/hero2.webp", width: 600, height: 800 }, + { src: "/assets/hero3.webp", width: 800, height: 600 }, + { src: "/assets/hero4.webp", width: 600, height: 800 }, + { src: "/assets/hero5.webp", width: 800, height: 600 }, + { src: "/assets/hero6.webp", width: 800, height: 600 }, + { src: "/assets/hero7.webp", width: 800, height: 600 }, + { src: "/assets/hero8.webp", width: 800, height: 600 }, + { src: "/assets/hero9.webp", width: 800, height: 600 }, + { src: "/assets/hero10.webp", width: 800, height: 600 }, + { src: "/assets/hero11.webp", width: 800, height: 600 }, + { src: "/assets/hero12.webp", width: 800, height: 600 }, + { src: "/assets/hero13.webp", width: 800, height: 600 }, + { src: "/assets/hero14.webp", width: 800, height: 600 }, + { src: "/assets/hero15.webp", width: 800, height: 600 }, + { src: "/assets/hero16.webp", width: 800, height: 600 }, + { src: "/assets/hero17.webp", width: 800, height: 600 }, + { src: "/assets/hero18.webp", width: 800, height: 600 }, + { src: "/assets/hero19.webp", width: 800, height: 600 }, + { src: "/assets/hero20.webp", width: 800, height: 600 }, + { src: "/assets/hero21.webp", width: 800, height: 600 }, + { src: "/assets/hero22.webp", width: 800, height: 600 }, + { src: "/assets/hero23.webp", width: 800, height: 600 }, +]; + +export default photos;