From fbf800dc2e76dafcc5749d0faabc6978675d0d29 Mon Sep 17 00:00:00 2001 From: Joseph Steele Date: Sat, 16 May 2026 14:34:35 +0100 Subject: [PATCH 1/2] feat(sponsor): add /sponsor page with live progress + source breakdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #631. New page at /sponsor focused entirely on financial sponsorship: - Live progress block: current monthly recurring vs. ${'$'}12k goal, percentage bar, "+${'$'}X vs. one week ago" delta. Data fetched at build time from https://ddev.github.io/sponsorship-data/data/all-sponsorships.json with a fallback snapshot so the build never breaks on a transient network failure. - Three side-by-side CTAs for GitHub Sponsors, PayPal, and direct invoice, each with a one-line description targeted at its likely audience. - Source breakdown table — monthly recurring equivalent by channel (GitHub Sponsors, monthly invoiced, annual invoiced normalized to monthly, PayPal). Annual sponsorships are converted to a monthly figure for direct comparison. - Featured organizations block reusing the existing FeaturedSponsors component. - Bubble grid of all current sponsors via the existing Sponsors component (the piece the issue specifically asked to keep from the support-ddev page). - Footer CTA repeat + 501(c)(3) note pointing at /foundation. The /support-ddev page is unchanged in scope but now opens with a clearly-marked banner linking financial sponsors to /sponsor, and its table-of-contents item for "Financial Support" points there too. The existing financial section on /support-ddev stays in place so external links aren't broken; /sponsor is the canonical entry going forward. Implementation notes: - Build-time fetch uses AbortSignal.timeout(8000) and a typed fallback so the page renders even if the data endpoint is unreachable. Cloudflare Pages can be configured to rebuild daily to pull the latest snapshot; alternatively a small client-side refresh script could be added as a follow-up. - Direct, factual copy throughout per AGENTS.md — no marketing superlatives, no flowery framing. - Dark-mode aware (matches the rest of the site's dark: utility conventions). - Astro check passes cleanly for both files (98 pre-existing repo errors unchanged; none in sponsor.astro or support-ddev.astro). - Full astro build completes successfully and produces dist/sponsor/index.html with all live data baked in (verified locally: ${'$'}9,456 / 78.8% / +${'$'}1,603 week-over-week as of 2026-05-16). --- src/pages/sponsor.astro | 418 +++++++++++++++++++++++++++++++++++ src/pages/support-ddev.astro | 14 +- 2 files changed, 431 insertions(+), 1 deletion(-) create mode 100644 src/pages/sponsor.astro diff --git a/src/pages/sponsor.astro b/src/pages/sponsor.astro new file mode 100644 index 00000000..650ebbff --- /dev/null +++ b/src/pages/sponsor.astro @@ -0,0 +1,418 @@ +--- +import Layout from "../layouts/Layout.astro" +import Heading from "../components/Heading.astro" +import CtaButton from "../components/CtaButton.astro" +import FeaturedSponsors from "../components/FeaturedSponsors.astro" +import Sponsors from "../components/Sponsors.astro" + +const title = `Sponsor DDEV` +const description = `Help fund DDEV's two full-time maintainers. Monthly recurring sponsorship via GitHub, PayPal, or invoice — live progress, transparent breakdown.` + +/** + * Live sponsorship totals from the published GitHub Pages snapshot at + * https://ddev.github.io/sponsorship-data/data/all-sponsorships.json + * (alias: https://ddev.com/s/sponsorship-data.json). Updated daily by + * the github-sponsorships.sh script in ddev/sponsorship-data. + * + * Fetched at build time. If the endpoint is unreachable we render the + * page with the most-recent known totals so the build never breaks on a + * transient network failure. + */ +const SPONSORSHIP_DATA_URL = + "https://ddev.github.io/sponsorship-data/data/all-sponsorships.json" + +interface SourceTotals { + monthly: number + sponsors: number +} + +interface SponsorshipSnapshot { + github: SourceTotals + monthly_invoiced: SourceTotals + annual_invoiced_monthly_equivalent: SourceTotals + paypal_monthly: number + total_monthly: number + goal_amount: number + progress_percentage: number + one_week_ago_monthly: number | null + updated_iso: string | null + fallback: boolean +} + +const fallback: SponsorshipSnapshot = { + github: { monthly: 5484, sponsors: 239 }, + monthly_invoiced: { monthly: 3048, sponsors: 8 }, + annual_invoiced_monthly_equivalent: { monthly: 879, sponsors: 9 }, + paypal_monthly: 45, + total_monthly: 9456, + goal_amount: 12000, + progress_percentage: 78.8, + one_week_ago_monthly: 7853, + updated_iso: null, + fallback: true, +} + +async function fetchSponsorshipData(): Promise { + try { + const res = await fetch(SPONSORSHIP_DATA_URL, { + signal: AbortSignal.timeout(8000), + }) + if (!res.ok) return fallback + const data: any = await res.json() + const ghDdev = data.github_ddev_sponsorships ?? {} + const ghRfay = data.github_rfay_sponsorships ?? {} + const mi = data.monthly_invoiced_sponsorships ?? {} + const ai = data.annual_invoiced_sponsorships ?? {} + return { + github: { + monthly: + (ghDdev.total_monthly_sponsorship ?? 0) + + (ghRfay.total_monthly_sponsorship ?? 0), + sponsors: (ghDdev.total_sponsors ?? 0) + (ghRfay.total_sponsors ?? 0), + }, + monthly_invoiced: { + monthly: mi.total_monthly_sponsorship ?? 0, + sponsors: mi.total_sponsors ?? 0, + }, + annual_invoiced_monthly_equivalent: { + monthly: ai.monthly_equivalent_sponsorship ?? 0, + sponsors: ai.total_sponsors ?? 0, + }, + paypal_monthly: data.paypal_sponsorships ?? 0, + total_monthly: data.total_monthly_average_income ?? 0, + goal_amount: data.current_goal?.target_amount ?? 12000, + progress_percentage: data.current_goal?.progress_percentage ?? 0, + one_week_ago_monthly: data.historical_data?.one_week_ago ?? null, + updated_iso: data.updated_datetime ?? null, + fallback: false, + } + } catch { + return fallback + } +} + +const data = await fetchSponsorshipData() + +const usd = (n: number) => + `$${n.toLocaleString("en-US", { maximumFractionDigits: 0 })}` + +const sevenDayDelta = + data.one_week_ago_monthly != null + ? data.total_monthly - data.one_week_ago_monthly + : null + +const updatedHuman = data.updated_iso + ? new Date(data.updated_iso).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }) + : null + +// Clamp progress for the bar; the real percentage is also shown numerically. +const barPercent = Math.min(100, Math.max(0, data.progress_percentage)) + +const sources = [ + { + name: "GitHub Sponsors", + amount: data.github.monthly, + sponsors: data.github.sponsors, + note: "ddev org + rfay", + url: "https://github.com/sponsors/ddev", + }, + { + name: "Monthly invoiced", + amount: data.monthly_invoiced.monthly, + sponsors: data.monthly_invoiced.sponsors, + note: "direct billing", + url: "/contact", + }, + { + name: "Annual invoiced", + amount: data.annual_invoiced_monthly_equivalent.monthly, + sponsors: data.annual_invoiced_monthly_equivalent.sponsors, + note: "monthly equivalent", + url: "/contact", + }, + { + name: "PayPal", + amount: data.paypal_monthly, + sponsors: null, + note: "monthly equivalent", + url: "https://www.paypal.com/donate/?hosted_button_id=MCNCSZHC7LHSQ", + }, +] +--- + + +
+ + + {/* --- Live progress block --- */} +
+
+
+

