From 79594754602986cd470a970b49ee44887a0f1507 Mon Sep 17 00:00:00 2001 From: Aviv Keller Date: Fri, 5 Jun 2026 18:14:40 -0400 Subject: [PATCH 1/8] feat(pages): add sponsors --- components/Icons/Diamond.jsx | 30 + components/Layout.jsx | 2 + components/SectionHeader/index.jsx | 28 + components/SectionHeader/index.module.css | 17 + components/Sponsors/BackerWall/index.jsx | 55 + .../Sponsors/BackerWall/index.module.css | 9 + components/Sponsors/Card/index.jsx | 86 ++ components/Sponsors/Card/index.module.css | 55 + components/Sponsors/PricingCard/index.jsx | 38 + .../Sponsors/PricingCard/index.module.css | 41 + components/Sponsors/SortToggle/index.jsx | 31 + .../Sponsors/SortToggle/index.module.css | 5 + components/Sponsors/Tier/index.jsx | 60 + components/Sponsors/Tier/index.module.css | 58 + generated/sponsors.json | 1282 +++++++++++++++++ layouts/Sponsors/index.jsx | 129 ++ layouts/Sponsors/index.module.css | 41 + package-lock.json | 1 + package.json | 5 +- pages/about/sponsors.md | 3 + pages/site.json | 4 + scripts/html/doc-kit.config.mjs | 1 + scripts/prepare/sponsors.mjs | 120 ++ 23 files changed, 2100 insertions(+), 1 deletion(-) create mode 100644 components/Icons/Diamond.jsx create mode 100644 components/SectionHeader/index.jsx create mode 100644 components/SectionHeader/index.module.css create mode 100644 components/Sponsors/BackerWall/index.jsx create mode 100644 components/Sponsors/BackerWall/index.module.css create mode 100644 components/Sponsors/Card/index.jsx create mode 100644 components/Sponsors/Card/index.module.css create mode 100644 components/Sponsors/PricingCard/index.jsx create mode 100644 components/Sponsors/PricingCard/index.module.css create mode 100644 components/Sponsors/SortToggle/index.jsx create mode 100644 components/Sponsors/SortToggle/index.module.css create mode 100644 components/Sponsors/Tier/index.jsx create mode 100644 components/Sponsors/Tier/index.module.css create mode 100644 generated/sponsors.json create mode 100644 layouts/Sponsors/index.jsx create mode 100644 layouts/Sponsors/index.module.css create mode 100644 pages/about/sponsors.md create mode 100644 scripts/prepare/sponsors.mjs diff --git a/components/Icons/Diamond.jsx b/components/Icons/Diamond.jsx new file mode 100644 index 0000000..740dd21 --- /dev/null +++ b/components/Icons/Diamond.jsx @@ -0,0 +1,30 @@ +/** + * Small diamond/gem glyph used to mark a sponsorship tier. + * + * @param {import('react').SVGProps} props + */ +export default function DiamondIcon(props) { + return ( + + ); +} diff --git a/components/Layout.jsx b/components/Layout.jsx index 0e14468..66c1b1c 100644 --- a/components/Layout.jsx +++ b/components/Layout.jsx @@ -1,9 +1,11 @@ import DefaultLayout from '@node-core/doc-kit/src/generators/web/ui/components/Layout/index.jsx'; import HomeLayout from '../layouts/Home/index.jsx'; +import SponsorsLayout from '../layouts/Sponsors/index.jsx'; import '../styles/index.css'; const LAYOUTS = { home: HomeLayout, + sponsors: SponsorsLayout, }; export default function Layout(props) { diff --git a/components/SectionHeader/index.jsx b/components/SectionHeader/index.jsx new file mode 100644 index 0000000..ed7d71e --- /dev/null +++ b/components/SectionHeader/index.jsx @@ -0,0 +1,28 @@ +import classNames from 'classnames'; + +import styles from './index.module.css'; + +/** + * Centered section heading: an uppercase eyebrow, a title and an optional description. + * + * @param {import('react').ComponentProps<'header'> & { + * eyebrow?: import('react').ReactNode, + * title: import('react').ReactNode, + * description?: import('react').ReactNode, + * }} props + */ +export default function SectionHeader({ + eyebrow, + title, + description, + className, + ...props +}) { + return ( +
+ {eyebrow &&

{eyebrow}

} +

{title}

+ {description &&

{description}

} +
+ ); +} diff --git a/components/SectionHeader/index.module.css b/components/SectionHeader/index.module.css new file mode 100644 index 0000000..f71895e --- /dev/null +++ b/components/SectionHeader/index.module.css @@ -0,0 +1,17 @@ +@reference "../../styles/index.css"; + +.header { + @apply mx-auto flex max-w-2xl flex-col items-center gap-3 text-center; +} + +.eyebrow { + @apply m-0 text-xs font-semibold tracking-[0.12em] uppercase text-blue-600; +} + +.title { + @apply m-0 text-3xl font-bold tracking-tight text-neutral-900 sm:text-4xl; +} + +.description { + @apply m-0 text-base leading-relaxed text-neutral-600; +} diff --git a/components/Sponsors/BackerWall/index.jsx b/components/Sponsors/BackerWall/index.jsx new file mode 100644 index 0000000..e181899 --- /dev/null +++ b/components/Sponsors/BackerWall/index.jsx @@ -0,0 +1,55 @@ +import AvatarGroup from '@node-core/ui-components/Common/AvatarGroup'; +import classNames from 'classnames'; + +import styles from './index.module.css'; + +const OC_BASE = 'https://opencollective.com'; + +const initialsOf = name => + name + .split(/\s+/) + .slice(0, 2) + .map(word => word[0]) + .join('') + .toUpperCase(); + +/** + * Wall of individual backer avatars with an overflow chip, plus a link to the full list + * on Open Collective. Built on the shared {@link AvatarGroup}. + * + * @param {import('react').ComponentProps<'div'> & { + * backers: Array<{ name: string, slug: string, imageUrl: string|null, allTime: number }>, + * total: number, + * limit?: number, + * }} props + */ +export default function BackerWall({ + backers, + total, + limit = 48, + className, + ...props +}) { + const avatars = backers.map(backer => ({ + image: backer.imageUrl ?? undefined, + name: backer.name, + nickname: backer.slug, + fallback: initialsOf(backer.name), + url: `${OC_BASE}/${backer.slug}`, + })); + + return ( +
+ {CLIENT && } + + See all {total.toLocaleString('en-US')} backers on Open Collective + → + +
+ ); +} diff --git a/components/Sponsors/BackerWall/index.module.css b/components/Sponsors/BackerWall/index.module.css new file mode 100644 index 0000000..dd85e59 --- /dev/null +++ b/components/Sponsors/BackerWall/index.module.css @@ -0,0 +1,9 @@ +@reference "../../../styles/index.css"; + +.wall { + @apply flex flex-col items-center gap-6; +} + +.link { + @apply text-sm font-medium text-blue-600 no-underline hover:text-blue-700; +} diff --git a/components/Sponsors/Card/index.jsx b/components/Sponsors/Card/index.jsx new file mode 100644 index 0000000..ad27c50 --- /dev/null +++ b/components/Sponsors/Card/index.jsx @@ -0,0 +1,86 @@ +import classNames from 'classnames'; +import Avatar from '@node-core/ui-components/Common/AvatarGroup/Avatar'; + +import styles from './index.module.css'; + +const formatUSD = value => `$${Math.round(value).toLocaleString('en-US')}`; + +const amountLabel = (sponsor, metric) => + metric === 'monthly' + ? `${formatUSD(sponsor.monthly)} / mo` + : `${formatUSD(sponsor.allTime)} total`; + +/** + * A single sponsor tile. The visual weight scales with `size` so higher tiers read larger, + * matching the sponsor wall in the design. `lg` renders an expanded card (used by the top + * tier); the smaller sizes render compact rows. + * + * @param {import('react').ComponentProps<'a'> & { + * sponsor: { name: string, slug: string, imageUrl: string|null, url: string, monthly: number, allTime: number, tier: string, description?: string }, + * size?: 'lg'|'md'|'sm'|'xs', + * metric?: 'monthly'|'allTime', + * }} props + */ +export default function SponsorCard({ + sponsor, + size = 'md', + metric = 'monthly', + className, + ...props +}) { + const linkProps = { + href: sponsor.url, + target: '_blank', + rel: 'noreferrer noopener', + ...props, + }; + + if (size === 'lg') { + return ( + + {CLIENT && ( + + )} + {sponsor.name} + {sponsor.description && ( +

{sponsor.description}

+ )} +
+ {amountLabel(sponsor, metric)} + Visit → +
+
+ ); + } + + return ( + +
+ {CLIENT && ( + + )} +
+ + {sponsor.name} + {size !== 'xs' && ( + {amountLabel(sponsor, metric)} + )} + + {size !== 'xs' && ( + + )} +
+ ); +} diff --git a/components/Sponsors/Card/index.module.css b/components/Sponsors/Card/index.module.css new file mode 100644 index 0000000..2d600a3 --- /dev/null +++ b/components/Sponsors/Card/index.module.css @@ -0,0 +1,55 @@ +@reference "../../../styles/index.css"; + +.card { + @apply flex rounded-xl border border-neutral-200 bg-white no-underline transition-colors duration-150 hover:border-blue-300 hover:bg-blue-50/40; +} + +/* Expanded card used by the top tier. */ +.lg { + @apply flex-col items-start gap-3 p-5; +} + +/* Compact rows for the lower tiers. */ +.md { + @apply items-center gap-3 p-3.5; +} + +.sm { + @apply items-center gap-2.5 p-3; +} + +.xs { + @apply items-center gap-2 p-2.5; +} + +.body { + @apply flex min-w-0 flex-col!; +} + +.name { + @apply truncate text-sm font-semibold text-neutral-900; +} + +.lg .name { + @apply text-base; +} + +.amount { + @apply text-xs text-neutral-500; +} + +.description { + @apply m-0 text-sm leading-relaxed text-neutral-600; +} + +.footer { + @apply mt-auto flex w-full items-center justify-between pt-1; +} + +.visit { + @apply text-sm font-medium text-blue-600; +} + +.chevron { + @apply ml-auto text-xl leading-none text-neutral-300; +} diff --git a/components/Sponsors/PricingCard/index.jsx b/components/Sponsors/PricingCard/index.jsx new file mode 100644 index 0000000..7d9a97e --- /dev/null +++ b/components/Sponsors/PricingCard/index.jsx @@ -0,0 +1,38 @@ +import classNames from 'classnames'; + +import DiamondIcon from '../../Icons/Diamond.jsx'; + +import styles from './index.module.css'; + +/** + * A single sponsorship-tier price tile used in the CTA band. + * + * @param {import('react').ComponentProps<'div'> & { + * tier: string, + * label: string, + * price: string, + * interval: string, + * description: string, + * }} props + */ +export default function PricingCard({ + tier, + label, + price, + interval, + description, + className, + ...props +}) { + return ( +
+ + {label} +

+ {price} + {interval} +

+

{description}

+
+ ); +} diff --git a/components/Sponsors/PricingCard/index.module.css b/components/Sponsors/PricingCard/index.module.css new file mode 100644 index 0000000..9deb508 --- /dev/null +++ b/components/Sponsors/PricingCard/index.module.css @@ -0,0 +1,41 @@ +@reference "../../../styles/index.css"; + +.card { + @apply flex flex-col gap-1.5 rounded-xl border border-white/10 bg-white/5 p-5; +} + +.icon { + @apply text-xl; +} + +.platinum { + @apply text-blue-300; +} + +.gold { + @apply text-amber-400; +} + +.silver { + @apply text-neutral-300; +} + +.bronze { + @apply text-orange-400; +} + +.label { + @apply text-xs font-semibold tracking-[0.08em] uppercase text-neutral-400; +} + +.price { + @apply m-0 text-2xl font-bold text-white; +} + +.interval { + @apply ml-1 text-sm font-normal text-neutral-400; +} + +.description { + @apply m-0 text-sm leading-relaxed text-neutral-400; +} diff --git a/components/Sponsors/SortToggle/index.jsx b/components/Sponsors/SortToggle/index.jsx new file mode 100644 index 0000000..1a7521d --- /dev/null +++ b/components/Sponsors/SortToggle/index.jsx @@ -0,0 +1,31 @@ +import Tabs from '@node-core/ui-components/Common/Tabs'; +import classNames from 'classnames'; + +import styles from './index.module.css'; + +const TABS = [ + { key: 'monthly', label: 'Sort by Monthly' }, + { key: 'allTime', label: 'Sort by All-Time' }, +]; + +/** + * Controlled segmented control that picks the metric used to rank sponsors. Built on the + * shared {@link Tabs} primitive. + * + * @param {import('react').ComponentProps & { + * value: 'monthly'|'allTime', + * onChange: (value: 'monthly'|'allTime') => void, + * }} props + */ +export default function SortToggle({ value, onChange, className, ...props }) { + return ( + + ); +} diff --git a/components/Sponsors/SortToggle/index.module.css b/components/Sponsors/SortToggle/index.module.css new file mode 100644 index 0000000..a9a7d09 --- /dev/null +++ b/components/Sponsors/SortToggle/index.module.css @@ -0,0 +1,5 @@ +@reference "../../../styles/index.css"; + +.toggle { + @apply inline-flex; +} diff --git a/components/Sponsors/Tier/index.jsx b/components/Sponsors/Tier/index.jsx new file mode 100644 index 0000000..c034901 --- /dev/null +++ b/components/Sponsors/Tier/index.jsx @@ -0,0 +1,60 @@ +import classNames from 'classnames'; + +import Badge from '@node-core/ui-components/Common/Badge'; + +import DiamondIcon from '../../Icons/Diamond.jsx'; +import SponsorCard from '../Card/index.jsx'; + +import styles from './index.module.css'; + +/** + * One sponsorship tier: a labelled header (icon, name, count, price) above a responsive + * grid of {@link SponsorCard}s. Renders nothing when the tier has no sponsors. + * + * @param {import('react').ComponentProps<'section'> & { + * tier: string, + * label: string, + * price: string, + * cardSize: 'lg'|'md'|'sm'|'xs', + * sponsors: Array, + * metric: 'monthly'|'allTime', + * }} props + */ +export default function SponsorTier({ + tier, + label, + price, + cardSize, + sponsors, + metric, + className, + ...props +}) { + if (!sponsors.length) return null; + + return ( +
+
+ + + {label} + + + {String(sponsors.length)} + + {price} +
+ +
+ {sponsors.map(sponsor => ( + + ))} +
+
+ ); +} diff --git a/components/Sponsors/Tier/index.module.css b/components/Sponsors/Tier/index.module.css new file mode 100644 index 0000000..fe99259 --- /dev/null +++ b/components/Sponsors/Tier/index.module.css @@ -0,0 +1,58 @@ +@reference "../../../styles/index.css"; + +.tier { + @apply flex flex-col gap-4; +} + +.header { + @apply flex items-center gap-3 border-b border-neutral-200 pb-3; +} + +.label { + @apply inline-flex items-center gap-2 text-base font-semibold text-neutral-900; +} + +.icon { + @apply text-lg; +} + +.price { + @apply ml-auto text-sm text-neutral-500; +} + +/* Tier accent colors for the diamond glyph. */ +.platinum .icon { + @apply text-blue-400; +} + +.gold .icon { + @apply text-amber-500; +} + +.silver .icon { + @apply text-neutral-400; +} + +.bronze .icon { + @apply text-orange-700; +} + +.grid { + @apply grid grid-cols-1 gap-4; +} + +.grid-platinum { + @apply sm:grid-cols-2; +} + +.grid-gold { + @apply sm:grid-cols-2 lg:grid-cols-3; +} + +.grid-silver { + @apply grid-cols-2 sm:grid-cols-3 lg:grid-cols-4; +} + +.grid-bronze { + @apply grid-cols-2 gap-3 sm:grid-cols-3 lg:grid-cols-6; +} diff --git a/generated/sponsors.json b/generated/sponsors.json new file mode 100644 index 0000000..f90e628 --- /dev/null +++ b/generated/sponsors.json @@ -0,0 +1,1282 @@ +{ + "sponsors": [ + { + "name": "AG Grid", + "slug": "ag-grid", + "imageUrl": "https://images.opencollective.com/ag-grid/bec0580/logo.png", + "url": "https://www.ag-grid.com/", + "monthly": 2500, + "allTime": 265000, + "tier": "platinum" + }, + { + "name": "Airbnb", + "slug": "airbnb", + "imageUrl": "https://images.opencollective.com/airbnb/d327d66/logo.png", + "url": "https://www.airbnb.com/", + "monthly": 200, + "allTime": 69670, + "tier": "silver" + }, + { + "name": "Elastic", + "slug": "elastic", + "imageUrl": "https://images.opencollective.com/elastic/dce5929/logo.png", + "url": "https://elastic.co/", + "monthly": 200, + "allTime": 16000, + "tier": "silver" + }, + { + "name": "Turtlebet - Nettikasinot", + "slug": "turtlebet-nettikasinot", + "imageUrl": "https://images.opencollective.com/turtlebet-nettikasinot/d4e220c/logo.png", + "url": "https://www.turtlebet.com/fi/kaikki-nettikasinot.html", + "monthly": 200, + "allTime": 12840, + "tier": "silver" + }, + { + "name": "Parimatch India", + "slug": "seo-pmi", + "imageUrl": "https://images.opencollective.com/seo-pmi/768ffc5/avatar.png", + "url": "https://parimatch.in/", + "monthly": 200, + "allTime": 10600, + "tier": "silver" + }, + { + "name": "Blastup", + "slug": "blastupcom", + "imageUrl": "https://images.opencollective.com/blastupcom/0551801/logo.png", + "url": "https://blastup.com", + "monthly": 200, + "allTime": 5610, + "tier": "silver" + }, + { + "name": "Buy Instagram Followers Twicsy", + "slug": "buy-instagram-followers-twicsy", + "imageUrl": "https://images.opencollective.com/buy-instagram-followers-twicsy/b4c5d7f/logo.png", + "url": "https://twicsy.com/buy-instagram-followers", + "monthly": 200, + "allTime": 4530, + "tier": "silver" + }, + { + "name": "luotettavat suomalaiset nettikasinot", + "slug": "luotettavat-suomalaiset-nettikasinot", + "imageUrl": "https://images.opencollective.com/luotettavat-suomalaiset-nettikasinot/95e37c7/avatar.png", + "url": "https://pomus.net/", + "monthly": 200, + "allTime": 4400, + "tier": "silver" + }, + { + "name": "Casivo.se", + "slug": "casivo-sverige", + "imageUrl": "https://images.opencollective.com/casivo-sverige/782a747/avatar.png", + "url": "https://www.casivo.se/", + "monthly": 200, + "allTime": 4234, + "tier": "silver" + }, + { + "name": "Buzzoid - Buy Instagram Followers", + "slug": "buzzoid-buy-instagram-followers", + "imageUrl": "https://images.opencollective.com/buzzoid-buy-instagram-followers/56a09fe/logo.png", + "url": "https://buzzoid.com/buy-instagram-followers/", + "monthly": 200, + "allTime": 4000, + "tier": "silver" + }, + { + "name": "c19.cl - Mejores Casinos Online", + "slug": "casino-online-chile", + "imageUrl": "https://images.opencollective.com/casino-online-chile/91f1057/avatar.png", + "url": "https://www.c19.cl/", + "monthly": 200, + "allTime": 3800, + "tier": "silver" + }, + { + "name": "SocialBoosting", + "slug": "socialboosting", + "imageUrl": "https://images.opencollective.com/socialboosting/7b6b775/logo.png", + "url": "https://www.socialboosting.com/", + "monthly": 200, + "allTime": 3000, + "tier": "silver" + }, + { + "name": "Ausländische Online Casinos Schweiz", + "slug": "auslandische-online-casinos-schweiz", + "imageUrl": "https://images.opencollective.com/auslandische-online-casinos-schweiz/091532c/avatar.png", + "url": "https://www.palace-luzern.ch/", + "monthly": 200, + "allTime": 2800, + "tier": "silver" + }, + { + "name": "casino utan svensk licens", + "slug": "casinonutansvensklicensorg", + "imageUrl": "https://images.opencollective.com/casinonutansvensklicensorg/71b8d8b/avatar.png", + "url": "https://popmani.se/", + "monthly": 200, + "allTime": 2400, + "tier": "silver" + }, + { + "name": "Wolf Winner Casino", + "slug": "wolf-winner-casino", + "imageUrl": "https://images.opencollective.com/wolf-winner-casino/d3c423b/avatar.png", + "url": "https://www.wolfwinner.fun/en", + "monthly": 200, + "allTime": 2200, + "tier": "silver" + }, + { + "name": "AUCrazyVegas", + "slug": "aucrazyvegas1", + "imageUrl": "https://images.opencollective.com/aucrazyvegas1/960fd37/avatar.png", + "url": "https://au.crazyvegas.com/", + "monthly": 200, + "allTime": 2200, + "tier": "silver" + }, + { + "name": "Releaf – Medizinischer Cannabis Shop", + "slug": "releaf-medizinischer-cannabis-shop", + "imageUrl": "https://images.opencollective.com/releaf-medizinischer-cannabis-shop/7b8d533/logo.png", + "url": "https://releaf.com/de", + "monthly": 200, + "allTime": 2000, + "tier": "silver" + }, + { + "name": "Daman Game", + "slug": "daman-game-login", + "imageUrl": "https://images.opencollective.com/daman-game-login/86bac66/logo.png", + "url": "https://damanapp.download/", + "monthly": 200, + "allTime": 1400, + "tier": "silver" + }, + { + "name": "Fun88", + "slug": "fun88-thailand", + "imageUrl": "https://images.opencollective.com/fun88-thailand/81e1909/avatar.png", + "url": "https://www.fun88thf.com/th/", + "monthly": 200, + "allTime": 1028, + "tier": "silver" + }, + { + "name": "Fun88", + "slug": "fun88-vietnam", + "imageUrl": "https://images.opencollective.com/fun88-vietnam/192b9db/avatar.png", + "url": "https://www.fun88fp.com/vn/", + "monthly": 200, + "allTime": 1022, + "tier": "silver" + }, + { + "name": "Fun88", + "slug": "fun88-official", + "imageUrl": "https://images.opencollective.com/fun88-official/bf2843c/logo.png", + "url": "https://global.fun88.com/", + "monthly": 200, + "allTime": 1020, + "tier": "silver" + }, + { + "name": "utländska casino 10 euro", + "slug": "utlandska-casino-10-euro", + "imageUrl": "https://images.opencollective.com/utlandska-casino-10-euro/92f3262/avatar.png", + "url": "https://casinobonusutaninsattning.net/10-euro-casino-utan-licens/", + "monthly": 200, + "allTime": 1000, + "tier": "silver" + }, + { + "name": "casino utan svensk licens 10 euro", + "slug": "casino-utan-svensk-licens-10-euro", + "imageUrl": "https://images.opencollective.com/casino-utan-svensk-licens-10-euro/099c920/logo.png", + "url": "https://casino-utan-svensk-licens.info/casino-utan-svensk-licens-10-euro/", + "monthly": 200, + "allTime": 1000, + "tier": "silver" + }, + { + "name": "TopKasynoOnline PL", + "slug": "topkasynoonline-pl", + "imageUrl": "https://images.opencollective.com/topkasynoonline-pl/714b70a/logo.png", + "url": "https://pl.topkasynoonline.com/", + "monthly": 200, + "allTime": 800, + "tier": "silver" + }, + { + "name": "Czech Casinos", + "slug": "czech-casinos", + "imageUrl": "https://images.opencollective.com/czech-casinos/90c12ce/logo.png", + "url": "https://czech-casinos.com/", + "monthly": 200, + "allTime": 800, + "tier": "silver" + }, + { + "name": "Laser Book 247", + "slug": "laserbook247", + "imageUrl": "https://images.opencollective.com/laserbook247/92c8628/avatar.png", + "url": "https://laser247.uk.com/", + "monthly": 200, + "allTime": 600, + "tier": "silver" + }, + { + "name": "Paras pikakasino", + "slug": "uusi-pikakasino", + "imageUrl": "https://images.opencollective.com/uusi-pikakasino/6c68161/logo.png", + "url": "https://kasinokurko.com/pikakasinot/", + "monthly": 200, + "allTime": 600, + "tier": "silver" + }, + { + "name": "Appfolio", + "slug": "appfolio", + "imageUrl": "https://images.opencollective.com/appfolio/1f45aab/avatar.png", + "url": "https://engineering.appfolio.com", + "monthly": 100, + "allTime": 10900, + "tier": "silver" + }, + { + "name": "WonderProxy", + "slug": "wonderproxy", + "imageUrl": "https://images.opencollective.com/wonderproxy/60a7cec/logo.png", + "url": "https://wonderproxy.com", + "monthly": 100, + "allTime": 9100, + "tier": "silver" + }, + { + "name": "RelativeCI", + "slug": "relative_ci", + "imageUrl": "https://images.opencollective.com/relative_ci/41ed8b8/logo.png", + "url": "https://relative-ci.com/", + "monthly": 50, + "allTime": 2630, + "tier": "bronze" + }, + { + "name": "GitBook", + "slug": "gitbook", + "imageUrl": "https://images.opencollective.com/gitbook/820419f/logo.png", + "url": "https://www.gitbook.com", + "monthly": 50, + "allTime": 1245, + "tier": "bronze" + }, + { + "name": "Fast payout casinos Canada", + "slug": "casinos-canada", + "imageUrl": "https://images.opencollective.com/casinos-canada/51b54e4/logo.png", + "url": "https://oercongress.org/", + "monthly": 50, + "allTime": 250, + "tier": "bronze" + }, + { + "name": "Stiltsoft", + "slug": "stiltsoftdevelopment", + "imageUrl": "https://images.opencollective.com/stiltsoftdevelopment/4b2eb11/logo.png", + "url": "https://stiltsoft.com", + "monthly": 25, + "allTime": 4675, + "tier": "bronze" + }, + { + "name": "DontPayFull", + "slug": "dontpayfull", + "imageUrl": "https://images.opencollective.com/dontpayfull/45766da/logo.png", + "url": "https://www.dontpayfull.com", + "monthly": 25, + "allTime": 1600, + "tier": "bronze" + }, + { + "name": "Social Followers", + "slug": "social-followers", + "imageUrl": "https://images.opencollective.com/social-followers/54d38ae/logo.png", + "url": "https://www.socialfollowers.uk/buy-tiktok-followers/", + "monthly": 25, + "allTime": 525, + "tier": "bronze" + }, + { + "name": "French Casinos", + "slug": "french-casinos", + "imageUrl": "https://images.opencollective.com/french-casinos/118fc20/logo.png", + "url": "https://french-casinos.com/", + "monthly": 25, + "allTime": 75, + "tier": "bronze" + }, + { + "name": "WestAce", + "slug": "westace", + "imageUrl": "https://images.opencollective.com/westace/b707f47/logo.png", + "url": "https://west-ace.com/", + "monthly": 25, + "allTime": 75, + "tier": "bronze" + }, + { + "name": "Mafia Casino", + "slug": "mafia-casino", + "imageUrl": "https://images.opencollective.com/mafia-casino/f69acf5/logo.png", + "url": "https://mafia-casino.com/", + "monthly": 25, + "allTime": 75, + "tier": "bronze" + }, + { + "name": "Casinos UK", + "slug": "onlinecasinos-ebb6bb41", + "imageUrl": "https://images.opencollective.com/onlinecasinos-ebb6bb41/avatar.png", + "url": "https://casinos-uk.com/", + "monthly": 25, + "allTime": 75, + "tier": "bronze" + }, + { + "name": "Casino Godz", + "slug": "casino-godz", + "imageUrl": "https://images.opencollective.com/casino-godz/1a7ebff/logo.png", + "url": "https://casino-godz.com/", + "monthly": 25, + "allTime": 25, + "tier": "bronze" + }, + { + "name": "Mesh Payments", + "slug": "meshpayments", + "imageUrl": "https://images.opencollective.com/meshpayments/87e9336/logo.png", + "url": "https://meshpayments.com/", + "monthly": 20, + "allTime": 1180, + "tier": "bronze" + }, + { + "name": "Barbados Bingo", + "slug": "barbados-bingo", + "imageUrl": "https://images.opencollective.com/barbados-bingo/f536a61/logo.png", + "url": "https://www.barbadosbingo.com", + "monthly": 20, + "allTime": 1080, + "tier": "bronze" + }, + { + "name": "Abed Elezz", + "slug": "abed-elezz", + "imageUrl": "https://images.opencollective.com/abed-elezz/avatar.png", + "url": "https://opencollective.com/abed-elezz", + "monthly": 20, + "allTime": 580, + "tier": "bronze" + }, + { + "name": "Automatio AI", + "slug": "kinder", + "imageUrl": "https://images.opencollective.com/kinder/cd0b9b4/avatar.png", + "url": "https://automatio.ai/", + "monthly": 15, + "allTime": 499.33, + "tier": "bronze" + }, + { + "name": "Fun88 Vietnam EN", + "slug": "fun88-vietnam-en", + "imageUrl": "https://images.opencollective.com/fun88-vietnam-en/2541290/avatar.png", + "url": "https://www.fun88vnplay.com/", + "monthly": 15, + "allTime": 265, + "tier": "bronze" + }, + { + "name": "FUN88 Thailand EN", + "slug": "fun88-thailand-en", + "imageUrl": "https://images.opencollective.com/fun88-thailand-en/cacc530/avatar.png", + "url": "https://www.fun88asiath.com/", + "monthly": 15, + "allTime": 265, + "tier": "bronze" + }, + { + "name": "Icons8", + "slug": "icons8", + "imageUrl": "https://images.opencollective.com/icons8/7fa1641/logo.png", + "url": "https://icons8.com/", + "monthly": 10, + "allTime": 17128, + "tier": "bronze" + }, + { + "name": "TaopaiC Tao", + "slug": "taopaic", + "imageUrl": "https://images.opencollective.com/taopaic/c630b5d/avatar.png", + "url": "https://opencollective.com/taopaic", + "monthly": 10, + "allTime": 1090, + "tier": "bronze" + }, + { + "name": "Waiterio LLC", + "slug": "waiterio", + "imageUrl": "https://images.opencollective.com/waiterio/b574fc9/logo.png", + "url": "https://www.waiterio.com", + "monthly": 10, + "allTime": 980, + "tier": "bronze" + }, + { + "name": "Posit", + "slug": "rstudio", + "imageUrl": "https://images.opencollective.com/rstudio/6609cf1/logo.png", + "url": "https://posit.co/", + "monthly": 10, + "allTime": 750, + "tier": "bronze" + }, + { + "name": "Extremely Heavy", + "slug": "xh", + "imageUrl": "https://images.opencollective.com/xh/cec0963/logo.png", + "url": "https://xh.io", + "monthly": 10, + "allTime": 650, + "tier": "bronze" + }, + { + "name": "Raider.IO", + "slug": "raiderio_wow", + "imageUrl": "https://images.opencollective.com/raiderio_wow/6935515/logo.png", + "url": "https://raider.io", + "monthly": 10, + "allTime": 650, + "tier": "bronze" + }, + { + "name": "Correct Casinos", + "slug": "correct-casinos", + "imageUrl": "https://images.opencollective.com/correct-casinos/7cce33a/avatar.png", + "url": "https://www.correctcasinos.com/", + "monthly": 10, + "allTime": 520, + "tier": "bronze" + }, + { + "name": "MFB Technologies", + "slug": "mfbtech", + "imageUrl": "https://images.opencollective.com/mfbtech/3c62a0c/logo.png", + "url": "https://mfbtech.com", + "monthly": 10, + "allTime": 510, + "tier": "bronze" + }, + { + "name": "Metoree", + "slug": "metoree", + "imageUrl": "https://images.opencollective.com/metoree/8e6ec3c/avatar.png", + "url": "https://us.metoree.com/", + "monthly": 10, + "allTime": 385, + "tier": "bronze" + }, + { + "name": "Casino Australia Online", + "slug": "casino-australia-online", + "imageUrl": "https://images.opencollective.com/casino-australia-online/eac6102/logo.png", + "url": "https://www.casinoaustraliaonline.net/", + "monthly": 10, + "allTime": 380, + "tier": "bronze" + }, + { + "name": "Fast.Bet Australia", + "slug": "fastbet-australia", + "imageUrl": "https://images.opencollective.com/fastbet-australia/5b3cf57/logo.png", + "url": "https://www.fast.bet/au/", + "monthly": 10, + "allTime": 350, + "tier": "bronze" + }, + { + "name": "Matter", + "slug": "matter_hq", + "imageUrl": "https://images.opencollective.com/matter_hq/d8a975c/logo.png", + "url": "https://matterapp.com", + "monthly": 10, + "allTime": 278, + "tier": "bronze" + }, + { + "name": "Greece Casinos", + "slug": "greece-casinos", + "imageUrl": "https://images.opencollective.com/greece-casinos/9971c83/logo.png", + "url": "https://greece-casinos.com/", + "monthly": 10, + "allTime": 270, + "tier": "bronze" + }, + { + "name": "Gokken Online", + "slug": "gokken-online", + "imageUrl": "https://images.opencollective.com/gokken-online/8d6292c/logo.png", + "url": "https://gokken.cc/", + "monthly": 10, + "allTime": 260, + "tier": "bronze" + }, + { + "name": "m c", + "slug": "m-c1", + "imageUrl": "https://images.opencollective.com/m-c1/avatar.png", + "url": "https://opencollective.com/m-c1", + "monthly": 10, + "allTime": 250, + "tier": "bronze" + }, + { + "name": "Online Casinos Deutschland", + "slug": "online-casinos-deutschland", + "imageUrl": "https://images.opencollective.com/online-casinos-deutschland/895277e/logo.png", + "url": "https://www.casinosdeutschlandonline.com/", + "monthly": 10, + "allTime": 210, + "tier": "bronze" + }, + { + "name": "Casinos Portugal", + "slug": "casinos-portugal", + "imageUrl": "https://images.opencollective.com/casinos-portugal/05f094a/logo.png", + "url": "https://casinosportugal.com", + "monthly": 10, + "allTime": 210, + "tier": "bronze" + }, + { + "name": "Betting Site", + "slug": "betting-site", + "imageUrl": "https://images.opencollective.com/betting-site/76188fb/logo.png", + "url": "https://bettingsite.cc/", + "monthly": 10, + "allTime": 210, + "tier": "bronze" + }, + { + "name": "Fast.bet Finland", + "slug": "fastbet-finland", + "imageUrl": "https://images.opencollective.com/fastbet-finland/3c79ab1/logo.png", + "url": "https://www.fast.bet/finland/", + "monthly": 10, + "allTime": 200, + "tier": "bronze" + }, + { + "name": "RoboCat Casino", + "slug": "robocat-casino", + "imageUrl": "https://images.opencollective.com/robocat-casino/9a65aa9/logo.png", + "url": "https://robocat.casino/", + "monthly": 10, + "allTime": 190, + "tier": "bronze" + }, + { + "name": "Pistolocasino", + "slug": "pistolocasino", + "imageUrl": "https://images.opencollective.com/pistolocasino/1ea9a3d/logo.png", + "url": "https://pistolocasino.com", + "monthly": 10, + "allTime": 140, + "tier": "bronze" + }, + { + "name": "VanguardNGR France", + "slug": "vanguardngr-france", + "imageUrl": "https://images.opencollective.com/vanguardngr-france/4adb09f/logo.png", + "url": "https://www.vanguardngr.com/casino/fr/", + "monthly": 10, + "allTime": 100, + "tier": "bronze" + }, + { + "name": "Australian Casinos", + "slug": "australian-casinos", + "imageUrl": "https://images.opencollective.com/australian-casinos/f665258/logo.png", + "url": "https://1517.media/best/", + "monthly": 10, + "allTime": 70, + "tier": "bronze" + }, + { + "name": "DeContrabas", + "slug": "decontrabas", + "imageUrl": "https://images.opencollective.com/decontrabas/0c1ef83/logo.png", + "url": "https://decontrabas.com/", + "monthly": 10, + "allTime": 60, + "tier": "bronze" + }, + { + "name": "$5 Deposit Casinos", + "slug": "dollar5-deposit-casinos", + "imageUrl": "https://images.opencollective.com/dollar5-deposit-casinos/3728ee6/logo.png", + "url": "https://au.trustpilot.com/review/5dollardepositcasinos.org", + "monthly": 10, + "allTime": 40, + "tier": "bronze" + }, + { + "name": "BETON", + "slug": "beton", + "imageUrl": "https://images.opencollective.com/beton/6d9eb5e/avatar.png", + "url": "https://beton.ua/", + "monthly": 10, + "allTime": 40, + "tier": "bronze" + }, + { + "name": "New Online Casinos", + "slug": "new-online-casinos", + "imageUrl": "https://images.opencollective.com/new-online-casinos/d60f3e2/logo.png", + "url": "https://au.trustpilot.com/review/newonlinecasinosau.org", + "monthly": 10, + "allTime": 40, + "tier": "bronze" + }, + { + "name": "Online Pokies in Australia", + "slug": "online-pokies-au", + "imageUrl": "https://images.opencollective.com/online-pokies-au/7b2ecfc/logo.png", + "url": "https://au.trustpilot.com/review/aussiepokies.net", + "monthly": 10, + "allTime": 40, + "tier": "bronze" + }, + { + "name": "Snelst Uitbetalende Online Casinos", + "slug": "snelst-uitbetalende-online-cas", + "imageUrl": "https://images.opencollective.com/snelst-uitbetalende-online-cas/9d2a2fd/logo.png", + "url": "https://nl.trustpilot.com/review/snelstuitbetalendecasinos.org", + "monthly": 10, + "allTime": 30, + "tier": "bronze" + }, + { + "name": "Minimum Deposit Casinos", + "slug": "minimum-deposit-casinos", + "imageUrl": "https://images.opencollective.com/minimum-deposit-casinos/1a12635/logo.png", + "url": "https://au.trustpilot.com/review/minimumdepositpokies.org", + "monthly": 10, + "allTime": 30, + "tier": "bronze" + }, + { + "name": "$10 Deposit Casinos", + "slug": "dollar10-deposit-casinos", + "imageUrl": "https://images.opencollective.com/dollar10-deposit-casinos/fb16eff/logo.png", + "url": "https://au.trustpilot.com/review/10dollardepositcasinos.org", + "monthly": 10, + "allTime": 30, + "tier": "bronze" + }, + { + "name": "Muchbetter Casinos", + "slug": "muchbetter-casinos", + "imageUrl": "https://images.opencollective.com/muchbetter-casinos/9319770/logo.png", + "url": "https://ca.trustpilot.com/review/muchbettercasinos.org", + "monthly": 10, + "allTime": 30, + "tier": "bronze" + }, + { + "name": "Socibly", + "slug": "socibly", + "imageUrl": "https://images.opencollective.com/socibly/7a8d905/avatar.png", + "url": "https://www.socibly.com/", + "monthly": 10, + "allTime": 20, + "tier": "bronze" + } + ], + "backers": [ + { + "name": "JDLT", + "slug": "jdlt", + "imageUrl": "https://images.opencollective.com/jdlt/d26d6af/logo.png", + "allTime": 2318 + }, + { + "name": "Kazino Bonusi", + "slug": "kazino-bonusi", + "imageUrl": "https://images.opencollective.com/kazino-bonusi/c6a5f60/logo.png", + "allTime": 2260 + }, + { + "name": "Parker Bond", + "slug": "parkerbond", + "imageUrl": "https://images.opencollective.com/parkerbond/642d38e/avatar.png", + "allTime": 599 + }, + { + "name": "Vladimir Starkov", + "slug": "iamstarkov", + "imageUrl": "https://images.opencollective.com/iamstarkov/e76ac5d/avatar.png", + "allTime": 585 + }, + { + "name": "David Ang", + "slug": "david-ang", + "imageUrl": "https://images.opencollective.com/david-ang/361f0cf/avatar.png", + "allTime": 505 + }, + { + "name": "Michael Loughry", + "slug": "michael-loughry", + "imageUrl": "https://images.opencollective.com/michael-loughry/avatar.png", + "allTime": 480 + }, + { + "name": "Arkiraha", + "slug": "arkiraha", + "imageUrl": "https://images.opencollective.com/arkiraha/4ecfc00/logo.png", + "allTime": 460 + }, + { + "name": "Intevation", + "slug": "intevation-gmbh", + "imageUrl": "https://images.opencollective.com/intevation-gmbh/logo.png", + "allTime": 420 + }, + { + "name": "Lainaneuvos", + "slug": "lainaneuvos", + "imageUrl": "https://images.opencollective.com/lainaneuvos/logo.png", + "allTime": 380 + }, + { + "name": "Reach Digital", + "slug": "reach-digital-agency", + "imageUrl": "https://images.opencollective.com/reach-digital-agency/0770c6f/logo.png", + "allTime": 353 + }, + { + "name": "Gyuri Lajos", + "slug": "gyuri-lajos", + "imageUrl": "https://images.opencollective.com/gyuri-lajos/81517f9/avatar.png", + "allTime": 241 + }, + { + "name": "buzzvoice.com", + "slug": "buzzvoicecom", + "imageUrl": "https://images.opencollective.com/buzzvoicecom/c7477dd/logo.png", + "allTime": 234 + }, + { + "name": "Gavin Mogan", + "slug": "gavinmogan", + "imageUrl": "https://images.opencollective.com/gavinmogan/a52dc41/avatar.png", + "allTime": 222 + }, + { + "name": "Bulan", + "slug": "bulan", + "imageUrl": "https://images.opencollective.com/bulan/avatar.png", + "allTime": 218 + }, + { + "name": "Robert Knight", + "slug": "robertknight", + "imageUrl": "https://images.opencollective.com/robertknight/b585920/avatar.png", + "allTime": 202 + }, + { + "name": "Ryan Tallmadge", + "slug": "ryantallmadge", + "imageUrl": "https://images.opencollective.com/ryantallmadge/20b86e8/avatar.png", + "allTime": 196 + }, + { + "name": "Bonuskoodit", + "slug": "bonuskoodit", + "imageUrl": "https://images.opencollective.com/bonuskoodit/196fe5f/logo.png", + "allTime": 186 + }, + { + "name": "Driven Coffee Roasters", + "slug": "drivencoffee1", + "imageUrl": "https://images.opencollective.com/drivencoffee1/9177e16/logo.png", + "allTime": 184 + }, + { + "name": "Nopeustesti", + "slug": "nopeustesti", + "imageUrl": "https://images.opencollective.com/nopeustesti/10f1ba4/avatar.png", + "allTime": 184 + }, + { + "name": "Lotto tulokset", + "slug": "lotto-tulokset-tanaan", + "imageUrl": "https://images.opencollective.com/lotto-tulokset-tanaan/131eaca/logo.png", + "allTime": 184 + }, + { + "name": "Bonus Koodit", + "slug": "bonus-koodit", + "imageUrl": "https://images.opencollective.com/bonus-koodit/avatar.png", + "allTime": 184 + }, + { + "name": "Nick Dandakis", + "slug": "nickdandakis", + "imageUrl": "https://images.opencollective.com/nickdandakis/db9f423/avatar.png", + "allTime": 182 + }, + { + "name": "Jeremy Tice", + "slug": "jeremy-tice", + "imageUrl": "https://images.opencollective.com/jeremy-tice/7d45f03/avatar.png", + "allTime": 172 + }, + { + "name": "Scott Wolf", + "slug": "scott-wolf", + "imageUrl": "https://images.opencollective.com/scott-wolf/85e29fa/avatar.png", + "allTime": 172 + }, + { + "name": "Y8", + "slug": "y81", + "imageUrl": "https://images.opencollective.com/y81/c9812a7/logo.png", + "allTime": 172 + }, + { + "name": "Clean Green Compare", + "slug": "cleangreencars-co-uk", + "imageUrl": "https://images.opencollective.com/cleangreencars-co-uk/484a84c/logo.png", + "allTime": 170 + }, + { + "name": "Pika-kasinot.com", + "slug": "pika-kasinot-com", + "imageUrl": "https://images.opencollective.com/pika-kasinot-com/4becc63/logo.png", + "allTime": 150 + }, + { + "name": "Trevor Richardson", + "slug": "trevor-richardson", + "imageUrl": "https://images.opencollective.com/trevor-richardson/57b8265/avatar.png", + "allTime": 138 + }, + { + "name": "Content Camel", + "slug": "content-camel", + "imageUrl": "https://images.opencollective.com/content-camel/96a3366/logo.png", + "allTime": 136 + }, + { + "name": "lex Alexander", + "slug": "thelextimes", + "imageUrl": "https://images.opencollective.com/thelextimes/530326c/avatar.png", + "allTime": 125 + }, + { + "name": "Buy Google Reviews", + "slug": "buy-google-reviews-usa", + "imageUrl": "https://images.opencollective.com/buy-google-reviews-usa/78797fd/logo.png", + "allTime": 125 + }, + { + "name": "Ivan Lagunovsky", + "slug": "ivan-lagunovsky", + "imageUrl": "https://images.opencollective.com/ivan-lagunovsky/31a473a/avatar.png", + "allTime": 123 + }, + { + "name": "EscortA.com", + "slug": "escortacom", + "imageUrl": "https://images.opencollective.com/escortacom/e21c1af/avatar.png", + "allTime": 120 + }, + { + "name": "Ігрові автомати", + "slug": "igrovye-avtomaty", + "imageUrl": "https://images.opencollective.com/igrovye-avtomaty/e15c244/logo.png", + "allTime": 120 + }, + { + "name": "Fjord Finans", + "slug": "user-e945a8d9", + "imageUrl": "https://images.opencollective.com/user-e945a8d9/e825192/avatar.png", + "allTime": 113 + }, + { + "name": "Nordiclenders", + "slug": "nordiclenders", + "imageUrl": "https://images.opencollective.com/nordiclenders/dc14432/logo.png", + "allTime": 108 + }, + { + "name": "GraphCommerce", + "slug": "graphcommerce", + "imageUrl": "https://images.opencollective.com/graphcommerce/3171029/logo.png", + "allTime": 104 + }, + { + "name": "Vegas", + "slug": "vegas", + "imageUrl": "https://images.opencollective.com/vegas/6cfa195/logo.png", + "allTime": 95 + }, + { + "name": "Nikotiinipussit.com", + "slug": "nikotiinipussit-com", + "imageUrl": "https://images.opencollective.com/nikotiinipussit-com/742cda2/logo.png", + "allTime": 92 + }, + { + "name": "betking онлайн казино", + "slug": "betking", + "imageUrl": "https://images.opencollective.com/betking/59924a1/logo.png", + "allTime": 90 + }, + { + "name": "anonymous", + "slug": "anonymous191", + "imageUrl": "https://images.opencollective.com/anonymous191/avatar.png", + "allTime": 89 + }, + { + "name": "Jackpot Rabbit Social Casino", + "slug": "jackpotrabbit", + "imageUrl": "https://images.opencollective.com/jackpotrabbit/70e3c6f/logo.png", + "allTime": 85 + }, + { + "name": "Tiara Rodney", + "slug": "tiara-rodney", + "imageUrl": "https://images.opencollective.com/tiara-rodney/ed37e32/avatar.png", + "allTime": 80 + }, + { + "name": "yaakaito", + "slug": "yaakaito", + "imageUrl": "https://images.opencollective.com/yaakaito/d5f11b7/avatar.png", + "allTime": 76 + }, + { + "name": "Iranshartbandi.com", + "slug": "iranshartbandi", + "imageUrl": "https://images.opencollective.com/iranshartbandi/6e9bb50/logo.png", + "allTime": 68 + }, + { + "name": "betking YouTube", + "slug": "betking-youtube", + "imageUrl": "https://images.opencollective.com/betking-youtube/d6f932f/logo.png", + "allTime": 63 + }, + { + "name": "CEOBuySell", + "slug": "ceo-buy-sell", + "imageUrl": "https://images.opencollective.com/ceo-buy-sell/avatar.png", + "allTime": 58 + }, + { + "name": "Mister 7", + "slug": "mister-7", + "imageUrl": "https://images.opencollective.com/mister-7/0440604/avatar.png", + "allTime": 54 + }, + { + "name": "Lainojen-yhdistäminen", + "slug": "lainojen-yhdistaminen", + "imageUrl": "https://images.opencollective.com/lainojen-yhdistaminen/logo.png", + "allTime": 52 + }, + { + "name": "Linden Photonics", + "slug": "linden-photonics", + "imageUrl": "https://images.opencollective.com/linden-photonics/abf0bd8/logo.png", + "allTime": 48 + }, + { + "name": "https://betking.com.ua/games/all-slots/", + "slug": "slotokinguagamesallslots", + "imageUrl": "https://images.opencollective.com/slotokinguagamesallslots/logo.png", + "allTime": 46 + }, + { + "name": "Socialfollowers.io", + "slug": "socialfollowers", + "imageUrl": "https://images.opencollective.com/socialfollowers/99f79b4/avatar.png", + "allTime": 44 + }, + { + "name": "Українські онлайн казино", + "slug": "online-casinos-ua", + "imageUrl": "https://images.opencollective.com/online-casinos-ua/2b5d565/logo.png", + "allTime": 44 + }, + { + "name": "Mymoneycomparison.com", + "slug": "mymoneycomparison", + "imageUrl": "https://images.opencollective.com/mymoneycomparison/93c0e0e/avatar.png", + "allTime": 38 + }, + { + "name": "www.interviewpal.com", + "slug": "www-interviewpal-com", + "imageUrl": "https://images.opencollective.com/www-interviewpal-com/avatar.png", + "allTime": 38 + }, + { + "name": "Gitea", + "slug": "gitea", + "imageUrl": "https://images.opencollective.com/gitea/bf35c2f/logo.png", + "allTime": 35 + }, + { + "name": "ingatbola88", + "slug": "ingatbola88", + "imageUrl": "https://images.opencollective.com/ingatbola88/9e16161/avatar.png", + "allTime": 35 + }, + { + "name": "Tescort.com", + "slug": "tescortcom", + "imageUrl": "https://images.opencollective.com/tescortcom/fb537f1/logo.png", + "allTime": 32 + }, + { + "name": "techzjc", + "slug": "techzjc", + "imageUrl": "https://images.opencollective.com/techzjc/2dc46e7/avatar.png", + "allTime": 30 + }, + { + "name": "East Villa FC", + "slug": "eastvillafc", + "imageUrl": "https://images.opencollective.com/eastvillafc/4b38432/logo.png", + "allTime": 25 + }, + { + "name": "BuyTikTokFollowers.co", + "slug": "buy-tiktok-followers-co", + "imageUrl": "https://images.opencollective.com/buy-tiktok-followers-co/82431fe/logo.png", + "allTime": 24 + }, + { + "name": "Seven Slots", + "slug": "seven-slots", + "imageUrl": "https://images.opencollective.com/seven-slots/7c0bd83/avatar.png", + "allTime": 22 + }, + { + "name": "Adi", + "slug": "nodepositbonuscc", + "imageUrl": "https://images.opencollective.com/nodepositbonuscc/avatar.png", + "allTime": 22 + }, + { + "name": "betking", + "slug": "betking-app", + "imageUrl": "https://images.opencollective.com/betking-app/674dcf6/logo.png", + "allTime": 18 + }, + { + "name": "VolgersBoost", + "slug": "volgersboostsocial", + "imageUrl": "https://images.opencollective.com/volgersboostsocial/1d333e7/logo.png", + "allTime": 14 + }, + { + "name": "Prothots", + "slug": "prothotse", + "imageUrl": "https://images.opencollective.com/prothotse/8bd9200/avatar.png", + "allTime": 12 + }, + { + "name": "YouTube Downloader", + "slug": "yt-downloader", + "imageUrl": "https://images.opencollective.com/yt-downloader/88f5399/logo.png", + "allTime": 10 + }, + { + "name": "Network Tools", + "slug": "network-tools", + "imageUrl": "https://images.opencollective.com/network-tools/527a9f7/logo.png", + "allTime": 10 + }, + { + "name": "Calculators", + "slug": "calculators", + "imageUrl": "https://images.opencollective.com/calculators/2ed556f/logo.png", + "allTime": 10 + }, + { + "name": "Wallpapers.Com", + "slug": "wallpapers-com", + "imageUrl": "https://images.opencollective.com/wallpapers-com/b624c91/logo.png", + "allTime": 10 + }, + { + "name": "Online Casinos 247", + "slug": "online-casinos-247", + "imageUrl": "https://images.opencollective.com/online-casinos-247/f8c55c0/logo.png", + "allTime": 10 + }, + { + "name": "FoloGrow - Buy Tiktok Followers", + "slug": "fologrow", + "imageUrl": "https://images.opencollective.com/fologrow/d9607ca/avatar.png", + "allTime": 10 + }, + { + "name": "GTR Socials", + "slug": "gtr-socials", + "imageUrl": "https://images.opencollective.com/gtr-socials/c634f87/logo.png", + "allTime": 10 + }, + { + "name": "Casino ohne oasis", + "slug": "casinos-ohne-oasis", + "imageUrl": "https://images.opencollective.com/casinos-ohne-oasis/4d51700/logo.png", + "allTime": 8 + }, + { + "name": "Paysafecard Casino", + "slug": "paysafecardcasino", + "imageUrl": "https://images.opencollective.com/paysafecardcasino/4b23c2e/logo.png", + "allTime": 8 + }, + { + "name": "serveur-minecraft.org", + "slug": "serveur-minecraft-org", + "imageUrl": "https://images.opencollective.com/serveur-minecraft-org/4df2ef8/avatar.png", + "allTime": 6 + }, + { + "name": "VanguardNGR Spain", + "slug": "vanguardngr-spain", + "imageUrl": "https://images.opencollective.com/vanguardngr-spain/28b6611/logo.png", + "allTime": 6 + }, + { + "name": "Lizaa", + "slug": "lizaa", + "imageUrl": "https://images.opencollective.com/lizaa/avatar.png", + "allTime": 6 + }, + { + "name": "Time.so", + "slug": "time-so", + "imageUrl": "https://images.opencollective.com/time-so/d62b586/logo.png", + "allTime": 6 + }, + { + "name": "Casinos ohne OASIS", + "slug": "casino-ohne-oasis", + "imageUrl": "https://images.opencollective.com/casino-ohne-oasis/2669ea2/logo.png", + "allTime": 4 + }, + { + "name": "Casino mit Schneller Auszahlung", + "slug": "de-casino-mit-schneller-auszahlun", + "imageUrl": "https://images.opencollective.com/de-casino-mit-schneller-auszahlun/4661023/logo.png", + "allTime": 4 + }, + { + "name": "WNB Casino DE", + "slug": "wnb-casino-de", + "imageUrl": "https://images.opencollective.com/wnb-casino-de/7dfb7a3/logo.png", + "allTime": 4 + }, + { + "name": "Buy YouTube Views on Instant Famous", + "slug": "buy-youtube-views-on-instant-famous", + "imageUrl": "https://images.opencollective.com/buy-youtube-views-on-instant-famous/e2249c1/logo.png", + "allTime": 2 + }, + { + "name": "Seriöse Online Casinos", + "slug": "seriose-online-casinos", + "imageUrl": "https://images.opencollective.com/seriose-online-casinos/00df02a/logo.png", + "allTime": 2 + }, + { + "name": "Live Casino", + "slug": "live-casino-de", + "imageUrl": "https://images.opencollective.com/live-casino-de/03b7e18/logo.png", + "allTime": 2 + }, + { + "name": "Neue Online Casinos", + "slug": "neue-online-casinos-de", + "imageUrl": "https://images.opencollective.com/neue-online-casinos-de/ac3918c/logo.png", + "allTime": 2 + }, + { + "name": "Bitcoin Casino", + "slug": "de-bitcoin-casino", + "imageUrl": "https://images.opencollective.com/de-bitcoin-casino/e41adc2/logo.png", + "allTime": 2 + }, + { + "name": "Paypal Casino", + "slug": "paypal-casino", + "imageUrl": "https://images.opencollective.com/paypal-casino/fea3896/logo.png", + "allTime": 2 + }, + { + "name": "Crypto Casino", + "slug": "de-crypto-casino", + "imageUrl": "https://images.opencollective.com/de-crypto-casino/0c70323/logo.png", + "allTime": 2 + }, + { + "name": "Casino ohne Anmeldung", + "slug": "casino-ohne-anmeldung", + "imageUrl": "https://images.opencollective.com/casino-ohne-anmeldung/a14bc86/logo.png", + "allTime": 2 + }, + { + "name": "Casino ohne Verifizierung", + "slug": "casino-ohne-verifizierung", + "imageUrl": "https://images.opencollective.com/casino-ohne-verifizierung/8ee56cf/logo.png", + "allTime": 2 + }, + { + "name": "Casino ohne Limit", + "slug": "casino-ohne-limit", + "imageUrl": "https://images.opencollective.com/casino-ohne-limit/c065cb4/logo.png", + "allTime": 2 + }, + { + "name": "Casino ohne Lizenz", + "slug": "de-casino-ohne-lizenz", + "imageUrl": "https://images.opencollective.com/de-casino-ohne-lizenz/8e03176/logo.png", + "allTime": 2 + }, + { + "name": "Wettanbieter ohne OASIS", + "slug": "wettanbieter-ohne-oasis", + "imageUrl": "https://images.opencollective.com/wettanbieter-ohne-oasis/9a733cc/logo.png", + "allTime": 2 + } + ], + "totalBackers": 94 +} diff --git a/layouts/Sponsors/index.jsx b/layouts/Sponsors/index.jsx new file mode 100644 index 0000000..e4e4b46 --- /dev/null +++ b/layouts/Sponsors/index.jsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; + +import BaseButton from '@node-core/ui-components/Common/BaseButton'; + +import NavBar from '../../components/NavBar.jsx'; +import Footer from '../../components/Footer/index.jsx'; +import SectionHeader from '../../components/SectionHeader/index.jsx'; +import SponsorTier from '../../components/Sponsors/Tier/index.jsx'; +import BackerWall from '../../components/Sponsors/BackerWall/index.jsx'; +import SortToggle from '../../components/Sponsors/SortToggle/index.jsx'; +import data from '#theme/sponsors' with { type: 'json' }; + +import styles from './index.module.css'; + +const OC_URL = 'https://opencollective.com/webpack'; +const TIERS = [ + { + tier: 'platinum', + label: 'Platinum', + price: '$2,500+ / month', + cardSize: 'lg', + thresholds: { monthly: 2500, allTime: 50000 }, + }, + { + tier: 'gold', + label: 'Gold', + price: '$500 / month', + cardSize: 'md', + thresholds: { monthly: 500, allTime: 10000 }, + }, + { + tier: 'silver', + label: 'Silver', + price: '$100 / month', + cardSize: 'sm', + thresholds: { monthly: 100, allTime: 2000 }, + }, + { + tier: 'bronze', + label: 'Bronze', + price: '$10 / month', + cardSize: 'xs', + thresholds: { monthly: 10, allTime: 200 }, + }, +]; + +const sortByMetric = (list, metric) => + [...list].sort((a, b) => b[metric] - a[metric]); + +/** Group sponsors into tier buckets according to the active metric's thresholds. */ +const bucketSponsors = (sponsors, metric) => { + const buckets = { platinum: [], gold: [], silver: [], bronze: [] }; + for (const sponsor of sortByMetric(sponsors, metric)) { + const match = + TIERS.find(t => sponsor[metric] >= t.thresholds[metric]) ?? + TIERS[TIERS.length - 1]; + buckets[match.tier].push(sponsor); + } + return buckets; +}; + +/** + * Sponsors page layout. Lists Open Collective sponsors by tier with a control to re-rank + * them by recurring monthly amount or all-time contribution, plus a backer wall and CTA. + * + * @param {{ metadata: object }} props + */ +export default function SponsorsLayout({ metadata }) { + const [metric, setMetric] = useState('monthly'); + + const buckets = bucketSponsors(data.sponsors, metric); + + return ( + <> + + +
+
+
+ +
+ + View on Open Collective + +
+
+ +
+
+ {TIERS.map(tier => ( + + ))} +
+
+
+ +
+
+ +
+ +
+
+
+
+ +