diff --git a/src/components/CommunityEvents/CommunityEvents.astro b/src/components/CommunityEvents/CommunityEvents.astro
new file mode 100644
index 00000000000..2d31b6a0945
--- /dev/null
+++ b/src/components/CommunityEvents/CommunityEvents.astro
@@ -0,0 +1,110 @@
+---
+import { SvgArrowRight2, Typography } from "@chainlink/blocks"
+import EventCard from "./EventCard.astro"
+import ImageGallery from "./ImageGallery.astro"
+import type { GalleryImage } from "./types"
+import { fetchEventsFromRSS } from "./fetchEvents"
+import styles from "./CommunityEvents.module.css"
+
+// Community event gallery images
+const galleryImages: GalleryImage[] = [
+ // Top row - scrolls left
+ {
+ id: "1",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd59_home-community-5.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "2",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd55_home-community-2.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "3",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd30_home-community-4.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "4",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd46_home-community-11.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "5",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd51_home-community-3.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "6",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd42_home-community-10.webp",
+ alt: "Chainlink community event",
+ },
+ // Bottom row - scrolls right
+ {
+ id: "7",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd24_community-photo-12.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "8",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd2b_community-photo-10.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "9",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd4c_community-photo-24.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "10",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd3d_community-photo-22.webp",
+ alt: "Chainlink community event",
+ },
+ {
+ id: "11",
+ imageUrl:
+ "https://cdn.prod.website-files.com/64cc2c23d8dbd707cdb556d8/677d1da974d919ae98a3bd36_community-photo-30.webp",
+ alt: "Chainlink community event",
+ },
+]
+
+const events = await fetchEventsFromRSS()
+---
+
+
+
+
+
+
+ {events.map((event) => )}
+
+
+
+
+
+
+
+
diff --git a/src/components/CommunityEvents/CommunityEvents.module.css b/src/components/CommunityEvents/CommunityEvents.module.css
new file mode 100644
index 00000000000..562ac07105c
--- /dev/null
+++ b/src/components/CommunityEvents/CommunityEvents.module.css
@@ -0,0 +1,87 @@
+.wrapper {
+ margin: 86px 0;
+}
+.component {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ grid-template-rows: auto;
+ grid-auto-columns: 1fr;
+ align-items: center;
+ gap: 33px;
+ width: 100%;
+}
+
+.contentLeft {
+ justify-self: end;
+ width: 100%;
+ max-width: calc(var(--fullwidth-max-width) / 2);
+ padding-left: var(--space-10x);
+}
+
+.contentRight {
+ display: flex;
+ align-items: center;
+ overflow: hidden;
+}
+
+.sectionHeader {
+ display: flex;
+ gap: var(--space-4x);
+ align-items: end;
+ max-width: var(--fullwidth-max-width);
+ margin-bottom: var(--space-8x);
+ margin-left: auto;
+ margin-right: auto;
+ padding: 0 var(--space-10x);
+}
+
+.arrow {
+ padding: 10px;
+ border: 1px solid var(--border);
+ height: fit-content;
+ cursor: pointer;
+ transition: border-color 0.2s ease;
+}
+
+.arrow:hover {
+ border: 1px solid var(--foreground);
+}
+
+.eventList {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-6x);
+}
+
+/* Tablet */
+@media (max-width: 1024px) {
+ .component {
+ display: flex;
+ flex-direction: column-reverse;
+ }
+
+ .contentRight {
+ margin-left: 0;
+ width: 100%;
+ }
+
+ .contentLeft {
+ max-width: 100%;
+ }
+}
+
+/* Mobile */
+@media (max-width: 768px) {
+ .sectionHeader {
+ margin-bottom: var(--space-6x);
+ margin-left: unset;
+ margin-right: unset;
+ & > h2 {
+ font-size: 28px;
+ }
+ }
+
+ .wrapper {
+ margin: 36px 0;
+ }
+}
diff --git a/src/components/CommunityEvents/EventCard.astro b/src/components/CommunityEvents/EventCard.astro
new file mode 100644
index 00000000000..4bf52bef5a0
--- /dev/null
+++ b/src/components/CommunityEvents/EventCard.astro
@@ -0,0 +1,32 @@
+---
+import { SvgArrowRight2, Typography } from "@chainlink/blocks"
+import type { CommunityEvent } from "./types"
+import styles from "./EventCard.module.css"
+
+interface Props {
+ event: CommunityEvent
+}
+
+const { event } = Astro.props
+---
+
+
+
+ {event.month}
+ {event.day}
+
+
+
+
+

