|
1 | 1 | <script lang="ts"> |
| 2 | + import ScrollToTop from '$lib/components/scroll-to-top/scroll.svelte'; |
2 | 3 | import Hackathon from './hackathon.svelte'; |
3 | 4 | import HACKATHONS from './data'; |
4 | 5 | import Spacing from '$lib/public/legacy/spacing.svelte'; |
| 6 | + import type { Hackathon as HackathonType } from '$lib/public/hackathons'; |
| 7 | +
|
| 8 | + // Extract year from date string (e.g., "April 2025" -> 2025) |
| 9 | + function getYear(dateStr: string): number { |
| 10 | + const match = dateStr.match(/\d{4}/); |
| 11 | + return match ? parseInt(match[0]) : 0; |
| 12 | + } |
| 13 | +
|
| 14 | + // Sort hackathons by year |
| 15 | + const sortedHackathons: HackathonType[] = [...HACKATHONS].sort( |
| 16 | + (a, b) => getYear(b.date) - getYear(a.date) |
| 17 | + ); |
| 18 | +
|
| 19 | + // Group hackathons by year |
| 20 | + const hackathonsByYear: Record<number, HackathonType[]> = {}; |
| 21 | + for (const hackathon of sortedHackathons) { |
| 22 | + const year = getYear(hackathon.date); |
| 23 | + if (!hackathonsByYear[year]) { |
| 24 | + hackathonsByYear[year] = []; |
| 25 | + } |
| 26 | + hackathonsByYear[year].push(hackathon); |
| 27 | + } |
| 28 | +
|
| 29 | + // Get sorted years |
| 30 | + const years = Object.keys(hackathonsByYear) |
| 31 | + .map(Number) |
| 32 | + .sort((a, b) => b - a); |
| 33 | +
|
| 34 | + // Scroll to year function - scrolls to first hackathon of that year |
| 35 | + function scrollToYear(year: number) { |
| 36 | + const hackathons = hackathonsByYear[year]; |
| 37 | + if (hackathons && hackathons.length > 0) { |
| 38 | + const firstHackathon = hackathons[0]; |
| 39 | + const element = document.getElementById(firstHackathon.id); |
| 40 | + if (element) { |
| 41 | + const navbarHeight = 80; |
| 42 | + const extraPadding = 20; |
| 43 | + const elementPosition = element.getBoundingClientRect().top + window.scrollY; |
| 44 | + const offsetPosition = elementPosition - navbarHeight - extraPadding; |
| 45 | +
|
| 46 | + window.scrollTo({ |
| 47 | + top: offsetPosition, |
| 48 | + behavior: 'smooth', |
| 49 | + }); |
| 50 | + } |
| 51 | + } |
| 52 | + } |
5 | 53 | </script> |
6 | 54 |
|
7 | 55 | <svelte:head> |
8 | | - <title>Hackthons | ACM at CSUF</title> |
| 56 | + <title>Hackathons | ACM at CSUF</title> |
9 | 57 | </svelte:head> |
10 | 58 |
|
11 | | -<Spacing --min="175px" --med="200px" --max="200px" /> |
| 59 | +<!-- Year sections with alternating backgrounds --> |
| 60 | +{#each years as year, index (year)} |
| 61 | + <section |
| 62 | + id="year-{year}" |
| 63 | + class="year-section" |
| 64 | + class:first-section={index === 0} |
| 65 | + class:alt-bg={index % 2 === 0} |
| 66 | + > |
| 67 | + <div class="year-section-content"> |
| 68 | + <!-- Show header only in the first section --> |
| 69 | + {#if index === 0} |
| 70 | + <header class="page-header"> |
| 71 | + <h1><span class="title-accent">ACM</span> at CSUF Hackathons</h1> |
| 72 | + <p class="page-caption"> |
| 73 | + Journey through our annual hackathons and see how it all started. Open to students from |
| 74 | + all colleges and universities as we support and motivate the next generation of |
| 75 | + builders. |
| 76 | + </p> |
| 77 | + |
| 78 | + <nav class="year-nav"> |
| 79 | + {#each years as navYear (navYear)} |
| 80 | + <button |
| 81 | + class="year-pill" |
| 82 | + on:click={() => scrollToYear(navYear)} |
| 83 | + aria-label="Jump to {navYear} hackathons" |
| 84 | + > |
| 85 | + {navYear} |
| 86 | + </button> |
| 87 | + {/each} |
| 88 | + </nav> |
| 89 | + </header> |
| 90 | + {/if} |
| 91 | + |
| 92 | + <div class="hackathons-grid"> |
| 93 | + {#each hackathonsByYear[year] as hackathon (hackathon.id)} |
| 94 | + <Hackathon data={hackathon} /> |
| 95 | + {/each} |
| 96 | + </div> |
| 97 | + </div> |
| 98 | + </section> |
| 99 | +{/each} |
12 | 100 |
|
13 | | -<div class="hackathons"> |
14 | | - <h1>Hackathons</h1> |
| 101 | +<Spacing --min="100px" --med="125px" --max="125px" /> |
15 | 102 |
|
16 | | - <div> |
17 | | - {#each HACKATHONS as hackathon (hackathon.id)} |
18 | | - <Hackathon data={hackathon} /> |
19 | | - {/each} |
20 | | - </div> |
21 | | -</div> |
| 103 | +<ScrollToTop /> |
22 | 104 |
|
23 | 105 | <style> |
24 | | - .hackathons { |
| 106 | + .page-header { |
| 107 | + text-align: center; |
| 108 | + max-width: 1400px; |
| 109 | + margin: 0 auto 2rem; |
| 110 | + } |
| 111 | +
|
| 112 | + .page-header h1 { |
| 113 | + margin-bottom: 1rem; |
| 114 | + } |
| 115 | +
|
| 116 | + .title-accent { |
| 117 | + color: #00b0ff; |
| 118 | + } |
| 119 | +
|
| 120 | + .page-caption { |
| 121 | + font-size: 1.1rem; |
| 122 | + opacity: 0.85; |
| 123 | + max-width: 50ch; |
| 124 | + margin: 0 auto; |
| 125 | + line-height: 1.6; |
| 126 | + } |
| 127 | +
|
| 128 | + /* Year quick navigation */ |
| 129 | + .year-nav { |
25 | 130 | display: flex; |
26 | | - flex-direction: column; |
27 | | - align-items: center; |
28 | 131 | justify-content: center; |
| 132 | + flex-wrap: wrap; |
| 133 | + gap: 0.75rem; |
| 134 | + margin-top: 1.5rem; |
| 135 | + } |
| 136 | +
|
| 137 | + .year-pill { |
| 138 | + background: rgba(0, 176, 255, 0.1); |
| 139 | + border: 1px solid rgba(0, 176, 255, 0.3); |
| 140 | + color: #00b0ff; |
| 141 | + padding: 0.5rem 1.25rem; |
| 142 | + border-radius: 50px; |
| 143 | + font-size: 0.95rem; |
| 144 | + font-weight: 600; |
| 145 | + cursor: pointer; |
| 146 | + transition: all 0.2s ease; |
| 147 | + } |
| 148 | +
|
| 149 | + .year-pill:hover { |
| 150 | + background: rgba(0, 176, 255, 0.2); |
| 151 | + transform: translateY(-2px); |
| 152 | + } |
| 153 | + .year-section { |
| 154 | + width: 100%; |
| 155 | + max-width: 100%; |
| 156 | + padding: 3rem 2rem; |
| 157 | + box-sizing: border-box; |
| 158 | + overflow-x: hidden; |
| 159 | + background-color: var(--acm-odd); |
| 160 | + } |
| 161 | +
|
| 162 | + /* First section needs extra top padding for navbar */ |
| 163 | + .year-section.first-section { |
| 164 | + padding-top: 200px; |
| 165 | + } |
| 166 | +
|
| 167 | + /* Alternating background using same variables as teams page */ |
| 168 | + .year-section.alt-bg { |
| 169 | + background-color: var(--acm-even); |
| 170 | + } |
| 171 | +
|
| 172 | + .year-section-content { |
| 173 | + max-width: 1400px; |
| 174 | + margin: 0 auto; |
| 175 | + } |
| 176 | +
|
| 177 | + .hackathons-grid { |
| 178 | + display: grid; |
| 179 | + grid-template-columns: repeat(2, 1fr); |
| 180 | + gap: 2rem; |
| 181 | + width: 100%; |
| 182 | + } |
| 183 | +
|
| 184 | + /* When there's only one hackathon in a year center it */ |
| 185 | + .hackathons-grid > :global(:only-child) { |
| 186 | + grid-column: 1 / -1; |
| 187 | + } |
| 188 | +
|
| 189 | + @media (max-width: 1024px) { |
| 190 | + .hackathons-grid { |
| 191 | + grid-template-columns: 1fr; |
| 192 | + gap: 3rem; |
| 193 | + } |
| 194 | + } |
| 195 | +
|
| 196 | + @media (max-width: 768px) { |
| 197 | + .page-header { |
| 198 | + margin-bottom: 1.5rem; |
| 199 | + } |
| 200 | +
|
| 201 | + .page-caption { |
| 202 | + font-size: 1rem; |
| 203 | + max-width: 35ch; |
| 204 | + } |
| 205 | +
|
| 206 | + .year-nav { |
| 207 | + gap: 0.5rem; |
| 208 | + } |
| 209 | +
|
| 210 | + .year-pill { |
| 211 | + padding: 0.4rem 1rem; |
| 212 | + font-size: 0.85rem; |
| 213 | + } |
| 214 | +
|
| 215 | + .year-section { |
| 216 | + padding: 2rem 1rem; |
| 217 | + } |
| 218 | +
|
| 219 | + .year-section.first-section { |
| 220 | + padding-top: 150px; |
| 221 | + } |
29 | 222 | } |
30 | 223 | </style> |
0 commit comments