Skip to content

Commit c49380b

Browse files
authored
Merge branch 'main' into feat/1185
2 parents b95f26f + 818b044 commit c49380b

8 files changed

Lines changed: 379 additions & 55 deletions

File tree

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<script lang="ts">
2+
import { onMount } from 'svelte';
3+
4+
// Back to top button visibility
5+
let showBackToTop = false;
6+
7+
// Scroll to top function
8+
function scrollToTop() {
9+
window.scrollTo({ top: 0, behavior: 'smooth' });
10+
}
11+
12+
onMount(() => {
13+
function handleScroll() {
14+
showBackToTop = window.scrollY > 500;
15+
}
16+
window.addEventListener('scroll', handleScroll);
17+
return () => {
18+
window.removeEventListener('scroll', handleScroll);
19+
};
20+
});
21+
</script>
22+
23+
<button
24+
class="back-to-top"
25+
class:visible={showBackToTop}
26+
on:click={scrollToTop}
27+
aria-label="Back to top"
28+
>
29+
30+
</button>
31+
32+
<style>
33+
/* Back to top button */
34+
.back-to-top {
35+
position: fixed;
36+
bottom: 2rem;
37+
right: 2rem;
38+
width: 50px;
39+
height: 50px;
40+
border-radius: 50%;
41+
background: #00b0ff;
42+
color: white;
43+
border: none;
44+
font-size: 1.5rem;
45+
cursor: pointer;
46+
opacity: 0;
47+
visibility: hidden;
48+
transition: all 0.3s ease;
49+
box-shadow: 0 4px 15px rgba(0, 176, 255, 0.4);
50+
z-index: 1000;
51+
}
52+
.back-to-top:hover {
53+
background: #00c8ff;
54+
transform: translateY(-3px);
55+
box-shadow: 0 6px 20px rgba(0, 176, 255, 0.5);
56+
}
57+
.back-to-top.visible {
58+
opacity: 1;
59+
visibility: visible;
60+
}
61+
62+
@media (max-width: 768px) {
63+
.back-to-top {
64+
bottom: 1.5rem;
65+
right: 1.5rem;
66+
width: 45px;
67+
height: 45px;
68+
font-size: 1.25rem;
69+
}
70+
}
71+
</style>

src/routes/(site)/+page.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import Teams from './teams.svelte';
66
import Lucky from './lucky.svelte';
77
import FAQ from './faq.svelte';
8-
// import Blog from './blog.svelte';
8+
import ScrollToTop from '$lib/components/scroll-to-top/scroll.svelte';
99
</script>
1010

1111
<svelte:head>
@@ -101,6 +101,8 @@
101101
</div> -->
102102
</div>
103103

104+
<ScrollToTop />
105+
104106
<style>
105107
.section-container {
106108
background-color: var(--acm-odd);

src/routes/(site)/blog/+page.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import LabelField from './labelfield.svelte';
55
import { makeBlogPostsPageDataURL, makeBlogPostsPageURL } from '$lib/public/blog/urls';
66
import BlogPostPreview from '$lib/components/blog/blog-post-preview.svelte';
7+
import ScrollToTop from '$lib/components/scroll-to-top/scroll.svelte';
78
89
export let data: PageData;
910
@@ -72,6 +73,8 @@
7273

7374
<Spacing --min="40px" --med="95px" --max="120px" />
7475

76+
<ScrollToTop />
77+
7578
<style lang="scss">
7679
.subtitle {
7780
a {
Lines changed: 206 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,223 @@
11
<script lang="ts">
2+
import ScrollToTop from '$lib/components/scroll-to-top/scroll.svelte';
23
import Hackathon from './hackathon.svelte';
34
import HACKATHONS from './data';
45
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+
}
553
</script>
654

755
<svelte:head>
8-
<title>Hackthons | ACM at CSUF</title>
56+
<title>Hackathons | ACM at CSUF</title>
957
</svelte:head>
1058

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}
12100

13-
<div class="hackathons">
14-
<h1>Hackathons</h1>
101+
<Spacing --min="100px" --med="125px" --max="125px" />
15102

16-
<div>
17-
{#each HACKATHONS as hackathon (hackathon.id)}
18-
<Hackathon data={hackathon} />
19-
{/each}
20-
</div>
21-
</div>
103+
<ScrollToTop />
22104

23105
<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 {
25130
display: flex;
26-
flex-direction: column;
27-
align-items: center;
28131
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+
}
29222
}
30223
</style>

src/routes/(site)/hackathons/data.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export default [
88
location: 'Virtual',
99
theme: 'Camping',
1010
description:
11-
'It all began in 2021 when Jacob Nguyen and Samuel Sandoval reignited the tradition of hackathons at California State University Fullerton, being the first CSUF hackathon in decades. Due to the global pandemic, TuffyHacks 2021 was held virtually.',
11+
'It all began in 2021 when Jacob Nguyen and Samuel Sandoval reignited the tradition of hackathons at California State University, Fullerton, being the first CSUF hackathon in decades. Due to the global pandemic, TuffyHacks 2021 was held virtually.',
1212
directors: [
1313
{ name: 'Jacob Nguyen', picture: '/people/jacob-nguyen.webp' },
1414
{ name: 'Samuel Sandoval', picture: '/people/samuel-sandoval.webp' },
@@ -33,7 +33,7 @@ export default [
3333
location: 'California State University, Fullerton',
3434
theme: 'Party',
3535
description:
36-
'As Jacob and Sam approached their graduation dates, they transitioned the leadership role to Daniel "Anh Duy" Truong. The hackathon name was changed to "FullyHacks" starting with FullyHacks 2023. With the global pandemic starting to improve, FullyHacks 2023 became the first in-person hackathon event of the series.',
36+
'As Jacob and Sam approached their graduation dates, they transitioned the leadership role to Daniel "Anh Duy" Truong. The hackathon name was changed to "FullyHacks", starting with FullyHacks 2023. With the global pandemic starting to improve, FullyHacks 2023 became the first in-person hackathon event of the series.',
3737
directors: [{ name: 'Daniel Truong', picture: '/people/daniel-truong.webp' }],
3838
},
3939
{

0 commit comments

Comments
 (0)