Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/dwosi/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const record: RecordEntry[] = [
},
]

/** Published Dev Weekends numbers. Keep in sync with components/home-impact.tsx. */
/** Published Dev Weekends numbers. Keep in sync with components/home/data.ts. */
export const communityStats = [
{ value: "1,000+", label: "Engineers trained since 2018" },
{ value: "$20K+", label: "Open source stipends" },
Expand Down
7 changes: 4 additions & 3 deletions app/team/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"
import Image from "next/image"
import { ExternalLink, Linkedin } from "lucide-react"

import HomeImpact from "@/components/home-impact"
import { Impact, Outcomes } from "@/components/home/sections-top"
import { Button, Card, Section, SectionHeading, PageHero } from "@/components/site/page-kit"
import { Voices } from "@/components/home/sections-bottom"
import { Slider } from "@/components/site/slider"
Expand Down Expand Up @@ -198,8 +198,9 @@ export default function TeamPage() {

<Voices />

{/* Impact: the proof comes right after the people */}
<HomeImpact />
{/* Impact and outcomes: the same sections as the homepage */}
<Impact />
<Outcomes />

{/* The founder story is written separately and rendered here */}
<FounderStory />
Expand Down
216 changes: 0 additions & 216 deletions components/home-impact.tsx

This file was deleted.

31 changes: 26 additions & 5 deletions components/home/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const LINKS = {
guides: "/guides",
blog: "/blog",
ourStory: "/team",
impactNumbers: "/team#impact",
impactNumbers: "/#impact",
fellowshipReport: "/blog/fellowship-2026-by-the-numbers",
gsocPost: "/blog/gsoc",
lfxPost: "/blog/lfx-mentorship-2026-talha-amjad",
Expand Down Expand Up @@ -106,6 +106,10 @@ export const applyPrograms: ApplyProgram[] = [
},
]

/**
* Only countries named in content/blog/fellowship-2026-by-the-numbers.md. The post
* counts 30 countries but names 29, so the 30th is left out until we know it.
*/
export const countries = [
"Pakistan", "India", "Nepal", "USA", "UK", "Nigeria", "Egypt", "Morocco", "Kenya", "Palestine",
"Rwanda", "Russia", "Australia", "Singapore", "Spain", "Italy", "China", "Turkey", "Cameroon",
Expand Down Expand Up @@ -155,11 +159,28 @@ export const placesLike: Org[] = [
{ name: "Olostep", href: "https://www.olostep.com", logo: "/logos/olostep.svg" },
]

/** Every line is a conservative estimate from numbers members reported; `info` says how it is counted */
export const impactBreakdown = [
{ label: "Remote salaries, every year", value: "$1.3M+" },
{ label: "Earned on Upwork", value: "$1M+" },
{ label: "Earned at Turing and Scale AI", value: "$250K+" },
{ label: "Open source stipends", value: "$20K+" },
{
label: "Remote salaries, every year",
value: "$1.3M+",
info: "50+ members in remote jobs paying $20,000 to $90,000 a year. The median is about $20,000, and 10 of them earn $50,000 to $90,000. This one recurs every year.",
},
{
label: "Earned on Upwork",
value: "$1M+",
info: "20 freelancers averaging about $50,000 in lifetime earnings each, with several past $100,000, $200,000 and $300,000.",
},
{
label: "Earned at Turing and Scale AI",
value: "$250K+",
info: "20+ engineers on full-time Turing engagements of 3 to 4 months each, plus members paid by Scale AI for competitive programming challenges used to train and test top LLMs.",
},
{
label: "Open source stipends",
value: "$20K+",
info: "11 Google Summer of Code stipends (9 in 2026, 2 in 2025) at about $1,500 each, plus the LFX Mentorship stipend.",
},
]

export const outcomes = [
Expand Down
20 changes: 17 additions & 3 deletions components/home/sections-top.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,10 +201,24 @@ export function Impact() {
<dd className="text-[28px] font-semibold tracking-[-0.03em]">{item.value}</dd>
</div>
))}
<SmartLink href={LINKS.impactNumbers} className="mt-5 inline-flex min-h-11 items-center text-sm font-medium text-white">
How we count these numbers →
</SmartLink>
</dl>
<details className="group min-w-0 basis-full border-t border-[#262626] pt-2 text-white">
<summary className="inline-flex min-h-11 cursor-pointer list-none items-center gap-2 text-sm font-medium [&::-webkit-details-marker]:hidden">
How we count these numbers
<span aria-hidden="true" className="transition-transform group-open:rotate-90">→</span>
</summary>
<ul className="mt-2 grid grid-cols-1 gap-x-10 gap-y-4 pb-2 md:grid-cols-2">
{impactBreakdown.map((item) => (
<li key={item.label} className="text-sm leading-[1.6] text-[#A3A3A3]">
<span className="font-semibold text-white">{item.label}, {item.value}.</span> {item.info}
</li>
))}
<li className="text-sm leading-[1.6] text-[#A3A3A3]">
The total is the sum of these lines, rounded down. Remote salaries are counted for one year only, so the real
figure grows every year.
</li>
</ul>
</details>
</div>
</div>
</section>
Expand Down
6 changes: 4 additions & 2 deletions components/home/voices.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import Link from "next/link"
import { ArrowLeft, ArrowRight, Play } from "lucide-react"

import { getVideoEmbed } from "@/lib/video-url"
import { byStoryRank } from "@/lib/story-order"
import { LINKS, videoStories, voices } from "./data"

type LiveVideo = { name: string; role: string; url: string; previewUrl?: string }

/**
* Stories rail: video stories, loaded from the same approved stories as the
* stories page (GSoC and LFX first), after the videos listed in data.ts.
* stories page, merged with the videos listed in data.ts and ordered by
* lib/story-order.ts (LFX, then GSoC, then senior engineers, then the rest).
* Written quotes only fill in when there are too few videos. It does not move
* by itself: people scroll it with the arrows, a swipe or the trackpad.
*/
Expand All @@ -29,7 +31,7 @@ export function VoicesRail() {

const seen = new Set<string>()
const listed: { name: string; role: string; url: string | null; previewUrl?: string }[] = [...videoStories, ...live]
const videos = listed.flatMap((story) => {
const videos = byStoryRank(listed).flatMap((story) => {
const embed = getVideoEmbed(story.url ?? undefined)
if (!embed || seen.has(embed.embedUrl)) return []
seen.add(embed.embedUrl)
Expand Down
3 changes: 2 additions & 1 deletion components/story/founder-story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ export function FounderStory() {

<div className="min-w-0 flex-[1.6_1_480px] space-y-6 text-[19px] leading-[1.7] text-[#262626]">
<p>
We started in 2018, on weekends, because that is when working people could show up. We kept seeing the same gaps.
We started in 2018, on weekends, because that is when working people could show up. The name stayed; today
something runs almost every day. We kept seeing the same gaps.
Universities teach syntax, not systems. Talented people have no roadmap. Learning alone burns people out. We made it
free and nonprofit on purpose, because the people who need it most cannot pay. We began in Pakistan, and today our
members come from 30 countries.
Expand Down
12 changes: 4 additions & 8 deletions lib/stories.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import connectDB from '@/lib/db';
import connectImpactDB from '@/lib/impact-db';
import { getVideoEmbed } from '@/lib/video-url';
import { byStoryRank } from '@/lib/story-order';
import { Testimonial as TestimonialModel } from '@/models/Testimonial';
import { getImpactStoryModel } from '@/models/ImpactStory';
import type { Testimonial } from '@/components/testimonial-card';
Expand Down Expand Up @@ -86,16 +87,11 @@ export async function withPreviews(stories: Testimonial[]): Promise<Testimonial[

export type VideoStory = { name: string; role: string; url: string; previewUrl?: string };

/** Open source stories (GSoC, LFX and similar) lead the video rail */
function openSourceRank(role: string) {
return /gsoc|summer of code|lfx|esoc/i.test(role) ? 0 : 1;
}

export async function getVideoStories(): Promise<VideoStory[]> {
const stories = await getPublicStories();
const videos = stories.filter((story) => story.type === 'video' && getVideoEmbed(story.videoUrl));
const withImages = await withPreviews(videos);
return withImages
.map((story) => ({ name: story.name, role: story.role, url: story.videoUrl as string, previewUrl: story.previewUrl }))
.sort((a, b) => openSourceRank(a.role) - openSourceRank(b.role));
return byStoryRank(
withImages.map((story) => ({ name: story.name, role: story.role, url: story.videoUrl as string, previewUrl: story.previewUrl })),
);
}
Loading
Loading