= {
home: { topId: 'notebook-top' },
+ team: { topId: 'team-top' },
+ 'contest-2025': { topId: 'contest-2025-top' },
+ 'contest-2024': { topId: 'contest-2024-top' },
careers: { topId: 'careers-top' },
}
diff --git a/src/components/ui/Button.module.css b/src/components/ui/Button.module.css
index 21fd993..6c2f9e7 100644
--- a/src/components/ui/Button.module.css
+++ b/src/components/ui/Button.module.css
@@ -27,13 +27,6 @@
transform: rotate(-0.6deg);
}
-.primary:hover {
- background: var(--accent-surface-hover);
- color: var(--accent-text);
- transform: translate(2px, 2px) rotate(-0.6deg);
- box-shadow: 1px 1px 0 rgba(59, 111, 175, 0.45);
-}
-
.ghost {
color: var(--navy);
background: var(--control-bg);
@@ -42,13 +35,32 @@
transform: rotate(0.5deg);
}
-.ghost:hover {
- background: var(--surface-hover);
- color: var(--navy);
- transform: translate(2px, 2px) rotate(0.5deg);
+.primary:active {
+ transform: translate(2px, 2px) rotate(-0.6deg) scale(0.97);
+ box-shadow: 1px 1px 0 rgba(59, 111, 175, 0.45);
+}
+
+.ghost:active {
+ transform: translate(2px, 2px) rotate(0.5deg) scale(0.97);
box-shadow: 1px 1px 0 rgba(31, 44, 92, 0.25);
}
+@media (hover: hover) and (pointer: fine) {
+ .primary:hover {
+ background: var(--accent-surface-hover);
+ color: var(--accent-text);
+ transform: translate(2px, 2px) rotate(-0.6deg);
+ box-shadow: 1px 1px 0 rgba(59, 111, 175, 0.45);
+ }
+
+ .ghost:hover {
+ background: var(--surface-hover);
+ color: var(--navy);
+ transform: translate(2px, 2px) rotate(0.5deg);
+ box-shadow: 1px 1px 0 rgba(31, 44, 92, 0.25);
+ }
+}
+
@media (max-width: 768px) {
.btn {
width: 100%;
diff --git a/src/components/ui/Hero.module.css b/src/components/ui/Hero.module.css
index 284f1b2..9989b94 100644
--- a/src/components/ui/Hero.module.css
+++ b/src/components/ui/Hero.module.css
@@ -1,5 +1,5 @@
.hero {
- padding: 72px 0 56px;
+ padding: 60px 0 46px;
position: relative;
text-align: center;
}
@@ -7,10 +7,10 @@
.hero h1 {
font-family: var(--font-hand);
font-weight: 700;
- font-size: clamp(2.6rem, 5.6vw, 4.2rem);
+ font-size: clamp(2.6rem, 5.2vw, 4rem);
line-height: 1.12;
color: var(--navy);
- max-width: 18ch;
+ max-width: 21ch;
margin: 10px auto 22px;
text-wrap: balance;
}
@@ -52,6 +52,26 @@
@media (max-width: 768px) {
.hero {
- padding: 48px 0 44px;
+ padding: 40px 0 36px;
+ }
+}
+
+@media (min-width: 769px) {
+ .hero h1 {
+ max-width: 24ch;
+ }
+}
+
+@media (max-width: 480px) {
+ .hero h1 {
+ font-size: clamp(2.15rem, 10.5vw, 2.6rem);
+ }
+
+ .status {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ row-gap: 8px;
}
}
diff --git a/src/components/ui/Hero.tsx b/src/components/ui/Hero.tsx
index 9d579b2..6b9c50d 100644
--- a/src/components/ui/Hero.tsx
+++ b/src/components/ui/Hero.tsx
@@ -22,7 +22,7 @@ type HeroStatusProps = {
export function HeroStatus({ label, value }: HeroStatusProps) {
return (
-
+
{label}
→
diff --git a/src/features/home/PhotoStrip.module.css b/src/components/ui/PhotoStrip.module.css
similarity index 86%
rename from src/features/home/PhotoStrip.module.css
rename to src/components/ui/PhotoStrip.module.css
index b879786..417e575 100644
--- a/src/features/home/PhotoStrip.module.css
+++ b/src/components/ui/PhotoStrip.module.css
@@ -3,8 +3,15 @@
align-items: flex-start;
gap: 26px;
overflow-x: auto;
- padding: 26px 6px 30px;
+ padding: 30px 8px 36px;
+ scroll-padding-inline: 8px;
scroll-snap-type: x proximity;
+ scrollbar-color: var(--blue) transparent;
+}
+
+.strip:focus-visible {
+ outline: 3px solid var(--blue-deep);
+ outline-offset: 4px;
}
.photo {
@@ -103,3 +110,11 @@
.photo:nth-child(8n + 8) img {
width: 270px;
}
+
+@media (max-width: 600px) {
+ .strip {
+ gap: 20px;
+ margin-inline: -18px;
+ padding-inline: 24px;
+ }
+}
diff --git a/src/features/home/PhotoStrip.tsx b/src/components/ui/PhotoStrip.tsx
similarity index 58%
rename from src/features/home/PhotoStrip.tsx
rename to src/components/ui/PhotoStrip.tsx
index fc0d0f3..6884eb6 100644
--- a/src/features/home/PhotoStrip.tsx
+++ b/src/components/ui/PhotoStrip.tsx
@@ -1,14 +1,15 @@
import type { Photo } from '~/data/photos'
import styles from './PhotoStrip.module.css'
-type PhotoStripProps = {
- photos: readonly Photo[]
-}
-
-export function PhotoStrip({ photos }: PhotoStripProps) {
+export function PhotoStrip({ photos }: { photos: readonly Photo[] }) {
return (
-
- {photos.map((photo) => (
+
+ {photos.map((photo, index) => (
{photo.caption}
diff --git a/src/features/home/TileGrid.module.css b/src/components/ui/TileGrid.module.css
similarity index 51%
rename from src/features/home/TileGrid.module.css
rename to src/components/ui/TileGrid.module.css
index 18e4e7f..61b2817 100644
--- a/src/features/home/TileGrid.module.css
+++ b/src/components/ui/TileGrid.module.css
@@ -1,29 +1,29 @@
.tiles {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 18px;
- margin-top: 24px;
+ max-width: 900px;
+ margin: 30px auto 0;
}
.tile {
composes: pinned lifted from '../../styles/surfaces.module.css';
- flex: 0 1 172px;
- min-width: min(148px, 100%);
+ min-width: 0;
+ min-height: 148px;
display: flex;
flex-direction: column;
align-items: center;
+ justify-content: center;
gap: 10px;
- padding: 26px 14px 18px;
+ padding: 28px 14px 18px;
text-decoration: none;
transition:
box-shadow var(--motion-fast) var(--ease-out),
transform var(--motion-fast) var(--ease-out);
}
-.tile:hover,
-.tile:focus-visible {
- transform: translateY(-2px);
+.tile:active {
+ transform: scale(0.97);
}
.tile img {
@@ -31,6 +31,10 @@
width: auto;
max-width: 86%;
object-fit: contain;
+ box-sizing: content-box;
+ background: #ffffff;
+ border-radius: 8px;
+ padding: 8px 10px;
}
.tile span:last-child {
@@ -41,18 +45,23 @@
line-height: 1.4;
}
-.wide {
- flex: 0 1 230px;
- min-width: min(180px, 100%);
- padding: 28px 16px 20px;
+@media (hover: hover) and (pointer: fine) {
+ .tile:hover,
+ .tile:focus-visible {
+ transform: translateY(-2px);
+ }
}
-.wide img {
- height: 58px;
-}
+@media (max-width: 520px) {
+ .tiles {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 12px;
+ }
-.wide span:last-child {
- font-size: 0.8rem;
+ .tile {
+ min-height: 136px;
+ padding-inline: 10px;
+ }
}
@media (prefers-reduced-motion: reduce) {
diff --git a/src/features/home/TileGrid.tsx b/src/components/ui/TileGrid.tsx
similarity index 53%
rename from src/features/home/TileGrid.tsx
rename to src/components/ui/TileGrid.tsx
index b7241cd..80bb7be 100644
--- a/src/features/home/TileGrid.tsx
+++ b/src/components/ui/TileGrid.tsx
@@ -1,18 +1,13 @@
-import { Pin } from '~/components/ui/Pin'
+import { Pin } from './Pin'
import type { Tile } from '~/data/sponsors'
import styles from './TileGrid.module.css'
-type TileGridProps = {
- tiles: readonly Tile[]
- wide?: boolean
-}
-
-export function TileGrid({ tiles, wide = false }: TileGridProps) {
+export function TileGrid({ tiles }: { tiles: readonly Tile[] }) {
return (
{tiles.map((tile) => (
-
+
{tile.name}
))}
diff --git a/src/data/contests.ts b/src/data/contests.ts
new file mode 100644
index 0000000..1e06925
--- /dev/null
+++ b/src/data/contests.ts
@@ -0,0 +1,37 @@
+import { fall25Photos, macso24Photos, type Photo } from './photos'
+import { scoreboards, type Scoreboard } from './results'
+import { sponsorsByYear, type Tile } from './sponsors'
+
+export type ContestYear = 2025 | 2024
+
+export type ContestArchive = {
+ readonly year: ContestYear
+ readonly page: 'contest-2025' | 'contest-2024'
+ readonly title: string
+ readonly summary: string
+ readonly photos: readonly Photo[]
+ readonly scoreboards?: readonly Scoreboard[]
+ readonly sponsors: readonly Tile[]
+}
+
+export const contests: Record
= {
+ 2025: {
+ year: 2025,
+ page: 'contest-2025',
+ title: 'MACSO Fall 2025',
+ summary:
+ 'A look back at MACSO Fall 2025, its community, and the organizations that made the contest possible.',
+ photos: fall25Photos,
+ sponsors: sponsorsByYear[2025],
+ },
+ 2024: {
+ year: 2024,
+ page: 'contest-2024',
+ title: 'MACSO 2024',
+ summary:
+ 'Photos and final standings from MACSO 2024, with thanks to the organizations that supported the contest.',
+ photos: macso24Photos,
+ scoreboards,
+ sponsors: sponsorsByYear[2024],
+ },
+}
diff --git a/src/data/faq.tsx b/src/data/faq.tsx
deleted file mode 100644
index eee6943..0000000
--- a/src/data/faq.tsx
+++ /dev/null
@@ -1,72 +0,0 @@
-import type { ReactNode } from 'react'
-import { TextLink } from '~/components/ui/TextLink'
-import { site } from './site'
-
-export type FaqItem = {
- readonly question: string
- readonly answer: ReactNode
-}
-
-export const faqItems: readonly FaqItem[] = [
- {
- question: 'Who can participate in the competition?',
- answer: (
- <>
- Anyone is welcome to participate this year. There is no qualification
- requirement. Whether you're brand new to competitive programming or have
- competed before, we'd love to have you join us!
- >
- ),
- },
- {
- question: 'How does the competition work?',
- answer: (
- <>
- MACSO 2026 is a single in-person contest: 6 coding problems, a 2-hour
- contest, competed individually. There's no online qualification stage
- this year, just register and show up!
- >
- ),
- },
- {
- question: 'What is the scoring system like?',
- answer: (
- <>
- The contest uses the ICPC system: each problem is either solved or not,
- and the time penalty serves as tie breaker.
- >
- ),
- },
- {
- question: 'When and where is the competition?',
- answer: (
- <>
- MACSO 2026 will take place in late September 2026, from 1:00 PM to 4:00
- PM at 101 Rogers Street, Cambridge, MA 02142. Please arrive by 12:45 PM
- so we can get everyone checked in before the contest begins. Prizes will
- be awarded to our top competitors, just like last year!
- >
- ),
- },
- {
- question: 'How can I register for the competition?',
- answer: (
- <>
- You can register by clicking the Registration Form button at the top of
- the page, or directly through the{' '}
- registration form {' '}
- where you can fill in your details.
- >
- ),
- },
- {
- question: 'Can I participate if I’m not in Massachusetts?',
- answer: (
- <>
- Yes! Anyone is welcome and there is no qualification requirement. This
- year’s contest is in person, so you’ll just need to make it to Cambridge
- in late September.
- >
- ),
- },
-]
diff --git a/src/data/photos.ts b/src/data/photos.ts
index e503715..2eb9179 100644
--- a/src/data/photos.ts
+++ b/src/data/photos.ts
@@ -30,4 +30,6 @@ export const macso24Photos: readonly Photo[] = MACSO_24_INDICES.map((n) => ({
src: `https://static.macso.org/output/low_res/_${n}.webp`,
alt: `MACSO 2024 contest photo ${n}`,
caption: `macso '24 · #${n}`,
+ width: 800,
+ height: 600,
}))
diff --git a/src/data/site.ts b/src/data/site.ts
index 309842b..eb23771 100644
--- a/src/data/site.ts
+++ b/src/data/site.ts
@@ -7,13 +7,6 @@ export const site = {
discord: 'https://discord.gg/cX7tmKwJ8f',
instagram:
'https://www.instagram.com/bhs.computerscience?utm_source=ig_web_button_share_sheet&igshid=ZDNlZDc0MzIxNw==',
- instagramHandle: '@bhs.computerscience',
- venue: {
- name: 'The Foundry',
- address: '101 Rogers Street, Cambridge, MA 02142',
- lat: 42.36662,
- lng: -71.08276,
- },
credits: {
lucasChen: 'https://lucasrchen.com',
mishaZhernevskii:
@@ -23,14 +16,21 @@ export const site = {
export type NavLink = {
readonly label: string
- readonly hash: string
+ readonly href: string
+ readonly page: 'home' | 'team' | 'contest-2025' | 'contest-2024'
}
export const navLinks: readonly NavLink[] = [
- { label: 'Highlights', hash: '#highlights' },
- { label: 'Results', hash: '#results' },
- { label: 'FAQ', hash: '#faq' },
- { label: 'Team', hash: '#team' },
- { label: 'Sponsors', hash: '#sponsors' },
- { label: 'Location', hash: '#location' },
+ { label: 'Home', href: '/', page: 'home' },
+ { label: 'Team', href: '/team/', page: 'team' },
+ {
+ label: '2025 Contest',
+ href: '/contests/2025/',
+ page: 'contest-2025',
+ },
+ {
+ label: '2024 Contest',
+ href: '/contests/2024/',
+ page: 'contest-2024',
+ },
]
diff --git a/src/data/sponsors.ts b/src/data/sponsors.ts
index 4c6ce5d..d3c5f11 100644
--- a/src/data/sponsors.ts
+++ b/src/data/sponsors.ts
@@ -4,68 +4,94 @@ export type Tile = {
readonly logo: string
}
-export const sponsors: readonly Tile[] = [
- {
+const sponsorDirectory = {
+ janeStreet: {
name: 'Jane Street',
href: 'https://www.janestreet.com/',
logo: '/images/logos/jane-street.png',
},
- {
+ google: {
name: 'Google',
href: 'https://edu.google.com/',
logo: '/images/logos/google.webp',
},
- {
+ wolfram: {
name: 'Wolfram',
href: 'https://www.wolfram.com/',
logo: '/images/logos/wolfram.webp',
},
- {
+ jetbrains: {
name: 'JetBrains',
href: 'https://www.jetbrains.com/',
logo: '/images/logos/jetbrains_logo.webp',
},
- {
+ brooklineTeenCenter: {
name: 'Brookline Teen Center',
href: 'https://brooklineteencenter.org/',
logo: '/images/logos/btc.webp',
},
- {
+ rsm: {
name: 'Russian School of Mathematics',
href: 'https://www.mathschool.com/',
logo: '/images/logos/rsm.webp',
},
- {
+ ktbyte: {
name: 'KTBYTE',
href: 'https://www.ktbyte.com/',
logo: '/images/logos/ktbyte.webp',
},
- {
+ dataIntensity: {
name: 'Data Intensity',
href: 'https://www.dataintensity.com/',
logo: '/images/logos/dataintensity.webp',
},
- {
+ hackClub: {
name: 'Hack Club',
href: 'https://hackclub.com/',
- logo: 'https://assets.hackclub.com/icon-rounded.svg',
+ logo: '/images/logos/hack-club.svg',
},
- {
+ aops: {
name: 'Art of Problem Solving',
href: 'https://artofproblemsolving.com/',
logo: '/images/logos/AoPS.webp',
},
- {
+ algoverse: {
name: 'Algoverse',
href: 'https://algoverseairesearch.org/',
logo: '/images/logos/algoverse_logo.webp',
},
- {
+ brooklineHigh: {
name: 'Brookline High School',
href: 'https://bhs.brookline.k12.ma.us/',
logo: '/images/logos/bhs1.webp',
},
-]
+} as const satisfies Record
+
+export const sponsorsByYear = {
+ 2025: [
+ sponsorDirectory.janeStreet,
+ sponsorDirectory.google,
+ sponsorDirectory.wolfram,
+ sponsorDirectory.jetbrains,
+ sponsorDirectory.brooklineTeenCenter,
+ sponsorDirectory.dataIntensity,
+ sponsorDirectory.hackClub,
+ sponsorDirectory.aops,
+ sponsorDirectory.brooklineHigh,
+ ],
+ 2024: [
+ sponsorDirectory.wolfram,
+ sponsorDirectory.jetbrains,
+ sponsorDirectory.brooklineTeenCenter,
+ sponsorDirectory.rsm,
+ sponsorDirectory.ktbyte,
+ sponsorDirectory.dataIntensity,
+ sponsorDirectory.hackClub,
+ sponsorDirectory.aops,
+ sponsorDirectory.algoverse,
+ sponsorDirectory.brooklineHigh,
+ ],
+} as const
export const partners: readonly Tile[] = [
{
diff --git a/src/data/team.ts b/src/data/team.ts
index 35dacb5..98a39fd 100644
--- a/src/data/team.ts
+++ b/src/data/team.ts
@@ -9,18 +9,18 @@ export type Person = {
export const staff: readonly Person[] = [
{
name: 'Sergey Zatsepin',
- role: 'director, problem author',
+ role: 'Director · Problem author',
photo: '/images/team/sergey.webp',
},
{
name: 'Ethan Chen',
- role: 'head of fundraising',
+ role: 'Head of fundraising',
photo: '/images/team/ethan.webp',
href: 'https://www.linkedin.com/in/ethan-chen-8a33a8200/',
},
{
name: 'Dana',
- role: 'problem writer assistant',
+ role: 'Assistant problem writer',
photo: '/images/team/dana.jpg',
},
]
@@ -28,19 +28,19 @@ export const staff: readonly Person[] = [
export const founders: readonly Person[] = [
{
name: 'Mikhail Zhernevskii',
- role: 'cofounder, ex-director, ex-problem author',
+ role: 'Cofounder · Former director and problem author',
photo: '/images/team/misha.webp',
href: 'https://www.linkedin.com/in/mikhail-zhernevskii-117477282/',
},
{
name: 'Lucas Chen',
- role: 'cofounder, website designer, ex-fund manager',
+ role: 'Cofounder · Website designer and former fund manager',
photo: '/images/team/lucas_chen.webp',
href: 'https://lucasrchen.com/',
},
{
name: 'Tyler Sacharow',
- role: 'cofounder, ex-advertiser, ex-problem author',
+ role: 'Cofounder · Former advertiser and problem author',
photo: '/images/team/tyler.webp',
href: 'https://www.linkedin.com/in/tyler-sacharow-028972318/',
},
@@ -48,7 +48,7 @@ export const founders: readonly Person[] = [
export const judge: Person = {
name: 'Shruti Dhumak',
- role: 'judge, senior engineer at google',
+ role: 'Judge · Senior Customer Engineer at Google',
photo: '/images/team/judge.webp',
- bio: 'Shruti is a Sr. Customer Engineer at Google working with their Cloud practice at the Cambridge office. She has extensive experience in technology and was a Sr. Systems Engineer at Juniper Networks before joining Google. Shruti graduated from University of Massachusetts, Amherst with a Masters in Computer Engineering in 2016. She has been actively contributing to students in STEM over more than 6 years.',
+ bio: 'Shruti is a Senior Customer Engineer in Google Cloud’s Cambridge office. She previously worked as a Senior Systems Engineer at Juniper Networks, earned a master’s degree in computer engineering from the University of Massachusetts Amherst, and has supported students in STEM for more than six years.',
}
diff --git a/src/entries/contest-2024.tsx b/src/entries/contest-2024.tsx
new file mode 100644
index 0000000..b521a9a
--- /dev/null
+++ b/src/entries/contest-2024.tsx
@@ -0,0 +1,6 @@
+import { ContestPage } from '~/pages/ContestPage'
+import '~/styles/tokens.css'
+import '~/styles/base.css'
+import { renderPage } from './renderPage'
+
+renderPage( )
diff --git a/src/entries/contest-2025.tsx b/src/entries/contest-2025.tsx
new file mode 100644
index 0000000..808bc0f
--- /dev/null
+++ b/src/entries/contest-2025.tsx
@@ -0,0 +1,6 @@
+import { ContestPage } from '~/pages/ContestPage'
+import '~/styles/tokens.css'
+import '~/styles/base.css'
+import { renderPage } from './renderPage'
+
+renderPage( )
diff --git a/src/entries/team.tsx b/src/entries/team.tsx
new file mode 100644
index 0000000..dfc3e5b
--- /dev/null
+++ b/src/entries/team.tsx
@@ -0,0 +1,6 @@
+import { TeamPage } from '~/pages/TeamPage'
+import '~/styles/tokens.css'
+import '~/styles/base.css'
+import { renderPage } from './renderPage'
+
+renderPage( )
diff --git a/src/features/careers/PositionsSection.module.css b/src/features/careers/PositionsSection.module.css
index 2b9e98c..94cb3c6 100644
--- a/src/features/careers/PositionsSection.module.css
+++ b/src/features/careers/PositionsSection.module.css
@@ -17,10 +17,6 @@
border-color var(--motion-fast) var(--ease-out);
}
-.card:hover {
- transform: rotate(0deg) !important;
-}
-
.roles .card:nth-child(6n + 1) {
transform: rotate(-0.4deg);
}
@@ -63,9 +59,15 @@
box-shadow: none;
}
-.open:hover {
- border-color: var(--blue-deep);
- box-shadow: none;
+@media (hover: hover) and (pointer: fine) {
+ .card:hover {
+ transform: rotate(0deg) !important;
+ }
+
+ .open:hover {
+ border-color: var(--blue-deep);
+ box-shadow: none;
+ }
}
@media (prefers-reduced-motion: reduce) {
diff --git a/src/features/contest/ContestHero.module.css b/src/features/contest/ContestHero.module.css
new file mode 100644
index 0000000..00418dc
--- /dev/null
+++ b/src/features/contest/ContestHero.module.css
@@ -0,0 +1,53 @@
+.eyebrow {
+ color: var(--blue-deep);
+ font-size: 0.78rem;
+ font-weight: 700;
+ letter-spacing: 0.13em;
+ text-transform: uppercase;
+}
+
+.summary {
+ max-width: 58ch;
+ margin: -2px auto 28px;
+ color: var(--ink-soft);
+ font-size: 1.05rem;
+ text-wrap: balance;
+}
+
+.sectionNav {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ gap: 10px;
+}
+
+.sectionNav a {
+ color: var(--navy);
+ background: var(--surface);
+ border: 1.5px solid var(--rule-strong);
+ border-radius: 999px;
+ padding: 7px 16px;
+ font-size: 0.86rem;
+ font-weight: 700;
+ text-decoration: none;
+ transition:
+ background-color var(--motion-fast) var(--ease-out),
+ transform var(--motion-fast) var(--ease-out);
+}
+
+.sectionNav a:active {
+ transform: scale(0.97);
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .sectionNav a:hover {
+ background: var(--surface-hover);
+ transform: translateY(-1px);
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .sectionNav a {
+ transition: none;
+ }
+}
diff --git a/src/features/contest/ContestHero.tsx b/src/features/contest/ContestHero.tsx
new file mode 100644
index 0000000..0da6b26
--- /dev/null
+++ b/src/features/contest/ContestHero.tsx
@@ -0,0 +1,26 @@
+import { Hero, HeroTitle, Highlight } from '~/components/ui/Hero'
+import type { ContestArchive } from '~/data/contests'
+import animations from '~/styles/animations.module.css'
+import styles from './ContestHero.module.css'
+
+export function ContestHero({ contest }: { contest: ContestArchive }) {
+ return (
+
+ Contest archive
+
+ MACSO {contest.year}
+
+
+ {contest.summary}
+
+
+ Photos
+ {contest.scoreboards && Final standings }
+ Sponsors
+
+
+ )
+}
diff --git a/src/features/home/ResultsSection.module.css b/src/features/contest/StandingsSection.module.css
similarity index 90%
rename from src/features/home/ResultsSection.module.css
rename to src/features/contest/StandingsSection.module.css
index 28c0fc7..60ab2e2 100644
--- a/src/features/home/ResultsSection.module.css
+++ b/src/features/contest/StandingsSection.module.css
@@ -4,7 +4,7 @@
justify-content: center;
gap: 30px;
align-items: flex-start;
- margin-top: 16px;
+ margin-top: 24px;
}
.scorecard {
@@ -55,7 +55,8 @@
border-bottom: 1px solid var(--rule-strong);
}
-.scorecard th:first-child {
+.scorecard th:first-child,
+.scorecard td:first-child {
padding-left: 22px;
}
@@ -65,7 +66,6 @@
}
.scorecard td:first-child {
- padding-left: 22px;
width: 70px;
}
@@ -74,16 +74,16 @@
padding-bottom: 14px;
}
-.name {
- font-weight: 700;
+.name,
+.award {
color: var(--navy);
+ font-weight: 700;
}
-.award {
+.award,
+.mention {
font-family: var(--font-hand);
- font-weight: 700;
font-size: 1.1rem;
- color: var(--navy);
}
.awardFirst {
@@ -98,8 +98,6 @@
}
.mention {
- font-family: var(--font-hand);
- font-weight: 400;
- font-size: 1rem;
color: var(--ink-muted);
+ font-size: 1rem;
}
diff --git a/src/features/home/ResultsSection.tsx b/src/features/contest/StandingsSection.tsx
similarity index 85%
rename from src/features/home/ResultsSection.tsx
rename to src/features/contest/StandingsSection.tsx
index b94bfdc..0518b72 100644
--- a/src/features/home/ResultsSection.tsx
+++ b/src/features/contest/StandingsSection.tsx
@@ -1,10 +1,14 @@
import { Section } from '~/components/ui/Section'
-import { scoreboards } from '~/data/results'
-import styles from './ResultsSection.module.css'
+import type { Scoreboard } from '~/data/results'
+import styles from './StandingsSection.module.css'
-export function ResultsSection() {
+export function StandingsSection({
+ scoreboards,
+}: {
+ scoreboards: readonly Scoreboard[]
+}) {
return (
-
+
{scoreboards.map((board) => (
@@ -17,7 +21,7 @@ export function ResultsSection() {
Award
- Full Name
+ Full name
School
diff --git a/src/features/home/ContextSection.module.css b/src/features/home/ContextSection.module.css
new file mode 100644
index 0000000..e95f476
--- /dev/null
+++ b/src/features/home/ContextSection.module.css
@@ -0,0 +1,93 @@
+.context {
+ display: grid;
+ grid-template-columns: minmax(0, 1.2fr) minmax(300px, 0.8fr);
+ gap: clamp(28px, 6vw, 68px);
+ align-items: center;
+ padding: 34px 0 40px;
+ border-top: 2px dashed var(--rule);
+}
+
+.eyebrow,
+.noteLabel {
+ color: var(--blue-deep);
+ font-size: 0.76rem;
+ font-weight: 700;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+}
+
+.about h2,
+.eligibility h2 {
+ color: var(--navy);
+ font-family: var(--font-hand);
+ line-height: 1.2;
+ text-wrap: balance;
+}
+
+.about h2 {
+ font-size: clamp(2rem, 4vw, 2.8rem);
+ margin: 7px 0 13px;
+}
+
+.about > p:last-child {
+ max-width: 62ch;
+ color: var(--ink-soft);
+ font-size: 1rem;
+}
+
+.eligibility {
+ composes: card from '../../styles/surfaces.module.css';
+ position: relative;
+ padding: 26px 28px 24px;
+ background: var(--note-bg);
+ transform: rotate(0.5deg);
+}
+
+.eligibility::before {
+ content: '';
+ position: absolute;
+ top: -11px;
+ left: 50%;
+ width: 76px;
+ height: 22px;
+ background: color-mix(in srgb, var(--blue) 62%, transparent);
+ transform: translateX(-50%) rotate(-2deg);
+}
+
+.eligibility h2 {
+ font-size: 1.55rem;
+ margin: 5px 0 12px;
+}
+
+.eligibility ul {
+ display: grid;
+ gap: 8px;
+ list-style: none;
+}
+
+.eligibility li {
+ position: relative;
+ padding-left: 22px;
+ color: var(--ink-soft);
+ font-size: 0.92rem;
+}
+
+.eligibility li::before {
+ content: '✓';
+ position: absolute;
+ left: 0;
+ color: var(--green);
+ font-weight: 700;
+}
+
+@media (max-width: 720px) {
+ .context {
+ grid-template-columns: 1fr;
+ gap: 30px;
+ padding: 34px 0 46px;
+ }
+
+ .eligibility {
+ transform: none;
+ }
+}
diff --git a/src/features/home/ContextSection.tsx b/src/features/home/ContextSection.tsx
new file mode 100644
index 0000000..3d17dad
--- /dev/null
+++ b/src/features/home/ContextSection.tsx
@@ -0,0 +1,33 @@
+import styles from './ContextSection.module.css'
+
+const eligibility = [
+ 'Everyone is welcome, from first-time competitors to experienced coders.',
+ 'No qualifier or Massachusetts residency is required.',
+ 'The 2026 contest is in person in Cambridge.',
+] as const
+
+export function ContextSection() {
+ return (
+
+
+
What is MACSO?
+
Think, code, and meet your people.
+
+ The Massachusetts Computer Science Olympiad is an in-person
+ programming competition created for high-school students by Brookline
+ Computer Science Initiative. It brings aspiring programmers together
+ for hands-on contest experience beyond the classroom.
+
+
+
+ 2026 eligibility
+ Who can participate?
+
+ {eligibility.map((item) => (
+ {item}
+ ))}
+
+
+
+ )
+}
diff --git a/src/features/home/FaqSection.module.css b/src/features/home/FaqSection.module.css
deleted file mode 100644
index 2f729ea..0000000
--- a/src/features/home/FaqSection.module.css
+++ /dev/null
@@ -1,77 +0,0 @@
-.faq {
- max-width: 820px;
- margin-inline: auto;
-}
-
-.contact {
- margin-bottom: 18px;
-}
-
-.faq details {
- border-bottom: 1px solid var(--rule);
-}
-
-.faq details:last-child {
- border-bottom: none;
-}
-
-.faq summary {
- cursor: pointer;
- display: flex;
- gap: 12px;
- align-items: baseline;
- padding: 16px 2px;
- font-weight: 700;
- font-size: 1rem;
- color: var(--navy);
- list-style: none;
- transition: color var(--motion-fast) var(--ease-out);
-}
-
-.faq summary::-webkit-details-marker {
- display: none;
-}
-
-.faq summary:hover,
-.faq details[open] summary {
- color: var(--blue-deep);
-}
-
-.q {
- font-family: var(--font-hand);
- font-size: 1.2rem;
- color: var(--blue-deep);
- flex-shrink: 0;
-}
-
-.faq details > p {
- padding: 0 2px 18px 34px;
- font-size: 0.95rem;
-}
-
-.faq details[open] > p {
- animation: detailArrive var(--motion-medium) var(--ease-out) both;
-}
-
-.a {
- font-family: var(--font-hand);
- color: var(--blue-deep);
- padding-right: 8px;
-}
-
-@keyframes detailArrive {
- from {
- opacity: 0.82;
- transform: translateY(-4px);
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- .faq summary {
- transition: none;
- }
-
- .faq details[open] > p {
- animation: none;
- }
-}
diff --git a/src/features/home/FaqSection.tsx b/src/features/home/FaqSection.tsx
deleted file mode 100644
index de91a55..0000000
--- a/src/features/home/FaqSection.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-import { Section } from '~/components/ui/Section'
-import { TextLink } from '~/components/ui/TextLink'
-import { faqItems } from '~/data/faq'
-import { site } from '~/data/site'
-import typography from '~/styles/typography.module.css'
-import styles from './FaqSection.module.css'
-
-export function FaqSection() {
- return (
-
-
- Contact {site.email} {' '}
- for any questions.
-
- {faqItems.map((item) => (
-
-
-
- Q.
-
- {item.question}
-
-
-
- A.
-
- {item.answer}
-
-
- ))}
-
- )
-}
diff --git a/src/features/home/HighlightsSection.tsx b/src/features/home/HighlightsSection.tsx
deleted file mode 100644
index 89625b7..0000000
--- a/src/features/home/HighlightsSection.tsx
+++ /dev/null
@@ -1,14 +0,0 @@
-import { Section } from '~/components/ui/Section'
-import { fall25Photos, macso24Photos } from '~/data/photos'
-import typography from '~/styles/typography.module.css'
-import { PhotoStrip } from './PhotoStrip'
-
-export function HighlightsSection() {
- return (
-
- )
-}
diff --git a/src/features/home/HomeHero.module.css b/src/features/home/HomeHero.module.css
deleted file mode 100644
index 6fab759..0000000
--- a/src/features/home/HomeHero.module.css
+++ /dev/null
@@ -1,29 +0,0 @@
-.ps {
- margin: 30px 0 0;
- font-family: var(--font-hand);
- font-size: 1.3rem;
- color: var(--navy);
-}
-
-.ps a {
- color: var(--navy);
- font-weight: 700;
- text-decoration: underline;
- text-decoration-thickness: 2.5px;
- text-decoration-color: var(--blue);
- text-underline-offset: 5px;
- transition:
- color var(--motion-fast) var(--ease-out),
- text-underline-offset var(--motion-fast) var(--ease-out);
-}
-
-.ps a:hover {
- color: var(--blue-deep);
- text-underline-offset: 7px;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .ps a {
- transition: none;
- }
-}
diff --git a/src/features/home/HomeHero.tsx b/src/features/home/HomeHero.tsx
index a877273..509c93a 100644
--- a/src/features/home/HomeHero.tsx
+++ b/src/features/home/HomeHero.tsx
@@ -7,9 +7,7 @@ import {
Highlight,
} from '~/components/ui/Hero'
import { ChatIcon, PencilIcon } from '~/components/ui/icons'
-import animations from '~/styles/animations.module.css'
import { site } from '~/data/site'
-import styles from './HomeHero.module.css'
export function HomeHero() {
return (
@@ -28,10 +26,6 @@ export function HomeHero() {
Discord Server
-
- p.s. we're hiring!{' '}
- join the MACSO leadership team →
-
)
}
diff --git a/src/features/home/IntroSection.module.css b/src/features/home/IntroSection.module.css
deleted file mode 100644
index 04671dc..0000000
--- a/src/features/home/IntroSection.module.css
+++ /dev/null
@@ -1,27 +0,0 @@
-.intro {
- padding: 24px 0 40px;
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 34px;
- border-top: 2px dashed var(--rule);
-}
-
-.polaroid {
- composes: pinned from '../../styles/surfaces.module.css';
- flex: 0 1 320px;
- min-width: min(260px, 100%);
- padding: 20px;
- transform: rotate(var(--tilt));
-}
-
-.polaroid img {
- display: block;
- width: 100%;
- height: auto;
-}
-
-.intro > p {
- flex: 1 1 380px;
- font-size: 1rem;
-}
diff --git a/src/features/home/IntroSection.tsx b/src/features/home/IntroSection.tsx
deleted file mode 100644
index e60b7b4..0000000
--- a/src/features/home/IntroSection.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import { Pin } from '~/components/ui/Pin'
-import styles from './IntroSection.module.css'
-
-export function IntroSection() {
- return (
-
-
-
-
-
-
- The Massachusetts Computer Science Olympiad is a high-school programming
- competition hosted by the leaders of Brookline Computer Science
- Initiative. Our goal is to provide Massachusetts CS students with
- hands-on experience and opportunities to build a network of fellow
- coders through an in-person coding competition.
-
-
- )
-}
diff --git a/src/features/home/LocationSection.module.css b/src/features/home/LocationSection.module.css
deleted file mode 100644
index db86a2e..0000000
--- a/src/features/home/LocationSection.module.css
+++ /dev/null
@@ -1,3 +0,0 @@
-.location {
- padding-bottom: 72px;
-}
diff --git a/src/features/home/LocationSection.tsx b/src/features/home/LocationSection.tsx
deleted file mode 100644
index 8512b3f..0000000
--- a/src/features/home/LocationSection.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { Section } from '~/components/ui/Section'
-import { site } from '~/data/site'
-import typography from '~/styles/typography.module.css'
-import { VenueMap } from './VenueMap'
-import styles from './LocationSection.module.css'
-
-export function LocationSection() {
- return (
-
-
- {site.venue.name}, {site.venue.address}
-
-
-
- )
-}
diff --git a/src/features/home/ResourcesSection.tsx b/src/features/home/ResourcesSection.tsx
deleted file mode 100644
index 936ba5d..0000000
--- a/src/features/home/ResourcesSection.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import { Section } from '~/components/ui/Section'
-import { TextLink } from '~/components/ui/TextLink'
-import typography from '~/styles/typography.module.css'
-
-export function ResourcesSection() {
- return (
-
-
- To learn the algorithms and data structures for MACSO, you can use
- websites like{' '}
- USACO Guide or{' '}
-
- Algorithms for Competitive Programming
-
- .
-
-
- )
-}
diff --git a/src/features/home/SponsorsSection.tsx b/src/features/home/SponsorsSection.tsx
deleted file mode 100644
index 5ec18c9..0000000
--- a/src/features/home/SponsorsSection.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import { Section } from '~/components/ui/Section'
-import { partners, sponsors } from '~/data/sponsors'
-import typography from '~/styles/typography.module.css'
-import { TileGrid } from './TileGrid'
-
-export function SponsorsSection() {
- return (
-
- )
-}
-
-export function PartnersSection() {
- return (
-
-
- We recommend trying their competitions as well!
-
-
-
- )
-}
diff --git a/src/features/home/TeamSection.module.css b/src/features/home/TeamSection.module.css
deleted file mode 100644
index b8d3b5f..0000000
--- a/src/features/home/TeamSection.module.css
+++ /dev/null
@@ -1,144 +0,0 @@
-.people {
- display: flex;
- flex-wrap: wrap;
- justify-content: center;
- gap: 24px;
- margin-top: 28px;
-}
-
-.person {
- composes: pinned from '../../styles/surfaces.module.css';
- flex: 0 1 260px;
- min-width: min(240px, 100%);
- padding: 26px 20px 20px;
- text-align: center;
- transition:
- transform var(--motion-fast) var(--ease-out),
- box-shadow var(--motion-fast) var(--ease-out);
-}
-
-.person:hover {
- transform: rotate(0deg) !important;
-}
-
-.people .person:nth-child(3n + 1) {
- transform: rotate(-0.5deg);
-}
-.people .person:nth-child(3n + 2) {
- transform: rotate(0.4deg);
-}
-.people .person:nth-child(3n + 3) {
- transform: rotate(-0.3deg);
-}
-
-.person a {
- display: inline-block;
-}
-
-.avatar {
- width: 116px;
- height: 116px;
- object-fit: cover;
- border: 2.5px solid var(--navy);
- display: block;
- margin: 0 auto;
-}
-
-.people .person:nth-child(3n + 1) .avatar {
- border-radius: 46% 54% 52% 48% / 55% 48% 52% 45%;
-}
-
-.people .person:nth-child(3n + 2) .avatar {
- border-radius: 52% 48% 46% 54% / 48% 55% 45% 52%;
-}
-
-.people .person:nth-child(3n + 3) .avatar {
- border-radius: 48% 52% 55% 45% / 52% 46% 54% 48%;
-}
-
-.name {
- margin-top: 13px;
- font-weight: 700;
- color: var(--navy);
-}
-
-.role {
- font-family: var(--font-hand);
- font-size: 1.05rem;
- color: var(--ink-muted);
-}
-
-.judge {
- flex: 0 1 460px;
- padding: 26px 24px 22px;
-}
-
-.bio {
- margin-top: 8px;
-}
-
-.bio summary {
- cursor: pointer;
- display: inline-block;
- font-family: var(--font-hand);
- font-size: 1.1rem;
- color: var(--blue-deep);
- list-style: none;
- transition: color var(--motion-fast) var(--ease-out);
-}
-
-.bio summary::-webkit-details-marker {
- display: none;
-}
-
-.bio p {
- margin-top: 12px;
- text-align: left;
- font-size: 0.92rem;
-}
-
-.bio[open] p {
- animation: detailArrive var(--motion-medium) var(--ease-out) both;
-}
-
-@keyframes detailArrive {
- from {
- opacity: 0.82;
- transform: translateY(-4px);
- }
-}
-
-.instagram {
- display: flex;
- align-items: center;
- gap: 8px;
- width: fit-content;
- margin: 10px auto 0;
- font-size: 0.85rem;
- font-weight: 700;
- color: var(--navy);
- text-decoration: none;
- border-bottom: 2px dotted var(--rule-strong);
- padding-bottom: 2px;
- transition: color var(--motion-fast) var(--ease-out);
-}
-
-.instagram:hover {
- color: var(--blue-deep);
-}
-
-.instagram img {
- display: block;
-}
-
-@media (prefers-reduced-motion: reduce) {
- .person,
- .bio summary,
- .instagram {
- transition: none;
- }
-
- .bio[open] p {
- animation: none;
- }
-}
diff --git a/src/features/home/TeamSection.tsx b/src/features/home/TeamSection.tsx
deleted file mode 100644
index f13c016..0000000
--- a/src/features/home/TeamSection.tsx
+++ /dev/null
@@ -1,97 +0,0 @@
-import { Pin } from '~/components/ui/Pin'
-import { Section } from '~/components/ui/Section'
-import { founders, judge, staff, type Person } from '~/data/team'
-import { site } from '~/data/site'
-import typography from '~/styles/typography.module.css'
-import styles from './TeamSection.module.css'
-
-function Avatar({ person }: { person: Person }) {
- const img = (
-
- )
-
- if (!person.href) return img
-
- return (
-
- {img}
-
- )
-}
-
-function PersonCard({ person, wide }: { person: Person; wide?: boolean }) {
- return (
-
-
-
-
{person.name}
-
{person.role}
- {person.bio && (
-
- read her bio ↓
- {person.bio}
-
- )}
-
- )
-}
-
-function PeopleRow({ people }: { people: readonly Person[] }) {
- return (
-
- {people.map((person) => (
-
- ))}
-
- )
-}
-
-export function TeamSection() {
- return (
-
-
- We are a group of students at Brookline High School passionate about
- computer science. Our goal is to give aspiring programmers in
- Massachusetts an experience beyond the school curriculum.
-
-
-
- {site.instagramHandle}
-
-
-
-
- the founders
-
-
- the judge
-
-
- )
-}
diff --git a/src/features/home/VenueMap.module.css b/src/features/home/VenueMap.module.css
deleted file mode 100644
index 34d755d..0000000
--- a/src/features/home/VenueMap.module.css
+++ /dev/null
@@ -1,36 +0,0 @@
-.frame {
- composes: card from '../../styles/surfaces.module.css';
- padding: 10px;
- margin-top: 22px;
-}
-
-.map {
- width: 100%;
- height: 430px;
- background: var(--surface-subtle);
-}
-
-:global(.leaflet-container) {
- font-family: var(--font-body);
-}
-
-:global(:root[data-theme='dark']) .map :global(.leaflet-bar a),
-:global(:root[data-theme='dark']) .map :global(.leaflet-popup-content-wrapper),
-:global(:root[data-theme='dark']) .map :global(.leaflet-popup-tip),
-:global(:root[data-theme='dark']) .map :global(.leaflet-control-attribution) {
- color: var(--ink);
- background: var(--control-bg);
-}
-
-:global(:root[data-theme='dark']) .map :global(.leaflet-bar a) {
- border-bottom-color: var(--rule);
-}
-
-:global(:root[data-theme='dark']) .map :global(.leaflet-bar a:hover) {
- color: var(--ink);
- background: var(--surface-hover);
-}
-
-:global(:root[data-theme='dark']) .map :global(.leaflet-control-attribution a) {
- color: var(--blue-deep);
-}
diff --git a/src/features/home/VenueMap.tsx b/src/features/home/VenueMap.tsx
deleted file mode 100644
index 335b9c9..0000000
--- a/src/features/home/VenueMap.tsx
+++ /dev/null
@@ -1,92 +0,0 @@
-import { useEffect, useRef } from 'react'
-import L from 'leaflet'
-import 'leaflet/dist/leaflet.css'
-import markerIcon from 'leaflet/dist/images/marker-icon.png'
-import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'
-import markerShadow from 'leaflet/dist/images/marker-shadow.png'
-import { site } from '~/data/site'
-import styles from './VenueMap.module.css'
-
-const venueIcon = L.icon({
- iconUrl: markerIcon,
- iconRetinaUrl: markerIcon2x,
- shadowUrl: markerShadow,
- iconSize: [25, 41],
- iconAnchor: [12, 41],
- popupAnchor: [1, -34],
- tooltipAnchor: [16, -28],
- shadowSize: [41, 41],
-})
-
-const ATTRIBUTION =
- '©
CARTO ©
OpenStreetMap contributors'
-
-const TILE_URLS = {
- light: 'https://basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png',
- dark: 'https://basemaps.cartocdn.com/dark_all/{z}/{x}/{y}{r}.png',
-} as const
-
-function getTileUrl() {
- return document.documentElement.dataset.theme === 'dark'
- ? TILE_URLS.dark
- : TILE_URLS.light
-}
-
-export function VenueMap() {
- const containerRef = useRef
(null)
-
- useEffect(() => {
- const container = containerRef.current
- if (!container) return
-
- const { lat, lng, name, address } = site.venue
- const map = L.map(container, {
- attributionControl: false,
- scrollWheelZoom: false,
- }).setView([lat, lng], 14)
-
- let currentTileUrl = getTileUrl()
- const tileLayer = L.tileLayer(currentTileUrl, {
- attribution: '',
- }).addTo(map)
-
- const themeObserver = new MutationObserver(() => {
- const nextTileUrl = getTileUrl()
- if (nextTileUrl === currentTileUrl) return
-
- currentTileUrl = nextTileUrl
- tileLayer.setUrl(currentTileUrl)
- })
-
- themeObserver.observe(document.documentElement, {
- attributes: true,
- attributeFilter: ['data-theme'],
- })
-
- L.control
- .attribution({ position: 'bottomright', prefix: false })
- .addAttribution(ATTRIBUTION)
- .addTo(map)
-
- L.marker([lat, lng], { icon: venueIcon })
- .addTo(map)
- .bindPopup(`${name} ${address}`)
- .openPopup()
-
- return () => {
- themeObserver.disconnect()
- map.remove()
- }
- }, [])
-
- return (
-
- )
-}
diff --git a/src/features/team/PeopleSection.module.css b/src/features/team/PeopleSection.module.css
new file mode 100644
index 0000000..4e65f2d
--- /dev/null
+++ b/src/features/team/PeopleSection.module.css
@@ -0,0 +1,138 @@
+.lede {
+ max-width: 58ch;
+ margin: 0 auto;
+ color: var(--ink-soft);
+ text-align: center;
+}
+
+.people {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 24px;
+ max-width: 900px;
+ margin: 30px auto 10px;
+}
+
+.person {
+ composes: pinned from '../../styles/surfaces.module.css';
+ min-width: 0;
+ padding: 30px 22px 24px;
+ text-align: center;
+ transition:
+ box-shadow var(--motion-fast) var(--ease-out),
+ transform var(--motion-fast) var(--ease-out);
+}
+
+.people .person:nth-child(3n + 1) {
+ transform: rotate(-0.5deg);
+}
+
+.people .person:nth-child(3n + 2) {
+ transform: rotate(0.4deg);
+}
+
+.people .person:nth-child(3n + 3) {
+ transform: rotate(-0.3deg);
+}
+
+.portraitLink {
+ display: block;
+ width: fit-content;
+ margin: 0 auto;
+ border-radius: 50%;
+}
+
+.avatar {
+ display: block;
+ width: 124px;
+ height: 124px;
+ margin: 0 auto;
+ object-fit: cover;
+ border: 2.5px solid var(--navy);
+ border-radius: 48% 52% 55% 45% / 52% 46% 54% 48%;
+}
+
+.identity h3 {
+ margin-top: 16px;
+ color: var(--navy);
+ font-family: var(--font-hand);
+ font-size: 1.3rem;
+ line-height: 1.25;
+}
+
+.role {
+ margin-top: 4px;
+ color: var(--ink-muted);
+ font-size: 0.9rem;
+}
+
+.featuredWrap {
+ display: flex;
+ justify-content: center;
+ margin: 28px auto 20px;
+}
+
+.featured {
+ width: min(100%, 680px);
+ display: grid;
+ grid-template-columns: 150px 1fr;
+ column-gap: 28px;
+ align-items: center;
+ text-align: left;
+}
+
+.featured .avatar {
+ width: 142px;
+ height: 142px;
+}
+
+.featured .identity {
+ grid-column: 1;
+}
+
+.featured .bio {
+ grid-column: 2;
+ margin-top: 0;
+}
+
+.bio {
+ margin-top: 10px;
+ color: var(--ink-soft);
+ font-size: 0.9rem;
+}
+
+@media (hover: hover) and (pointer: fine) {
+ .person:hover {
+ transform: translateY(-2px) rotate(0deg);
+ }
+}
+
+@media (max-width: 760px) {
+ .people {
+ grid-template-columns: 1fr;
+ max-width: 420px;
+ }
+
+ .people .person {
+ transform: none;
+ }
+
+ .featured {
+ display: block;
+ text-align: center;
+ }
+
+ .featured .identity h3 {
+ margin-top: 16px;
+ }
+
+ .bio {
+ text-align: left;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .person {
+ transition: none;
+ }
+}
diff --git a/src/features/team/PeopleSection.tsx b/src/features/team/PeopleSection.tsx
new file mode 100644
index 0000000..02d44fa
--- /dev/null
+++ b/src/features/team/PeopleSection.tsx
@@ -0,0 +1,88 @@
+import { Pin } from '~/components/ui/Pin'
+import { Section } from '~/components/ui/Section'
+import { founders, judge, staff, type Person } from '~/data/team'
+import styles from './PeopleSection.module.css'
+
+function PersonCard({
+ person,
+ featured = false,
+}: {
+ person: Person
+ featured?: boolean
+}) {
+ const portrait = (
+
+ )
+
+ return (
+
+
+
+ {person.href ? (
+
+ {portrait}
+
+ ) : (
+ portrait
+ )}
+
{person.name}
+
{person.role}
+
+ {person.bio && {person.bio}
}
+
+ )
+}
+
+function PeopleGrid({ people }: { people: readonly Person[] }) {
+ return (
+
+ {people.map((person) => (
+
+ ))}
+
+ )
+}
+
+export function PeopleSection() {
+ return (
+ <>
+
+
+ The students leading MACSO’s contests, problems, and partnerships
+ today.
+
+
+
+
+
+ The original student team that turned MACSO from an idea into a
+ statewide community.
+
+
+
+
+ >
+ )
+}
diff --git a/src/features/team/TeamHero.module.css b/src/features/team/TeamHero.module.css
new file mode 100644
index 0000000..d7a378c
--- /dev/null
+++ b/src/features/team/TeamHero.module.css
@@ -0,0 +1,15 @@
+.eyebrow {
+ color: var(--blue-deep);
+ font-size: 0.78rem;
+ font-weight: 700;
+ letter-spacing: 0.13em;
+ text-transform: uppercase;
+}
+
+.summary {
+ max-width: 58ch;
+ margin: -2px auto 28px;
+ color: var(--ink-soft);
+ font-size: 1.05rem;
+ text-wrap: balance;
+}
diff --git a/src/features/team/TeamHero.tsx b/src/features/team/TeamHero.tsx
new file mode 100644
index 0000000..4fdccb9
--- /dev/null
+++ b/src/features/team/TeamHero.tsx
@@ -0,0 +1,30 @@
+import { Button } from '~/components/ui/Button'
+import { Hero, HeroActions, HeroTitle, Highlight } from '~/components/ui/Hero'
+import { ArrowRightIcon } from '~/components/ui/icons'
+import { site } from '~/data/site'
+import animations from '~/styles/animations.module.css'
+import styles from './TeamHero.module.css'
+
+export function TeamHero() {
+ return (
+
+ Meet MACSO
+
+ The people behind MACSO
+
+
+ Students, alumni, and mentors working together to build a better
+ competitive programming community in Massachusetts.
+
+
+
+ Join the team
+
+
+
+ Follow our work
+
+
+
+ )
+}
diff --git a/src/hooks/useSectionReveal.ts b/src/hooks/useSectionReveal.ts
deleted file mode 100644
index d80d3c1..0000000
--- a/src/hooks/useSectionReveal.ts
+++ /dev/null
@@ -1,49 +0,0 @@
-import { useEffect, type RefObject } from 'react'
-
-const FAILSAFE_MS = 8000
-const ENTER_TRANSITION =
- 'opacity var(--motion-medium) var(--ease-out), transform var(--motion-medium) var(--ease-out)'
-
-export function useSectionReveal(containerRef: RefObject) {
- useEffect(() => {
- const container = containerRef.current
- if (!container) return
- if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return
- if (!('IntersectionObserver' in window)) return
-
- const sections = container.querySelectorAll(':scope > section')
-
- const reveal = (el: HTMLElement) => {
- el.style.opacity = '1'
- el.style.transform = 'none'
- }
-
- const observer = new IntersectionObserver(
- (entries) => {
- for (const entry of entries) {
- if (!entry.isIntersecting) continue
- reveal(entry.target as HTMLElement)
- observer.unobserve(entry.target)
- }
- },
- { threshold: 0.1, rootMargin: '0px 0px -40px 0px' },
- )
-
- for (const el of sections) {
- if (el.getBoundingClientRect().top < window.innerHeight * 0.9) continue
- el.style.opacity = '0'
- el.style.transform = 'translateY(18px)'
- el.style.transition = ENTER_TRANSITION
- observer.observe(el)
- }
-
- const failsafe = window.setTimeout(() => {
- for (const el of sections) reveal(el)
- }, FAILSAFE_MS)
-
- return () => {
- observer.disconnect()
- window.clearTimeout(failsafe)
- }
- }, [containerRef])
-}
diff --git a/src/pages/ContestPage.tsx b/src/pages/ContestPage.tsx
new file mode 100644
index 0000000..2084d02
--- /dev/null
+++ b/src/pages/ContestPage.tsx
@@ -0,0 +1,34 @@
+import { PageLayout } from '~/components/layout/PageLayout'
+import { PhotoStrip } from '~/components/ui/PhotoStrip'
+import { Section } from '~/components/ui/Section'
+import { TileGrid } from '~/components/ui/TileGrid'
+import { contests, type ContestYear } from '~/data/contests'
+import { ContestHero } from '~/features/contest/ContestHero'
+import { StandingsSection } from '~/features/contest/StandingsSection'
+import typography from '~/styles/typography.module.css'
+
+export function ContestPage({ year }: { year: ContestYear }) {
+ const contest = contests[year]
+
+ return (
+
+
+
+
+ Moments from the contest floor, captured by the MACSO team.
+
+
+
+ {contest.scoreboards && (
+
+ )}
+
+
+ )
+}
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
index 2f7fb36..cc03c92 100644
--- a/src/pages/HomePage.tsx
+++ b/src/pages/HomePage.tsx
@@ -1,30 +1,12 @@
import { PageLayout } from '~/components/layout/PageLayout'
-import { FaqSection } from '~/features/home/FaqSection'
-import { HighlightsSection } from '~/features/home/HighlightsSection'
+import { ContextSection } from '~/features/home/ContextSection'
import { HomeHero } from '~/features/home/HomeHero'
-import { IntroSection } from '~/features/home/IntroSection'
-import { LocationSection } from '~/features/home/LocationSection'
-import { ResourcesSection } from '~/features/home/ResourcesSection'
-import { ResultsSection } from '~/features/home/ResultsSection'
-import {
- PartnersSection,
- SponsorsSection,
-} from '~/features/home/SponsorsSection'
-import { TeamSection } from '~/features/home/TeamSection'
export function HomePage() {
return (
-
+
-
-
-
-
-
-
-
-
-
+
)
}
diff --git a/src/pages/TeamPage.tsx b/src/pages/TeamPage.tsx
new file mode 100644
index 0000000..a655c43
--- /dev/null
+++ b/src/pages/TeamPage.tsx
@@ -0,0 +1,12 @@
+import { PageLayout } from '~/components/layout/PageLayout'
+import { PeopleSection } from '~/features/team/PeopleSection'
+import { TeamHero } from '~/features/team/TeamHero'
+
+export function TeamPage() {
+ return (
+
+
+
+
+ )
+}
diff --git a/src/styles/surfaces.module.css b/src/styles/surfaces.module.css
index 40eb809..8ee5209 100644
--- a/src/styles/surfaces.module.css
+++ b/src/styles/surfaces.module.css
@@ -10,6 +10,8 @@
position: relative;
}
-.lifted:hover {
- box-shadow: var(--shadow);
+@media (hover: hover) and (pointer: fine) {
+ .lifted:hover {
+ box-shadow: var(--shadow);
+ }
}
diff --git a/src/styles/typography.module.css b/src/styles/typography.module.css
index c00923f..b7dd10b 100644
--- a/src/styles/typography.module.css
+++ b/src/styles/typography.module.css
@@ -43,9 +43,11 @@
text-underline-offset var(--motion-fast) var(--ease-out);
}
-.link:hover {
- color: var(--blue-deep);
- text-underline-offset: 6px;
+@media (hover: hover) and (pointer: fine) {
+ .link:hover {
+ color: var(--blue-deep);
+ text-underline-offset: 6px;
+ }
}
.highlight {
diff --git a/team/index.html b/team/index.html
new file mode 100644
index 0000000..5ab8037
--- /dev/null
+++ b/team/index.html
@@ -0,0 +1,96 @@
+
+
+
+
+
+ Meet the MACSO Team
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vite.config.ts b/vite.config.ts
index 172e173..847d7a3 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -16,6 +16,13 @@ export default defineConfig({
careers: fileURLToPath(
new URL('./careers/index.html', import.meta.url),
),
+ team: fileURLToPath(new URL('./team/index.html', import.meta.url)),
+ contest2025: fileURLToPath(
+ new URL('./contests/2025/index.html', import.meta.url),
+ ),
+ contest2024: fileURLToPath(
+ new URL('./contests/2024/index.html', import.meta.url),
+ ),
},
},
},