Skip to content
Merged
2 changes: 2 additions & 0 deletions src/components/layout/NavBar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
>
<NavButton href="/">Home</NavButton>
<NavButton href="/council">Meet the council</NavButton>
<NavButton href="/team">Team</NavButton>
<NavButton href="/events">Events</NavButton>
<NavButton href="/resources">Resources</NavButton>
{#if isElectionTime}
Expand All @@ -50,6 +51,7 @@
</a>
<NavButton href="/">Home</NavButton>
<NavButton href="/council">Meet the council</NavButton>
<NavButton href="/team">Team</NavButton>
<NavButton href="/events">Events</NavButton>
<NavButton href="/resources">Resources</NavButton>
{#if isElectionTime}
Expand Down
17 changes: 17 additions & 0 deletions src/components/team/CommitDot.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<script lang="ts">
let { active = false } = $props<{
active?: boolean;
}>();
</script>

<div class="relative">
<div
class="relative inline-flex items-center justify-center rounded-full transition-all duration-200 size-2.5 bg-ecsess-500 {active
? 'bg-ecsess-200'
: 'bg-ecsess-500'}"
>
{#if active}
<div class="relative inline-flex items-center justify-center bg-ecsess-100 size-2.5 animate-ping rounded-full"></div>
{/if}
</div>
</div>
38 changes: 38 additions & 0 deletions src/components/team/ContribTimeline.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import type { DevTeam } from '$lib/schemas';
import YearLine from './YearLine.svelte';
import DevCard from './DevCard.svelte';

let {
year,
members,
active = false
} = $props<{
year: string;
members: DevTeam[];
active?: boolean;
}>();
</script>

<div class="relative w-full">
<!-- Branch Line with Year -->
<div class="mb-6">
<YearLine {year} {active} />
</div>

<!-- Members Grid -->
<div class="ml-8 grid grid-cols-1 gap-8 sm:grid-cols-2 lg:grid-cols-3">
{#each members as member}
<DevCard
name={member.name}
role={member.role}
year={member.yearProgram}
src={member.image}
funFact={member.funFact}
github={member.github}
email={member.email}
{active}
/>
{/each}
</div>
</div>
126 changes: 126 additions & 0 deletions src/components/team/DevCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<script lang="ts">
import { Github, Mail } from '@lucide/svelte';

let {
name,
role,
year,
src,
funFact,
github,
email,
active = false
} = $props<{
name: string;
role: string;
year: string;
src: string;
funFact: string;
github: string;
email: string;
active?: boolean;
}>();

// Extract GitHub username from URL and construct avatar URL
let githubAvatar = $derived.by(() => {
if (!github) return src;
try {
const url = new URL(github);
const username = url.pathname.split('/').filter(Boolean)[0];
return username ? `https://github.com/${username}.png?size=200` : src;
} catch {
return src;
}
});
</script>

<div class="fadeup group relative flex flex-col">
<!-- Commit Card -->
<div
class="relative w-full overflow-hidden rounded-xl transition-all ease-linear duration-100 hover:scale-101 hover:shadow-ecsess-black/50 hover:shadow-md {active
? 'bg-ecsess-800 shadow-ecsess-black shadow-sm'
: 'bg-ecsess-900 shadow-ecsess-black'}"
>
<!-- Header -->
<div class="px-6 pt-5 pb-3">
<div class="flex flex-col items-center gap-2.5 text-center">
<img src={githubAvatar} alt={name} class="size-20 shrink-0 rounded-full object-cover" />
<div class="w-full">
<h3 class="text-ecsess-100 text-lg font-semibold">
{name}
</h3>
<p class="text-ecsess-400 text-base">{role}</p>
</div>
</div>
</div>

<!-- Body -->
<div class="space-y-3 px-6 pb-5">
<!-- Year Badge -->
<div class="flex items-center justify-center gap-2">
<span
class="rounded-full px-4 py-1.5 text-base font-medium {active
? 'bg-ecsess-900 text-ecsess-300'
: 'bg-ecsess-800 text-ecsess-400'}"
>
{year}
</span>
</div>

<!-- Fun Fact / Commit Message -->
<div class="min-h-[60px]">
<p class="text-ecsess-300 text-sm leading-relaxed italic">
{funFact || ``}
</p>
</div>

<!-- Actions -->
<div class="flex gap-3 pt-1">
{#if github}
<a
href={github}
target="_blank"
class="items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-base font-medium transition-colors {active
? 'bg-ecsess-900 text-ecsess-300 hover:bg-ecsess-950'
: 'bg-ecsess-800 text-ecsess-400 hover:bg-ecsess-700'} {active && email
? 'flex flex-1'
: 'flex w-full'}"
>
<Github class="size-4.5" />
GitHub
</a>
{/if}
{#if email && active}
<a
href="mailto:{email}"
class="flex flex-1 items-center justify-center gap-2 rounded-lg px-4 py-2.5 text-base font-medium transition-colors {active
? 'bg-ecsess-900 text-ecsess-300 hover:bg-ecsess-950'
: 'bg-ecsess-800 text-ecsess-400 hover:bg-ecsess-700'}"
>
<Mail class="size-4.5" />
Email
</a>
{/if}
</div>
</div>
</div>
</div>

<style>
.fadeup {
animation: fadeUp both;
animation-timeline: view();
animation-range: entry 20% cover 35%;
}

@keyframes fadeUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
</style>
27 changes: 27 additions & 0 deletions src/components/team/YearLine.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<script lang="ts">
import CommitDot from './CommitDot.svelte';

let { year, active = false } = $props<{
year: string;
active?: boolean;
}>();
</script>

<div class="relative flex items-center gap-4 mb-6">
<!-- Git Node column (w-3 = 12px, aligns with vertical timeline; circle centered) -->
<div class="flex w-3 shrink-0 justify-center">
<CommitDot {active} />
</div>

<!-- Year Label -->
<div
class="flex shrink-0 items-center justify-center font-mono text-xl font-bold tracking-wide uppercase transition-colors md:justify-start {active
? 'text-ecsess-300'
: 'text-ecsess-400'}"
>
<span>{year}</span>
</div>

