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 (
+
+ )
+ }
+
+ return (
+
+
+
Love Letters
+
+ Postcards of appreciation for the maintainers who keep open source thriving.
+
+
+
+ {items.map((postcard, index) => (
+
+
+

+
+
+
+ ))}
+
+
+ )
+}
+
+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';