+ Monthly recurring sponsorship +

+

+ {usd(data.total_monthly)} + +  / {usd(data.goal_amount)} + +

+
+

+ + {data.progress_percentage.toFixed(1)}% + + of the {usd(data.goal_amount)}/month goal + {sevenDayDelta != null && sevenDayDelta !== 0 && ( + <> +
+ + {sevenDayDelta > 0 ? "+" : ""} + {usd(sevenDayDelta)} vs. one week ago + + + )} +

+
+ +
+
+
+
+ + {updatedHuman && !data.fallback && ( +

+ Data updated daily · last update {updatedHuman} ·{" "} + + view raw JSON + +

+ )} +
+ + {/* --- Three primary CTAs --- */} +
+

+ Three ways to sponsor +

+

+ Any amount helps. Monthly recurring is what funds the maintainers; we + list each channel separately so you can pick the one that fits. +

+ +
+ {/* GitHub Sponsors */} +
+

+ GitHub Sponsors +

+

+ For individuals and organizations who already use GitHub. Monthly + recurring; tiers from $1 to $530. +

+ +
+ + {/* PayPal */} +
+

+ PayPal +

+

+ One-time gift or recurring without a GitHub account. Supports any + amount. +

+ +
+ + {/* Invoice */} +
+

+ Direct invoice +