<!-- Branch Line -->
<div class="h-px min-w-[100px] flex-1 {active ? 'bg-ecsess-500' : 'bg-ecsess-600'}"></div>
</div>
13 changes: 13 additions & 0 deletions src/lib/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,3 +81,16 @@ export type Redirect = {
shortname: string;
url: string;
};

export type DevTeam = {
name: string;
role: string;
yearProgram: string;
email: string;
active: boolean;
start: Date;
end: Date;
funFact: string;
github: string;
image: string; //URL
};
24 changes: 24 additions & 0 deletions src/routes/team/+page.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//Wait for CMS to setup
import type { DevTeam } from '$lib/schemas';
import { getFromCMS } from '$lib/utils';

const query = `*[_type == "devTeam"]{
name,
role,
yearProgram,
email,
active,
start,
end,
funFact,
github,
"image": image.asset->url+"?h=300&fm=webp",
}`;

export const load = async ({ url }) => {
let devTeam: DevTeam[] = await getFromCMS(query);
return {
devTeam: devTeam,
canonical: url.href
};
};
77 changes: 77 additions & 0 deletions src/routes/team/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script lang="ts">
import SeoMetaTags from 'components/layout/SeoMetaTags.svelte';
import Section from 'components/layout/Section.svelte';
import ContribTimeline from 'components/team/ContribTimeline.svelte';
import Link from 'components/Link.svelte';
import type { DevTeam } from '$lib/schemas.js';

let { data } = $props();

let devTeam = $derived(data.devTeam ?? []);

// Sort by Active (Current) first, then by start date (newest/latest first)
let sortedTeam = $derived(
[...devTeam].sort((a, b) => {
if (a.active && !b.active) return -1;
if (!a.active && b.active) return 1;

const dateA = new Date(a.start).getTime();
const dateB = new Date(b.start).getTime();
return dateB - dateA;
})
);

function getGroup(member: DevTeam) {
return member.active ? 'Active team' : new Date(member.start).getFullYear().toString();
}

let groupedTeam = $derived(
sortedTeam.reduce<{ group: string; members: DevTeam[]; active: boolean }[]>((acc, member) => {
const group = getGroup(member);
const last = acc[acc.length - 1];
if (last && last.group === group) {
last.members.push(member);
} else {
acc.push({ group, members: [member], active: member.active });
}
return acc;
}, [])
);
</script>

<SeoMetaTags />

<Section from="from-ecsess-black" to="to-ecsess-black" via="via-ecsess-800" direction="to-b">
<div class="relative flex h-full w-full flex-col items-center">
<!-- Hero -->
<div class="my-8">
<span class="page-title"> git log --dev-team </span>
<p class="text-ecsess-300 mt-6 font-mono text-lg">
Want to build the future of ECSESS? <br />
<Link href="https://github.com/mcgill-ecsess/ECSESS" external>
<span
class="text-ecsess-400 decoration-ecsess-500 hover:text-ecsess-300 hover:decoration-ecsess-400 font-semibold underline decoration-2 underline-offset-4 transition-all"
>
Contribute on GitHub →
</span>
</Link>
</p>
</div>

<!-- Git Tree History -->
<div class="relative w-full max-w-6xl px-4">
<div class="relative mx-auto max-w-fit">
<!-- Main vertical timeline connector (centered in 12px node column, aligns with GitNode circles) -->

<!-- Cohort Branches -->
<div class="space-y-16">
{#each groupedTeam as { group, members, active }}
<div class="relative">
<ContribTimeline year={group} {members} {active} />
</div>
{/each}
</div>
</div>
</div>
</div>
</Section>