diff --git a/bun.lock b/bun.lock index 2b79ebf..ca2b6ee 100644 --- a/bun.lock +++ b/bun.lock @@ -5,13 +5,11 @@ "": { "name": "macso-website", "dependencies": { - "leaflet": "^1.9.4", "react": "^19.2.8", "react-dom": "^19.2.8", }, "devDependencies": { "@lhci/cli": "0.15.1", - "@types/leaflet": "^1.9.22", "@types/node": "^26.2.0", "@types/react": "^19.2.18", "@types/react-dom": "^19.2.4", @@ -157,10 +155,6 @@ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - "@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="], - - "@types/leaflet": ["@types/leaflet@1.9.22", "", { "dependencies": { "@types/geojson": "*" } }, "sha512-h3lhECYEKDasG7LFHu+GiHqAvsgLuQvlJvVZzJDGONo3sEL+wUOqSFLnwkZlK0qVxnxbuGFW8iBlJNYs5wgndA=="], - "@types/node": ["@types/node@26.2.0", "", { "dependencies": { "undici-types": "~8.3.0" } }, "sha512-5IviulTZeRNp2vAJ514cc/HUlY5nZ9fCbq9DMyC52BrhFZACo3nI0R7qBxhQmo/d27NFe96ur/b7Wwxklda+kg=="], "@types/react": ["@types/react@19.2.18", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w=="], @@ -431,8 +425,6 @@ "js-yaml": ["js-yaml@3.15.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-S99WuO3HlhO3XN41EtYUNl9zzXjoJx7QvmipxsJVxtCBT0YHEFy+iOJhjSvrmV12nYhWpZaM8lPHkJm0yUMbag=="], - "leaflet": ["leaflet@1.9.4", "", {}, "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA=="], - "legacy-javascript": ["legacy-javascript@0.0.1", "", {}, "sha512-lPyntS4/aS7jpuvOlitZDFifBCb4W8L/3QU0PLbUTUj+zYah8rfVjYic88yG7ZKTxhS5h9iz7duT8oUXKszLhg=="], "lighthouse": ["lighthouse@13.4.1", "", { "dependencies": { "@paulirish/trace_engine": "0.0.65", "@sentry/node": "^10.0.0", "axe-core": "^4.12.1", "chrome-launcher": "^1.2.1", "configstore": "^7.0.0", "csp_evaluator": "1.1.8", "devtools-protocol": "0.0.1663043", "enquirer": "^2.3.6", "http-link-header": "^1.1.1", "intl-messageformat": "^10.5.3", "jpeg-js": "^0.4.4", "js-library-detector": "^6.7.0", "lighthouse-logger": "^2.0.2", "lighthouse-stack-packs": "1.12.3", "lodash-es": "^4.17.21", "lookup-closest-locale": "6.2.0", "open": "^8.4.0", "puppeteer-core": "^25.3.0", "robots-parser": "^3.0.1", "speedline-core": "^1.4.3", "third-party-web": "^0.29.2", "tldts-icann": "^7.4.9", "web-features": "^3.34.0", "ws": "^7.0.0", "yargs": "^17.3.1", "yargs-parser": "^21.0.0" }, "bin": { "lighthouse": "cli/index.js", "smokehouse": "cli/test/smokehouse/frontends/smokehouse-bin.js", "chrome-debug": "core/scripts/manual-chrome-launcher.js" } }, "sha512-fDu8lt3QLK/lTqIxtp1HkzQNJ32rsFHhbadYOepcMZFLgA8oINhxutMbMv8XXnpTOvZ0TXCo4JCk1LDTWaRLnA=="], diff --git a/config/lighthouserc.json b/config/lighthouserc.json index 5d1255e..a82ed82 100644 --- a/config/lighthouserc.json +++ b/config/lighthouserc.json @@ -2,7 +2,13 @@ "ci": { "collect": { "staticDistDir": "./dist", - "url": ["http://localhost/", "http://localhost/careers/"], + "url": [ + "http://localhost/", + "http://localhost/team/", + "http://localhost/careers/", + "http://localhost/contests/2025/", + "http://localhost/contests/2024/" + ], "numberOfRuns": 3 }, "assert": { diff --git a/contests/2024/index.html b/contests/2024/index.html new file mode 100644 index 0000000..a260247 --- /dev/null +++ b/contests/2024/index.html @@ -0,0 +1,97 @@ + + + + + + MACSO 2024 Contest Archive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/contests/2025/index.html b/contests/2025/index.html new file mode 100644 index 0000000..19d1100 --- /dev/null +++ b/contests/2025/index.html @@ -0,0 +1,96 @@ + + + + + + MACSO 2025 Contest Archive + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/index.html b/index.html index 2daf9ef..c304d65 100644 --- a/index.html +++ b/index.html @@ -136,9 +136,6 @@ - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/sitemap.xml b/public/sitemap.xml index 9fb951a..8036d86 100644 --- a/public/sitemap.xml +++ b/public/sitemap.xml @@ -2,13 +2,31 @@ https://macso.org/ - 2026-07-22 + 2026-08-26 monthly 1.0 + + https://macso.org/contests/2025/ + 2026-08-26 + yearly + 0.8 + + + https://macso.org/contests/2024/ + 2026-08-26 + yearly + 0.8 + + + https://macso.org/team/ + 2026-08-26 + monthly + 0.8 + https://macso.org/careers/ - 2026-07-22 + 2026-08-26 monthly 0.8 diff --git a/src/components/layout/Footer.module.css b/src/components/layout/Footer.module.css index 94929ee..86b6b09 100644 --- a/src/components/layout/Footer.module.css +++ b/src/components/layout/Footer.module.css @@ -1,48 +1,115 @@ .footer { border-top: 2px solid var(--navy); background: color-mix(in srgb, var(--footer-bg) 96%, transparent); - text-align: center; - font-size: 0.86rem; - line-height: 1.9; - padding: 36px 24px 44px; + padding: 26px 28px 18px; color: var(--footer-text); + font-size: 0.82rem; + line-height: 1.55; } -.footer p { - max-width: 720px; - margin: 4px auto; +.footerInner { + display: grid; + grid-template-columns: minmax(240px, 0.8fr) minmax(0, 1.7fr); + gap: 28px 48px; + align-items: center; + max-width: 1080px; + margin: 0 auto; } -.footer .creditLink { +.about p { + max-width: 42ch; + margin-top: 8px; +} + +.footerBrand { color: var(--accent-text); + font-family: var(--font-hand); + font-size: 1.35rem; + font-weight: 700; + line-height: 1; + text-decoration: none; +} + +.footerBrand span { + color: var(--blue); } -.footer .creditLink:hover { +.footerNav { + display: flex; + flex-wrap: wrap; + justify-content: flex-end; + gap: 8px 18px; +} + +.footerNav a { + color: var(--footer-text); + text-decoration-color: transparent; + text-underline-offset: 4px; + transition: + color var(--motion-fast) var(--ease-out), + text-decoration-color var(--motion-fast) var(--ease-out); +} + +.meta { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px 24px; + max-width: 1080px; + margin: 16px auto 0; + padding-top: 16px; + border-top: 1px solid color-mix(in srgb, var(--footer-text) 24%, transparent); + text-align: center; +} + +.footer .creditLink { color: var(--accent-text); } .topLink { color: var(--accent-text); text-decoration: none; - font-size: 0.82rem; font-weight: 700; border-bottom: 2px dotted var(--blue); padding-bottom: 2px; - transition: - color var(--motion-fast) var(--ease-out), - border-color var(--motion-fast) var(--ease-out); + transition: border-color var(--motion-fast) var(--ease-out); } -.topLink:hover { - color: var(--accent-text); - border-color: var(--accent-text); +@media (hover: hover) and (pointer: fine) { + .footerNav a:hover { + color: var(--accent-text); + text-decoration-color: var(--blue); + } + + .topLink:hover { + color: var(--accent-text); + border-color: var(--accent-text); + } } -.note { - margin-top: 20px; +@media (max-width: 720px) { + .footerInner { + grid-template-columns: 1fr; + gap: 22px; + } + + .footerNav { + justify-content: flex-start; + } +} + +@media (max-width: 520px) { + .footer { + padding-inline: 18px; + } + + .footerNav { + gap: 8px 14px; + } } @media (prefers-reduced-motion: reduce) { + .footerNav a, .topLink { transition: none; } diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index c5acd75..a7e2e57 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -12,39 +12,40 @@ export function Footer({ page }: FooterProps) { return ( ) } diff --git a/src/components/layout/Header.module.css b/src/components/layout/Header.module.css index f7d11c4..899efc4 100644 --- a/src/components/layout/Header.module.css +++ b/src/components/layout/Header.module.css @@ -27,10 +27,6 @@ transition: color var(--motion-fast) var(--ease-out); } -.brand:hover { - color: var(--blue-deep); -} - .nav { display: flex; align-items: center; @@ -44,40 +40,45 @@ font-size: 0.88rem; font-weight: 700; transition: + transform var(--motion-fast) var(--ease-out), color var(--motion-fast) var(--ease-out), text-underline-offset var(--motion-fast) var(--ease-out); } -.nav a:hover { - color: var(--blue-deep); - text-decoration: underline; - text-underline-offset: 5px; - text-decoration-thickness: 2px; - text-decoration-color: var(--blue); -} - .nav a.hire { - font-family: var(--font-hand); - font-weight: 700; - font-size: 1.02rem; + display: inline-block; + padding: 5px 15px 7px; color: var(--accent-text); - text-decoration: none; background: var(--accent-surface); - padding: 5px 16px 7px; box-shadow: 2px 3px 0 color-mix(in srgb, var(--blue) 50%, transparent); - transform: rotate(-1.5deg); - display: inline-block; - transition: - transform var(--motion-fast) var(--ease-out), - background-color var(--motion-fast) var(--ease-out), - box-shadow var(--motion-fast) var(--ease-out); + font-family: var(--font-hand); + font-size: 0.96rem; + text-decoration: none; + transform: rotate(-1deg); } -.nav a.hire:hover { - transform: rotate(0deg); - background: var(--accent-surface-hover); +.nav a.hire[aria-current='page'] { color: var(--accent-text); + background: var(--accent-surface-hover); text-decoration: none; + transform: rotate(0deg); +} + +.nav a.hire:active { + transform: scale(0.97); +} + +.nav a[aria-current='page'] { + color: var(--navy); + text-decoration: underline; + text-decoration-thickness: 2px; + text-decoration-color: var(--blue); + text-underline-offset: 5px; +} + +.nav a:active, +.themeButton:active { + transform: scale(0.97); } .themeButton { @@ -102,14 +103,6 @@ transform var(--motion-fast) var(--ease-out); } -.themeButton:hover { - background: var(--paper-blue); - box-shadow: - 3px 5px 0 color-mix(in srgb, var(--blue) 27%, transparent), - 0 5px 12px color-mix(in srgb, var(--navy) 10%, transparent); - transform: translateY(-1px); -} - .themeButton:active { box-shadow: 1px 2px 0 color-mix(in srgb, var(--blue) 24%, transparent); transform: translate(2px, 2px); @@ -132,18 +125,95 @@ transition: transform var(--motion-fast) var(--ease-out); } -.themeButton:hover svg { - transform: rotate(12deg); +@media (hover: hover) and (pointer: fine) { + .brand:hover { + color: var(--blue-deep); + } + + .nav a:hover { + color: var(--blue-deep); + text-decoration: underline; + text-underline-offset: 5px; + text-decoration-thickness: 2px; + text-decoration-color: var(--blue); + } + + .nav a.hire:hover { + color: var(--accent-text); + background: var(--accent-surface-hover); + text-decoration: none; + transform: rotate(0deg); + } + + .themeButton:hover { + background: var(--paper-blue); + box-shadow: + 3px 5px 0 color-mix(in srgb, var(--blue) 27%, transparent), + 0 5px 12px color-mix(in srgb, var(--navy) 10%, transparent); + transform: translateY(-1px); + } + + .themeButton:hover svg { + transform: rotate(12deg); + } } @media (max-width: 768px) { .header { padding: 12px 18px; } + + .nav { + gap: 2px 12px; + } + + .nav a { + font-size: 0.82rem; + } +} + +@media (max-width: 700px) { + .header { + display: block; + padding: 10px 14px 9px; + } + + .brand { + display: inline-block; + } + + .nav { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 4px; + width: 100%; + margin-top: 9px; + } + + .nav a { + padding: 3px 1px; + font-size: 0.75rem; + text-align: center; + } + + .themeButton { + position: absolute; + top: 8px; + right: 14px; + width: 34px; + height: 34px; + } + + .nav a.hire { + position: absolute; + top: 8px; + right: 58px; + padding: 5px 11px 6px; + font-size: 0.82rem; + } } @media (prefers-reduced-motion: reduce) { - .nav a.hire, .themeButton, .themeButton svg, .brand, diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index adf3694..c68cd7b 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -11,8 +11,6 @@ type HeaderProps = { export function Header({ page }: HeaderProps) { const [theme, setTheme] = useState(getPreferredTheme) const { topId } = pageMeta[page] - const isHome = page === 'home' - const hashBase = isHome ? '' : '/' useEffect(() => { applyTheme(theme) @@ -26,12 +24,19 @@ export function Header({ page }: HeaderProps) { return (
- + macso.
diff --git a/src/components/layout/PageLayout.module.css b/src/components/layout/PageLayout.module.css index 87fe63e..4fec293 100644 --- a/src/components/layout/PageLayout.module.css +++ b/src/components/layout/PageLayout.module.css @@ -4,8 +4,22 @@ padding: 0 28px; } +.footerBelowFold { + min-height: calc(100svh - 62px); + display: flex; + flex-direction: column; +} + +.footerBelowFold > section:last-child { + flex: 1; +} + @media (max-width: 768px) { .main { padding: 0 18px; } + + .footerBelowFold { + min-height: calc(100svh - 84px); + } } diff --git a/src/components/layout/PageLayout.tsx b/src/components/layout/PageLayout.tsx index 21f1bf5..b2f6ae4 100644 --- a/src/components/layout/PageLayout.tsx +++ b/src/components/layout/PageLayout.tsx @@ -1,5 +1,4 @@ -import { useRef, type ReactNode } from 'react' -import { useSectionReveal } from '~/hooks/useSectionReveal' +import type { ReactNode } from 'react' import { Footer } from './Footer' import { Header } from './Header' import { SkipLink } from './SkipLink' @@ -9,17 +8,26 @@ import styles from './PageLayout.module.css' type PageLayoutProps = { page: PageId children: ReactNode + footerBelowFold?: boolean } -export function PageLayout({ page, children }: PageLayoutProps) { - const mainRef = useRef(null) - useSectionReveal(mainRef) - +export function PageLayout({ + page, + children, + footerBelowFold = false, +}: PageLayoutProps) { return ( <>
-
+
{children}