+

+ For organizations with a procurement process. The DDEV Foundation + is a US 501(c)(3); invoices and W-9 available on request. +

+ +
+
+
+ + {/* --- Source breakdown table --- */} +
+

+ Where the {usd(data.total_monthly)} comes from +

+

+ Monthly recurring equivalent, by source. Annual sponsorships are + normalized to a monthly figure. +

+ +
+ + + + + + + + + + + {sources.map((s) => ( + + + + + + + ))} + + + + + + + +
Source + Monthly + + Sponsors +
+ + {s.name} + + + {usd(s.amount)} + + {s.sponsors != null ? s.sponsors : "—"} +
Total + {usd(data.total_monthly)} + + — +
+
+
+ + {/* --- Featured organizations --- */} +
+ +

+ Companies whose support keeps DDEV maintained. Want your logo here? + Sponsor at the major tier (≥ $200/month on GitHub Sponsors, or contact + us for invoicing). +

+ +
+ + {/* --- Bubble grid of all sponsors --- */} +
+

+ All current sponsors +

+

+ Every account currently sponsoring DDEV on GitHub, plus our featured + organizations. +

+ +
+ + {/* --- Closing CTAs + foundation note --- */} +
+
+
+ + +
+ +
+ +

+ The DDEV Foundation is a Colorado, USA 501(c)(3) non-profit. See{" "} + + DDEV Foundation + {" "} + for board, financials, and tax status. +

+
+
+
diff --git a/src/pages/support-ddev.astro b/src/pages/support-ddev.astro index 45b12751..ad683c6f 100644 --- a/src/pages/support-ddev.astro +++ b/src/pages/support-ddev.astro @@ -17,6 +17,18 @@ const title = `Support DDEV`
+
+

+ Looking to sponsor financially? + See ddev.com/sponsor + for the three sponsorship channels (GitHub, PayPal, invoice), a live + breakdown of where the monthly recurring total comes from, and the + current progress toward our $12,000/month goal. This page covers the + rest: community contributions, spreading the word, and other ways to + help. +

+
+

@@ -27,7 +39,7 @@ const title = `Support DDEV`

