diff --git a/common/routes.js b/common/routes.js index 490a17115..042bab67a 100644 --- a/common/routes.js +++ b/common/routes.js @@ -34,4 +34,6 @@ export const SHIPS = makePath('/ships') export const PARTNER_PACK = makePath('/partner-pack') +export const LOVE_LETTERS = makePath('/love-letters') + export const SECURITY_CHALLENGE = makePath('/security-challenge') \ No newline at end of file diff --git a/components/header/Header.jsx b/components/header/Header.jsx index 0e33ae614..49b145965 100644 --- a/components/header/Header.jsx +++ b/components/header/Header.jsx @@ -11,6 +11,7 @@ import GitHubLogo from '../../public/icons/github-logo' import IconCalendar from '../../public/icons/calendar' import IconBooks from '../../public/icons/books' import BoxGift from '../../public/icons/box-gift' +import Heart from '../../public/icons/heart' import Rocket from '../../public/icons/rocket' import { BREAKPOINTS } from '../../common/constants' @@ -122,6 +123,20 @@ const Header = () => { +
  • + + + + Love Letters + + +
  • diff --git a/components/postcard/Postcard.jsx b/components/postcard/Postcard.jsx new file mode 100644 index 000000000..aabcefae0 --- /dev/null +++ b/components/postcard/Postcard.jsx @@ -0,0 +1,47 @@ +/* eslint-disable @next/next/no-img-element */ +import { useMemo } from 'react' +import md from 'markdown-it' + +const Postcard = ({ postcards }) => { + const items = useMemo(() => postcards || [], [postcards]) + + if (items.length === 0) { + return ( +
    +

    No postcards yet.

    +
    + ) + } + + return ( +
    +
    +

    Love Letters

    +

    + Postcards of appreciation for the maintainers who keep open source thriving. +

    +
    +
    + {items.map((postcard, index) => ( +
    +
    + {postcard.quote} +
    +
    +
    + ))} +
    +
    + ) +} + +export default Postcard diff --git a/components/postcard/postcard.scss b/components/postcard/postcard.scss new file mode 100644 index 000000000..3a0ff7baf --- /dev/null +++ b/components/postcard/postcard.scss @@ -0,0 +1,89 @@ +.postcards { + padding: spacing(7) spacing(2); + padding-bottom: spacing(4); + max-width: 1440px; + margin: 0 auto; + box-sizing: border-box; + + @media (min-width: $lg) { + padding-left: spacing(4); + padding-right: spacing(4); + } + + &__intro { + margin-bottom: spacing(5); + max-width: 640px; + } + + &__heading { + @extend %header-2; + color: $white; + margin-bottom: spacing(2); + overflow-wrap: break-word; + } + + &__subtitle { + @extend %body-1; + color: $white-50; + } + + &__empty { + text-align: center; + padding: spacing(8) spacing(2); + @extend %body-1; + color: $white-50; + } + + &__grid { + display: grid; + grid-template-columns: 1fr; + gap: spacing(3); + + @media (min-width: $md) { + grid-template-columns: repeat(2, 1fr); + } + + @media (min-width: $lg) { + grid-template-columns: repeat(3, 1fr); + } + } + + &__card { + background: $white-20; + border-radius: 12px; + overflow: hidden; + + @extend %background-filter; + } + + &__image-wrapper { + aspect-ratio: 4 / 3; + overflow: hidden; + } + + &__image { + width: 100%; + height: 100%; + object-fit: cover; + display: block; + } + + &__quote { + padding: spacing(3); + @extend %body-1; + color: $white-80; + font-style: italic; + margin: 0; + + a { + color: $white; + text-decoration: underline; + text-underline-offset: 3px; + transition: opacity $simple-fade; + + &:hover { + opacity: 0.8; + } + } + } +} diff --git a/content/love-letters/postcards.md b/content/love-letters/postcards.md new file mode 100644 index 000000000..0a13cf68c --- /dev/null +++ b/content/love-letters/postcards.md @@ -0,0 +1,13 @@ +--- +postcards: + - image: https://github.com/user-attachments/assets/86057fb3-ff13-4548-b6e8-d379c73a80bc + quote: "Thank you for maintaining this project — it powers our entire workflow." + - image: https://github.com/user-attachments/assets/4a35b200-dd04-44d9-816e-b828e403b0b4 + quote: "Your dedication to open source inspires me every day." + - image: https://github.com/user-attachments/assets/2d6f0283-d9aa-4eee-9294-40ff2797bb17 + quote: "This library saved me hundreds of hours. You are appreciated!" + - image: https://github.com/user-attachments/assets/003a97f3-2866-419a-8464-29727d312d3d + quote: "This library saved me hundreds of hours. You are appreciated!" + - image: https://github.com/user-attachments/assets/fa44bc57-e10f-453e-a8c9-2bc5d6312327 + quote: "This library saved me hundreds of hours. You are appreciated! ~ [@samus-aran](https://github.com/samus-aran)" +--- diff --git a/pages/love-letters.js b/pages/love-letters.js new file mode 100644 index 000000000..01f57043f --- /dev/null +++ b/pages/love-letters.js @@ -0,0 +1,48 @@ +import { useEffect } from 'react' +import Head from 'next/head' +import path from 'path' + +import { getLiteral } from '../common/i18n' +import { getDataFromMD } from '../common/api' +import Postcard from '../components/postcard/Postcard' + +import { useBackground } from '../contexts/BackgroundContext' + +export default function LoveLettersPage({ postcards }) { + const { setAnimationStep } = useBackground() + + useEffect(() => { + setAnimationStep(6) + }, [setAnimationStep]) + + return ( +
    + + {`Love Letters - ${getLiteral('meta:title')}`} + + + + + + + + + + + + + +
    + ) +} + +export async function getStaticProps() { + const filePath = path.join(process.cwd(), 'content', 'love-letters', 'postcards.md') + const data = getDataFromMD(filePath) + + return { + props: { + postcards: data.postcards || [], + }, + } +} diff --git a/public/icons/heart.js b/public/icons/heart.js new file mode 100644 index 000000000..a2300b2c7 --- /dev/null +++ b/public/icons/heart.js @@ -0,0 +1,13 @@ +const Heart = () => ( + + + +) + +export default Heart diff --git a/styles/styles.scss b/styles/styles.scss index 213885114..640f7fbfe 100644 --- a/styles/styles.scss +++ b/styles/styles.scss @@ -33,6 +33,8 @@ @import '../components/ships/ships'; @import '../components/ships/ships-filter'; +@import '../components/postcard/postcard'; + @import '../components/not-found/not-found'; @import '../components/partner-pack/offers/offers';