+
+
{event.location}
+
+
+
+ {event.title}
+
+

+
+
+
diff --git a/src/components/CommunityEvents/EventCard.module.css b/src/components/CommunityEvents/EventCard.module.css
new file mode 100644
index 00000000000..4613acc0971
--- /dev/null
+++ b/src/components/CommunityEvents/EventCard.module.css
@@ -0,0 +1,80 @@
+.eventCardLink {
+ display: flex;
+ align-items: center;
+ gap: var(--space-4x);
+ border-radius: 0.5rem;
+ text-decoration: none;
+ transition: all 0.3s ease;
+ background-color: var(--background);
+}
+
+.eventCardLink:hover .eventCardH {
+ color: var(--brand) !important;
+}
+
+.eventCardLink:hover .linkArr {
+ opacity: 1 !important;
+}
+
+.eventDate {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ padding: var(--space-2x) var(--space-5x);
+ flex-shrink: 0;
+ border: 1px solid var(--border);
+ background-color: var(--muted);
+}
+
+.eventCardDesc {
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-2x);
+ flex: 1;
+ min-width: 0;
+}
+
+.eventCardCountry {
+ display: flex;
+ align-items: center;
+ gap: var(--space-2x);
+}
+
+.eventCardFlag {
+ width: 16px;
+ max-width: 16px;
+ height: 16px;
+ border-radius: 100%;
+ overflow: hidden;
+ flex-shrink: 0;
+ position: relative;
+}
+
+.coverImg {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ display: block;
+}
+
+.eventCardCountryName {
+ text-transform: uppercase;
+ color: var(--gray-600);
+}
+
+.eventCardHWrap {
+ display: flex;
+ align-items: baseline;
+ gap: var(--space-3x);
+}
+
+.linkArr {
+ opacity: 0;
+}
+
+@media (max-width: 768px) {
+ .eventCardLink {
+ gap: var(--space-3x);
+ }
+}
diff --git a/src/components/CommunityEvents/ImageGallery.astro b/src/components/CommunityEvents/ImageGallery.astro
new file mode 100644
index 00000000000..20c0f26e5ee
--- /dev/null
+++ b/src/components/CommunityEvents/ImageGallery.astro
@@ -0,0 +1,42 @@
+---
+import type { GalleryImage } from "./types"
+import styles from "./ImageGallery.module.css"
+
+interface Props {
+ images: GalleryImage[]
+}
+
+const { images } = Astro.props
+
+// Split images into two rows for the gallery
+const topRowImages = images.slice(0, Math.ceil(images.length / 2))
+const bottomRowImages = images.slice(Math.ceil(images.length / 2))
+
+// Duplicate images for seamless infinite scroll
+const topRowDuplicated = [...topRowImages, ...topRowImages]
+const bottomRowDuplicated = [...bottomRowImages, ...bottomRowImages]
+---
+
+
+
+
+ {
+ topRowDuplicated.map((image) => (
+
+

+
+ ))
+ }
+
+
+
+
+ {
+ bottomRowDuplicated.map((image) => (
+
+

+
+ ))
+ }
+
+
diff --git a/src/components/CommunityEvents/ImageGallery.module.css b/src/components/CommunityEvents/ImageGallery.module.css
new file mode 100644
index 00000000000..edc1af53337
--- /dev/null
+++ b/src/components/CommunityEvents/ImageGallery.module.css
@@ -0,0 +1,70 @@
+.gallery {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: var(--space-5x);
+ overflow: hidden;
+}
+
+.row {
+ display: flex;
+ gap: var(--space-5x);
+ animation: scrollLeft 30s linear infinite;
+ flex-shrink: 0;
+}
+
+.rowReverse {
+ animation: scrollRight 30s linear infinite;
+}
+
+.imageWrapper {
+ width: 16rem;
+ height: 12rem;
+ flex-shrink: 0;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ position: relative;
+}
+
+.image {
+ object-fit: cover;
+ border-radius: 0.5rem;
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ inset: 0;
+}
+
+@keyframes scrollLeft {
+ 0% {
+ transform: translate3d(0%, 0px, 0px);
+ }
+ 100% {
+ transform: translate3d(-50%, 0px, 0px);
+ }
+}
+
+@keyframes scrollRight {
+ 0% {
+ transform: translate3d(-50%, 0px, 0px);
+ }
+ 100% {
+ transform: translate3d(0%, 0px, 0px);
+ }
+}
+
+/* Pause animation on hover */
+.gallery:hover .row {
+ animation-play-state: paused;
+}
+
+@media (max-width: 1024px) {
+ .imageWrapper {
+ width: 12rem;
+ height: 9rem;
+ }
+
+ .gallery {
+ padding: 0 var(--space-10x);
+ }
+}
diff --git a/src/components/CommunityEvents/fetchEvents.ts b/src/components/CommunityEvents/fetchEvents.ts
new file mode 100644
index 00000000000..09ee45fbbef
--- /dev/null
+++ b/src/components/CommunityEvents/fetchEvents.ts
@@ -0,0 +1,53 @@
+import type { CommunityEvent } from "./types.ts"
+
+// Fetch events from Webflow RSS feed
+export const fetchEventsFromRSS = async (): Promise => {
+ try {
+ const response = await fetch("https://chain.link/events-coll/rss.xml")
+ const xml = await response.text()
+
+ // Parse RSS XML manually (lightweight approach without xml2js dependency)
+ const items = xml.match(/- [\s\S]*?<\/item>/g) || []
+ const now = new Date()
+ now.setHours(0, 0, 0, 0) // Set to start of today to include today's events
+
+ const events = items
+ .map((item, index) => {
+ const title = item.match(/(.*?)<\/title>/)?.[1] || ""
+ const pubDate = item.match(/(.*?)<\/pubDate>/)?.[1] || ""
+ const description = item.match(/(.*?)<\/description>/)?.[1] || ""
+ const mediaContent = item.match(/ s.trim())
+ const location = descParts[2] || "Virtual"
+ const eventUrl = descParts[3] || ""
+
+ // Parse date
+ const dateObj = new Date(pubDate)
+ const month = dateObj.toLocaleDateString("en-US", { month: "short" })
+ const day = dateObj.getDate().toString()
+
+ return {
+ id: index.toString(),
+ title: title.replace(/&/g, "&"),
+ date: dateObj.toISOString(),
+ month,
+ day,
+ location,
+ country: location,
+ flagUrl: mediaContent,
+ eventUrl,
+ backgroundColor: "rgb(12, 22, 44)",
+ }
+ })
+ .filter((event) => new Date(event.date) >= now) // Filter out past events
+ .sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime()) // Sort ascending (closest first)
+ .slice(0, 3) // Only take the first 3 events
+
+ return events
+ } catch (error) {
+ console.error("Error fetching events:", error)
+ return []
+ }
+}
diff --git a/src/components/CommunityEvents/types.ts b/src/components/CommunityEvents/types.ts
new file mode 100644
index 00000000000..ab6910a200d
--- /dev/null
+++ b/src/components/CommunityEvents/types.ts
@@ -0,0 +1,18 @@
+export interface CommunityEvent {
+ id: string
+ title: string
+ date: string // ISO date string
+ month: string
+ day: string
+ location: string
+ country: string
+ flagUrl: string
+ eventUrl: string
+ backgroundColor?: string
+}
+
+export interface GalleryImage {
+ id: string
+ imageUrl: string
+ alt: string
+}
diff --git a/src/pages/index.astro b/src/pages/index.astro
index 52e505921c5..8aba79a3fe7 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -7,6 +7,7 @@ import * as CONFIG from "../config"
import Demos from "~/components/Demos.astro"
import { Typography } from "@chainlink/blocks"
import LandingHero from "~/components/LandingHero/LandingHero.astro"
+import CommunityEvents from "~/components/CommunityEvents/CommunityEvents.astro"
const formattedContentTitle = `${CONFIG.PAGE.titleFallback} | ${CONFIG.SITE.title}`
---
@@ -21,7 +22,11 @@ const formattedContentTitle = `${CONFIG.PAGE.titleFallback} | ${CONFIG.SITE.titl
+
+
+
+
Recommended reading
We think you'd love to explore
@@ -142,11 +147,12 @@ const formattedContentTitle = `${CONFIG.PAGE.titleFallback} | ${CONFIG.SITE.titl
.wrapper {
display: flex;
flex-direction: column;
- gap: 82px;
max-width: var(--fullwidth-max-width);
padding: 0 var(--space-10x);
+ gap: 36px;
}
+ /*800px*/
@media (min-width: 50em) {
.hero {
max-width: var(--fullwidth-max-width);
@@ -186,7 +192,13 @@ const formattedContentTitle = `${CONFIG.PAGE.titleFallback} | ${CONFIG.SITE.titl
line-height: var(--space-6x);
margin-bottom: var(--space-2x);
}
+
+ .wrapper {
+ gap: 82px;
+ }
}
+
+ /* 992px */
@media (min-width: 62em) {
.hero {
max-width: min(1200px, calc(100% - 2 * var(--space-16x)));