Ready to invest in DDEV's future?

    -
  • 🎯 Financial Support
  • +
  • 🎯 Financial Support (Sponsor page)
  • 🤝 Community Contributions
  • 📢 Spread the Word
  • 🌟 Ready to Make a Difference?
  • From c2c81ef43bfe64ec0f6419045a823ab38d6936cc Mon Sep 17 00:00:00 2001 From: Joseph Steele <129185788+js360000@users.noreply.github.com> Date: Sun, 17 May 2026 19:09:59 +0100 Subject: [PATCH 2/2] feat(sponsor): add top-N monthly sponsors section + monthlyEquivalent field Closes requirement #4 from issue #631 ("top 10 list of largest monthly recurring sponsors with clickable logos and incentive copy") using the smallest practical extension to the existing hand-curated data. - Add optional `monthlyEquivalent: number` field to featured-sponsors.json entries. Populated for the four current major-tier entries (Upsun, Tag1, i-gelb, IAS) at the $200/month major-tier threshold. - New TopMonthlySponsors.astro component: filters featured-sponsors.json to entries with a confirmed amount, sorts descending, renders top-N with clickable logos + rank + amount. limit defaults to 10. - New "Top monthly sponsors" section on /sponsor, placed above the existing "Featured organizations" grid. The full grid and the all-sponsors bubble grid below it remain unchanged. The component lists only entries with monthlyEquivalent set so the page never shows fabricated amounts; the list grows naturally as the field is populated. A note links readers to featured-sponsors.json so anyone (including sponsors themselves) can submit confirmed amounts via PR. --- src/components/TopMonthlySponsors.astro | 83 +++++++++++++++++++++++++ src/featured-sponsors.json | 12 ++-- src/pages/sponsor.astro | 25 ++++++++ 3 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 src/components/TopMonthlySponsors.astro diff --git a/src/components/TopMonthlySponsors.astro b/src/components/TopMonthlySponsors.astro new file mode 100644 index 00000000..4086d8b0 --- /dev/null +++ b/src/components/TopMonthlySponsors.astro @@ -0,0 +1,83 @@ +--- +import featuredSponsors from "../featured-sponsors.json" + +/** + * Top-N featured organizations by tracked monthly contribution. + * + * Data comes from `src/featured-sponsors.json`. Entries that have an + * explicit `monthlyEquivalent` field are sorted descending and included + * in this list. Entries without that field are not shown (they still + * render in `` and in the all-sponsors bubble grid). + * + * The set of tracked entries will grow as `monthlyEquivalent` values are + * filled in. The current floor is $200/month, which matches the major-tier + * GitHub Sponsors threshold. + */ +type Sponsor = { + name: string + type: string + logo: string + darklogo: string + squareLogo: string + url: string + github?: string + isLeading?: boolean + monthlyEquivalent?: number +} + +const sponsors = featuredSponsors as Sponsor[] + +interface Props { + /** Maximum number of sponsors to show. Defaults to 10. */ + limit?: number +} + +const { limit = 10 } = Astro.props as Props + +const tracked = sponsors + .filter((s): s is Sponsor & { monthlyEquivalent: number } => + typeof s.monthlyEquivalent === "number" && s.monthlyEquivalent > 0, + ) + .slice() + .sort((a, b) => b.monthlyEquivalent - a.monthlyEquivalent) + .slice(0, limit) + +const usd = (n: number) => + `$${n.toLocaleString("en-US", { maximumFractionDigits: 0 })}` +--- + +{tracked.length > 0 && ( + +)} diff --git a/src/featured-sponsors.json b/src/featured-sponsors.json index 7c510909..5ff8d71c 100644 --- a/src/featured-sponsors.json +++ b/src/featured-sponsors.json @@ -6,7 +6,8 @@ "darklogo": "/logos/upsun-darkmode.svg", "squareLogo": "/logos/upsun-square.svg", "url": "https://upsun.com", - "isLeading": false + "isLeading": false, + "monthlyEquivalent": 200 }, { "name": "Tag1", @@ -15,7 +16,8 @@ "darklogo": "/logos/dark-tag1.svg", "squareLogo": "/logos/tag1-square.svg", "url": "https://tag1.com", - "isLeading": false + "isLeading": false, + "monthlyEquivalent": 200 }, { "name": "i-gelb", @@ -25,7 +27,8 @@ "squareLogo": "/logos/i-gelb-square.svg", "url": "https://i-gelb.net", "github": "i-gelb", - "isLeading": false + "isLeading": false, + "monthlyEquivalent": 200 }, { "name": "Institute for Advanced Study", @@ -34,7 +37,8 @@ "darklogo": "/logos/ias-dark.svg", "squareLogo": "/logos/ias-square.svg", "url": "https://www.ias.edu/", - "isLeading": false + "isLeading": false, + "monthlyEquivalent": 200 }, { "name": "b13", diff --git a/src/pages/sponsor.astro b/src/pages/sponsor.astro index 650ebbff..cb14ee63 100644 --- a/src/pages/sponsor.astro +++ b/src/pages/sponsor.astro @@ -3,6 +3,7 @@ import Layout from "../layouts/Layout.astro" import Heading from "../components/Heading.astro" import CtaButton from "../components/CtaButton.astro" import FeaturedSponsors from "../components/FeaturedSponsors.astro" +import TopMonthlySponsors from "../components/TopMonthlySponsors.astro" import Sponsors from "../components/Sponsors.astro" const title = `Sponsor DDEV` @@ -353,6 +354,30 @@ const sources = [
+ {/* --- Top monthly sponsors (tracked) --- */} +
+

+ Top monthly sponsors +

+

+ Featured organizations sorted by tracked monthly contribution. Only + entries with a confirmed amount in + + featured-sponsors.json + + are listed here; the full set is below. +

+ +
+ {/* --- Featured organizations --- */}