diff --git a/components/HomePage/ConfigSection/index.jsx b/components/HomePage/ConfigSection/index.jsx new file mode 100644 index 00000000..592f176a --- /dev/null +++ b/components/HomePage/ConfigSection/index.jsx @@ -0,0 +1,57 @@ +import styles from './index.module.css'; + +export default function ConfigSection({ children }) { + const configFeatures = [ + 'Zero-config for common setups', + 'Tree-shaking out of the box', + 'Hot Module Replacement', + 'Long-term caching with content hashes', + ]; + return ( +
+
+
+

CONFIGURATION

+

+ Sensible defaults. Configurable when you need it. +

+

+ A single config file is enough for most projects. Compose loaders to + transform any input; reach for plugins when behavior is non-trivial. +

+
+ +
+ {/* rendering the code block */} + {children} +
+

Loaders for any input

+

+ Through loaders, modules can be CommonJS, AMD, ES6 modules, CSS, + Images, JSON, Coffeescript, LESS — and your custom stuff. +

+ +
    + {configFeatures.map((feature, index) => ( +
  • + + + + {feature} +
  • + ))} +
+
+
+
+
+ ); +} diff --git a/components/HomePage/ConfigSection/index.module.css b/components/HomePage/ConfigSection/index.module.css new file mode 100644 index 00000000..27c1fff0 --- /dev/null +++ b/components/HomePage/ConfigSection/index.module.css @@ -0,0 +1,108 @@ +@reference "../../../styles/index.css"; + +.configSection { + @apply flex + justify-center + w-full + py-20 + border-neutral-200 + bg-neutral-100 + dark:border-neutral-800 + dark:bg-[#070c13]; +} + +.container { + @apply flex + w-[80%] + max-w-7xl + flex-col + gap-3; +} + +.configHeader { + @apply w-full + max-w-xl + text-left; +} + +.preTitle { + @apply mb-4 + flex + items-center + justify-center + text-[0.85rem] + font-normal + uppercase + tracking-wider + text-blue-600 + dark:text-blue-500 + lg:justify-start; +} + +.title { + @apply text-4xl + mb-4; +} + +.subtext { + @apply mb-8 + text-lg + leading-relaxed + text-neutral-800 + dark:text-neutral-500; +} + +.configGrid { + @apply flex + w-full + flex-col + items-start + gap-12 + lg:flex-row + lg:items-center; +} + +.features { + @apply flex + flex-1 + flex-col; +} + +.featuresTitle { + @apply mb-4 + text-2xl + font-normal + text-neutral-900 + dark:text-white; +} + +.featuresText { + @apply mb-4 + text-base + leading-relaxed + text-neutral-600 + dark:text-neutral-400; +} + +.checkList { + @apply flex + flex-col + gap-3; +} + +.checkItem { + @apply flex + items-center + gap-3 + text-sm + font-medium + text-neutral-700 + dark:text-neutral-300; +} + +.checkIcon { + @apply h-5 + w-5 + shrink-0 + text-blue-500; +} diff --git a/components/HomePage/FeaturesSection/index.jsx b/components/HomePage/FeaturesSection/index.jsx new file mode 100644 index 00000000..7a9d7161 --- /dev/null +++ b/components/HomePage/FeaturesSection/index.jsx @@ -0,0 +1,151 @@ +import styles from './index.module.css'; + +const Features = [ + { + title: 'Module Federation', + description: + 'Share code across separately-deployed applications at runtime. The micro-frontend pattern, done right.', + icon: ( + + + + ), + }, + { + title: 'Code splitting', + description: + "Split bundles by route, by demand, or by vendor. Load what's needed, when it's needed.", + icon: ( + + + + ), + }, + { + title: 'Tree shaking', + description: + 'Static analysis of ES modules eliminates dead code in production builds — automatically.', + icon: ( + + + + ), + }, + { + title: 'Hot module replacement', + description: + 'Edit and see the result without losing application state. The fastest feedback loop in JavaScript tooling.', + icon: ( + + + + ), + }, + { + title: 'Persistent caching', + description: + "v5's filesystem cache makes warm builds near-instant. Cold builds are 38% faster than v4 on large monorepos.", + icon: ( + + + + ), + }, + { + title: '11,000+ plugins', + description: + "The largest ecosystem in JavaScript tooling. If a build problem exists, there's a webpack plugin for it.", + icon: ( + + + + ), + }, +]; + +export default function FeaturesSection() { + return ( +
+
+
+

WHY WEBPACK

+

Built for serious applications.

+

+ The original module bundler. Used by Vercel, Shopify, GitHub, + Microsoft, and most of the modern frontend stack. +

+
+ +
+ {Features.map((feature, index) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ ); +} diff --git a/components/HomePage/FeaturesSection/index.module.css b/components/HomePage/FeaturesSection/index.module.css new file mode 100644 index 00000000..527fc510 --- /dev/null +++ b/components/HomePage/FeaturesSection/index.module.css @@ -0,0 +1,110 @@ +@reference "../../../styles/index.css"; + +.whySection { + @apply flex + w-full + justify-center + py-20; +} + +.container { + @apply flex + w-[80%] + max-w-7xl + flex-col + gap-3; +} + +.whyHeader { + @apply mb-4 + w-full + max-w-2xl + text-left; +} + +.preTitle { + @apply mb-4 + flex + items-center + justify-center + text-[0.85rem] + font-normal + uppercase + tracking-wider + text-blue-600 + dark:text-blue-500 + lg:justify-start; +} + +.title { + @apply text-4xl + mb-4; +} + +.subtext { + @apply mb-8 + text-lg + leading-relaxed + text-neutral-800 + dark:text-neutral-500; +} + +.gridContainer { + @apply grid + w-full + grid-cols-1 + gap-6 + md:grid-cols-2 + lg:grid-cols-3; +} + +.card { + @apply flex + flex-col + items-start + rounded-xl + border + border-neutral-200 + bg-white + p-8 + transition-shadow + hover:shadow-lg + dark:border-neutral-800 + dark:bg-[#0D131C] + dark:hover:shadow-blue-950; +} + +.iconWrapper { + @apply mb-6 + flex + h-12 + w-12 + items-center + justify-center + rounded-lg + bg-blue-50 + text-blue-600 + dark:bg-blue-900/30 + dark:text-blue-400; +} + +.iconWrapper svg { + @apply h-6 + w-6; +} + +.cardTitle { + @apply mb-3 + text-lg + font-medium + text-neutral-900 + dark:text-white; +} + +.cardDesc { + @apply m-0 + text-sm + leading-relaxed + text-neutral-600 + dark:text-neutral-400; +} diff --git a/components/HomePage/Hero/index.jsx b/components/HomePage/Hero/index.jsx new file mode 100644 index 00000000..863b907b --- /dev/null +++ b/components/HomePage/Hero/index.jsx @@ -0,0 +1,88 @@ +import styles from './index.module.css'; +import { version } from '#theme/config'; +import WebpackSVG from '../../Icons/WebpackSVG'; +export default function Hero() { + return ( +
+
+
+
+

+ STATIC MODULE BUNDLER +

+ +

+ Bundle the web. +

+ +

+ webpack packs many modules into a few bundled assets. Code + splitting allows for loading parts of the application on demand — + designed for modern JavaScript apps. +

+ +
+ + Get started + + + + + + + + View on GitHub + + + + +
+
+ +
+ +
+
+
+ +
+
+

35M+

+

weekly downloads

+
+
+

65K+

+

GitHub stars

+
+
+

11K+

+

plugins published

+
+
+

{`v${version.version}`}

+

current release

+
+
+
+ ); +} diff --git a/components/HomePage/Hero/index.module.css b/components/HomePage/Hero/index.module.css new file mode 100644 index 00000000..53af2293 --- /dev/null +++ b/components/HomePage/Hero/index.module.css @@ -0,0 +1,194 @@ +@reference "../../../styles/index.css"; + +.hero { + @apply flex + w-full + flex-col + items-center + pt-20; +} + +.webpackContainer { + @apply flex + justify-center + w-full + border-b + border-neutral-200 + dark:border-neutral-800; +} + +.webpack { + @apply pb-20 + flex + max-w-6xl + w-[75%] + flex-col + items-center + justify-between + gap-16 + text-center + lg:flex-row + lg:gap-10 + lg:text-left; +} + +.description { + @apply w-full + max-w-full + flex-1 + lg:max-w-[520px]; +} + +.preTitle { + @apply mb-4 + flex + items-center + justify-center + text-[0.85rem] + font-normal + uppercase + tracking-wider + text-blue-600 + dark:text-blue-500 + lg:justify-start; +} + +.dot { + @apply mr-2 + inline-block + h-1.5 + w-1.5 + rounded-full + bg-blue-600 + dark:bg-blue-500; +} + +.title { + @apply mb-4 + font-normal + text-5xl + leading-[1.1] + md:text-6xl; +} + +.highlight { + @apply text-blue-600 + dark:text-blue-500; +} + +.subtext { + @apply mb-8 + text-lg + leading-relaxed + text-neutral-800 + dark:text-neutral-500; +} + +.actions { + @apply flex + items-center + justify-center + gap-4 + lg:justify-start; +} + +.primaryBtn { + @apply inline-flex + items-center + justify-center + rounded + border + border-blue-700 + bg-blue-600 + dark:bg-blue-500 + px-5 + py-3 + text-sm + font-medium + text-white + transition-colors + duration-200 + hover:bg-blue-700 + dark:hover:bg-blue-400; +} + +.arrowIcon { + @apply ml-2 + h-5 + w-5 + transition-transform + duration-200 + group-hover:translate-x-1; +} + +.primaryBtn:hover .arrowIcon { + @apply translate-x-1; +} + +.secondaryBtn { + @apply inline-flex + items-center + justify-center + rounded + border + border-[#d1d9e0] + bg-white + px-5 + py-3 + text-sm + font-medium + text-[#2c3e50] + transition-colors + duration-200 + hover:border-[#b5c0cb] + hover:bg-[#f8f9fa]; +} + +.githubIcon { + @apply ml-2 + h-4 + w-4; +} + +.logo { + @apply flex + w-full + items-center + justify-center + lg:flex-1 + lg:justify-end; +} + +.webpackLogo { + @apply aspect-square + w-72 + max-w-full + lg:w-96; +} + +.stats { + @apply flex + w-[90%] + max-w-350 + flex-col + items-center + justify-center + gap-6 + py-6 + text-center + dark:border-neutral-800 + lg:flex-row + lg:justify-around + lg:gap-0; +} + +.statItem h2 { + @apply text-3xl + font-medium; +} + +.statItem p { + @apply text-sm + text-neutral-800 + dark:text-neutral-500; +} diff --git a/components/HomePage/HomeSponsorSection/index.jsx b/components/HomePage/HomeSponsorSection/index.jsx new file mode 100644 index 00000000..936fbf33 --- /dev/null +++ b/components/HomePage/HomeSponsorSection/index.jsx @@ -0,0 +1,186 @@ +import { useMemo } from 'react'; +import classNames from 'classnames'; +import Avatar from '@node-core/ui-components/Common/AvatarGroup/Avatar'; +import BaseButton from '@node-core/ui-components/Common/BaseButton'; + +import SectionHeader from '../../SectionHeader/index.jsx'; +import SponsorTier from '../../Sponsors/Tier/index.jsx'; +import data from '#theme/sponsors' with { type: 'json' }; + +import styles from './index.module.css'; + +const OC_URL = 'https://opencollective.com/webpack'; +const OC_BASE = 'https://opencollective.com'; +const SPONSORS_URL = '/about/sponsors'; + +// Home page always ranks by monthly contribution — no sort toggle needed. +const METRIC = 'monthly'; + +// Max cards shown per tier before the overflow "· · · +N more" row appears. +const TIERS = [ + { + tier: 'platinum', + label: 'Platinum', + price: '$2,500+ / month', + cardSize: 'lg', + limit: 4, + }, + { + tier: 'gold', + label: 'Gold', + price: '$500 / month', + cardSize: 'md', + limit: 6, + }, + { + tier: 'silver', + label: 'Silver', + price: '$100 / month', + cardSize: 'sm', + limit: 8, + }, + { + tier: 'bronze', + label: 'Bronze', + price: '$10 / month', + cardSize: 'xs', + limit: 12, + }, +]; + +// Max backer avatars shown before the overflow row appears. +const BACKER_LIMIT = 40; + +const sortByMetric = (list, metric) => + [...list].sort((a, b) => b[metric].value - a[metric].value); + +// Group all sponsors into tier buckets (mirrors SponsorsLayout logic). +const bucketSponsors = (sponsors, metric) => { + const buckets = { platinum: [], gold: [], silver: [], bronze: [] }; + for (const sponsor of sortByMetric(sponsors, metric)) { + const tier = sponsor[metric].tier; + if (!tier) continue; + buckets[tier].push(sponsor); + } + return buckets; +}; + +const initialsOf = name => + name + .split(/\s+/) + .slice(0, 2) + .map(w => w[0]) + .join('') + .toUpperCase(); + +// Randomise backer wall order on every render so no backer is permanently buried. +const shuffle = arr => [...arr].sort(() => Math.random() - 0.5); + +// Shared overflow indicator + +function SeeMore({ count, href, className }) { + return ( +
+ + + +{count} more (see all) + +
+ ); +} + +export default function HomeSponsorSection() { + const buckets = useMemo(() => bucketSponsors(data.sponsors, METRIC), []); + const hasAnySponsor = TIERS.some(({ tier }) => buckets[tier].length > 0); + + if (!hasAnySponsor) return null; + + return ( +
+
+ + + {/* ── Sponsor tiers ── */} +
+ {TIERS.map(({ tier, label, price, cardSize, limit }) => { + const all = buckets[tier]; + if (!all.length) return null; + + const shown = all.slice(0, limit); + const overflow = all.length - limit; + + return ( +
+ + {overflow > 0 && ( + + )} +
+ ); + })} +
+ + {/* ── Backer wall ── */} + {data.backers?.length > 0 && ( +
+

And the people who chip in

+
+ {CLIENT && + shuffle(data.backers) + .slice(0, BACKER_LIMIT) + .map(backer => ( + + ))} +
+ {data.backers.length > BACKER_LIMIT && ( + + )} +
+ )} + +
+ + Become a sponsor + + + See all sponsors → + +
+
+
+ ); +} diff --git a/components/HomePage/HomeSponsorSection/index.module.css b/components/HomePage/HomeSponsorSection/index.module.css new file mode 100644 index 00000000..aaab7a9d --- /dev/null +++ b/components/HomePage/HomeSponsorSection/index.module.css @@ -0,0 +1,130 @@ +@reference "../../../styles/index.css"; + +.section { + @apply bg-white + py-16 + lg:py-20 + dark:bg-neutral-950 + border-t + border-neutral-200 + dark:border-neutral-800; +} + +.container { + @apply mx-auto + max-w-7xl + px-6; +} + +.tiers { + @apply mt-12 + flex + flex-col + gap-10; +} + +.tierWrapper { + @apply flex + flex-col + gap-0; +} + +.platinumStage { + @apply rounded-2xl + px-6 + py-8; + + /* blue-50 (#eff6ff) at 50% opacity */ + background: radial-gradient( + ellipse 80% 60% at 50% 0%, + rgb(239 246 255 / 50%) 0%, + transparent 100% + ); +} + +:global(.dark) .platinumStage { + /* blue-950 (#172554) at 30% opacity */ + background: radial-gradient( + ellipse 80% 60% at 50% 0%, + rgb(23 37 84 / 30%) 0%, + transparent 100% + ); +} + +.seeMore { + @apply flex + items-center + justify-center + gap-2 + mt-4; +} + +.dots { + @apply select-none + tracking-widest + text-neutral-300 + dark:text-neutral-600; +} + +.seeMoreLink { + @apply text-sm + font-medium + text-blue-600 + no-underline + transition-colors + duration-150 + hover:text-blue-700 + dark:text-blue-400 + dark:hover:text-blue-300; +} + +.backersSection { + @apply mt-14 + border-t + border-neutral-100 + pt-10 + dark:border-neutral-800; +} + +.backersLabel { + @apply mb-6 + text-center + text-xs + font-semibold + uppercase + tracking-widest + text-neutral-400 + dark:text-neutral-500; +} + +.backerWall { + @apply flex + flex-wrap + justify-center + gap-2; +} + +.backerSeeMore { + @apply mt-5; +} + +.actions { + @apply mt-10 + flex + flex-wrap + items-center + justify-center + gap-4; +} + +.allSponsorsLink { + @apply text-sm + font-medium + text-blue-600 + no-underline + transition-colors + duration-150 + hover:text-blue-700 + dark:text-blue-400 + dark:hover:text-blue-300; +} diff --git a/components/Icons/WebpackSVG.jsx b/components/Icons/WebpackSVG.jsx new file mode 100644 index 00000000..47ae9597 --- /dev/null +++ b/components/Icons/WebpackSVG.jsx @@ -0,0 +1,23 @@ +export default function WebpackSVG({ className }) { + return ( + + + + + + + + ); +} diff --git a/layouts/Home/index.jsx b/layouts/Home/index.jsx index f237ddf5..c8628931 100644 --- a/layouts/Home/index.jsx +++ b/layouts/Home/index.jsx @@ -1 +1,13 @@ -export default undefined; +import Footer from '../../components/Footer'; +import NavBar from '../../components/NavBar'; + +export default function Home({ metadata, children }) { + return ( + <> + + {/* MDX content described inside root index.md */} + {children} +