From 7061f3fd5b777e1561d4e3c0ff67a2a72d1a09b1 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:17:37 -0700 Subject: [PATCH 01/28] Feat: add Panels homepage section components Panel/Row primitives plus the seven homepage sections and a local footer, matching the chosen Panels design exploration. Panels keep constant brand colors in both themes; only the page gutter is theme-aware. globals.css gains an additive tail: a Geist Mono utility for metric values and unlayered 0.15s transition classes (the sitewide unlayered transition rules beat Tailwind's layered duration utilities). Signed-off-by: lauren --- src/app/globals.css | 32 +++ .../home/panels/panel-contribute.tsx | 68 +++++ src/components/home/panels/panel-hero.tsx | 32 +++ src/components/home/panels/panel-join.tsx | 26 ++ src/components/home/panels/panel-members.tsx | 50 ++++ src/components/home/panels/panel-metrics.tsx | 31 +++ src/components/home/panels/panel-mission.tsx | 16 ++ src/components/home/panels/panel-pillars.tsx | 53 ++++ src/components/home/panels/panel.tsx | 234 ++++++++++++++++++ src/components/home/panels/panels-footer.tsx | 53 ++++ src/components/home/panels/types.ts | 25 ++ 11 files changed, 620 insertions(+) create mode 100644 src/components/home/panels/panel-contribute.tsx create mode 100644 src/components/home/panels/panel-hero.tsx create mode 100644 src/components/home/panels/panel-join.tsx create mode 100644 src/components/home/panels/panel-members.tsx create mode 100644 src/components/home/panels/panel-metrics.tsx create mode 100644 src/components/home/panels/panel-mission.tsx create mode 100644 src/components/home/panels/panel-pillars.tsx create mode 100644 src/components/home/panels/panel.tsx create mode 100644 src/components/home/panels/panels-footer.tsx create mode 100644 src/components/home/panels/types.ts diff --git a/src/app/globals.css b/src/app/globals.css index bdae34a..9354093 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -429,3 +429,35 @@ a.inline-flex { .animate-page-appear { animation: page-appear 0.8s cubic-bezier(0.16, 1, 0.3, 1) both; } + +/* ===== Panels homepage (additive only below this line) ===== */ + +/* Geist Mono for the metric values; Tailwind's font-mono is intentionally not remapped. */ +.font-mono-panels { + font-family: var(--font-geist-mono), ui-monospace, "SF Mono", Menlo, monospace; +} + +/* + * Unlayered on purpose: the sitewide :where(a, button, ...) transition rules above + * are unlayered and therefore beat Tailwind's layered duration utilities, so the + * panels' 0.15s motion has to be unlayered too. `translate` is listed alongside + * `transform` because Tailwind v4 translate utilities use the translate property. + */ +.panels-anim { + transition-property: background-color, color, border-color; + transition-duration: 0.15s; + transition-timing-function: ease; +} + +.panels-anim-arrow { + transition-property: transform, translate; + transition-duration: 0.15s; + transition-timing-function: ease; +} + +@media (prefers-reduced-motion: reduce) { + .panels-anim, + .panels-anim-arrow { + transition: none; + } +} diff --git a/src/components/home/panels/panel-contribute.tsx b/src/components/home/panels/panel-contribute.tsx new file mode 100644 index 0000000..ba023c7 --- /dev/null +++ b/src/components/home/panels/panel-contribute.tsx @@ -0,0 +1,68 @@ +import { CircleDollarSign, Code, Heart, UserPlus } from "lucide-react"; + +import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; +import type { ContentRow } from "./types"; + +const CONTRIBUTE_PATHWAYS: ContentRow[] = [ + { + icon: Code, + title: "Contribute to Repos", + description: + "Submit code, RFCs, proposals, documentation, or bug reports to React and 54+ ecosystem libraries. Your contributions directly improve the tools millions of developers use.", + cta: { label: "Browse React Repos", href: "https://github.com/facebook/react" }, + }, + { + icon: CircleDollarSign, + title: "Support Financially", + description: + "Financial support is one way to help fund maintainers, educational resources, and accessibility initiatives. This includes store purchases, direct donations, and sponsorships.", + cta: { label: "Learn More", href: "/store" }, + }, + { + icon: Heart, + title: "Sponsor a Library", + description: + "Directly sponsor your favorite React ecosystem library. Choose from 54 libraries including Redux, TanStack Query, React Router, and more.", + cta: { label: "Browse Libraries", href: "/impact#libraries" }, + }, + { + icon: UserPlus, + title: "Become a Member", + description: + "Join the React Foundation as an official member. Get voting rights on funding decisions, exclusive updates, and recognition in our community.", + cta: { + label: "Apply Now", + href: "https://enrollment.lfx.linuxfoundation.org/?project=react-foundation", + external: true, + }, + }, +]; + +export function PanelContribute() { + return ( + + Become a Contributor + + Join the movement to sustain and grow the React ecosystem. Contribute code, + organize communities, create educational content, or support financially — + every pathway helps build a stronger ecosystem. + + + {CONTRIBUTE_PATHWAYS.map((pathway) => ( + + + ))} + + + ); +} diff --git a/src/components/home/panels/panel-hero.tsx b/src/components/home/panels/panel-hero.tsx new file mode 100644 index 0000000..18146e1 --- /dev/null +++ b/src/components/home/panels/panel-hero.tsx @@ -0,0 +1,32 @@ +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelPlainLink } from "./panel"; + +export function PanelHero() { + return ( + + +
+ Community-driven · Transparent · Impactful +

+ Building the future of React, together. +

+

+ The React Foundation is a community-driven initiative dedicated to sustaining + and advancing the React ecosystem. Join thousands of contributors who code, + teach, organize, and support the tools millions of developers rely on. +

+ + + Get Involved + + + Learn Our Story + + Shop to Support + +
+
+ ); +} diff --git a/src/components/home/panels/panel-join.tsx b/src/components/home/panels/panel-join.tsx new file mode 100644 index 0000000..0840b37 --- /dev/null +++ b/src/components/home/panels/panel-join.tsx @@ -0,0 +1,26 @@ +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelPlainLink } from "./panel"; + +export function PanelJoin() { + return ( + + +
+ Join the Movement +

+ Whether you contribute code, organize meetups, create educational content, + or support financially, there are many ways to participate in building a + sustainable future for the React ecosystem. +

+ + + Get Involved + + + View Impact Reports + + Shop the Store + +
+
+ ); +} diff --git a/src/components/home/panels/panel-members.tsx b/src/components/home/panels/panel-members.tsx new file mode 100644 index 0000000..70c925b --- /dev/null +++ b/src/components/home/panels/panel-members.tsx @@ -0,0 +1,50 @@ +import { Panel, PanelButton, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; +import type { MemberName } from "./types"; + +const MEMBER_COLUMNS: MemberName[][] = [ + ["Meta", "Amazon", "Microsoft", "Huawei"], + ["Software Mansion", "Expo", "Callstack", "Vercel"], +]; + +export function PanelMembers() { + return ( + + Founding Members + + We're grateful to our founding members who believe in sustaining the React + ecosystem and supporting open source maintainers. + +
+ {MEMBER_COLUMNS.map((column, columnIndex) => ( + 0 ? "max-md:border-t max-md:border-[color:var(--panel-rule)]" : undefined + } + > + {column.map((name) => ( + + {name} + + + + + ))} + + ))} +
+
+
+

Become a member

+

+ Help fund React maintainers, education, and ecosystem support. +

+
+ + Become a member + +
+
+ ); +} diff --git a/src/components/home/panels/panel-metrics.tsx b/src/components/home/panels/panel-metrics.tsx new file mode 100644 index 0000000..cbdb230 --- /dev/null +++ b/src/components/home/panels/panel-metrics.tsx @@ -0,0 +1,31 @@ +import { Globe, Layers, ShieldCheck, Users } from "lucide-react"; + +import { Panel, PanelEyebrow, Row, RowArrow, RowList, RowRight } from "./panel"; +import type { MetricRow } from "./types"; + +const METRICS: MetricRow[] = [ + { icon: Layers, label: "Ecosystem libraries", value: "54+", href: "/libraries" }, + { icon: Users, label: "Founding members", value: "8", href: "#members" }, + { icon: Globe, label: "Developers served", value: "Millions", href: "/impact" }, + { icon: ShieldCheck, label: "Transparent funding", value: "100%", href: "/impact" }, +]; + +export function PanelMetrics() { + return ( + + By the numbers + + {METRICS.map((metric) => ( + + + ))} + + + ); +} diff --git a/src/components/home/panels/panel-mission.tsx b/src/components/home/panels/panel-mission.tsx new file mode 100644 index 0000000..4430d3c --- /dev/null +++ b/src/components/home/panels/panel-mission.tsx @@ -0,0 +1,16 @@ +import { Panel, PanelEyebrow } from "./panel"; + +export function PanelMission() { + return ( + + Our Mission +

+ We exist to ensure the React ecosystem{" "} + thrives for generations to come. Through + code contributions, community organizing, educational content creation, and + sustainable funding, we support maintainers, educators, and community organizers + who build the tools millions of developers rely on. +

+
+ ); +} diff --git a/src/components/home/panels/panel-pillars.tsx b/src/components/home/panels/panel-pillars.tsx new file mode 100644 index 0000000..382a0c8 --- /dev/null +++ b/src/components/home/panels/panel-pillars.tsx @@ -0,0 +1,53 @@ +import { BookOpen, Globe, HandHeart } from "lucide-react"; + +import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; +import type { ContentRow } from "./types"; + +const PILLARS: ContentRow[] = [ + { + icon: HandHeart, + title: "Fund Maintainers", + description: + "Direct financial support for the developers maintaining the libraries you depend on every day. Maintainers receive funding through multiple channels including code contributions, sponsorships, and community support.", + cta: { label: "See Impact", href: "/impact" }, + }, + { + icon: BookOpen, + title: "Education & Resources", + description: + "Supporting tutorials, documentation, workshops, and learning materials that help developers master React and its ecosystem.", + cta: { label: "Learn More", href: "/impact" }, + }, + { + icon: Globe, + title: "Global Accessibility", + description: + "Ensuring React remains accessible and inclusive for developers worldwide, regardless of location, background, or resources.", + cta: { label: "Our Commitment", href: "/impact" }, + }, +]; + +export function PanelPillars() { + return ( + + Three Pillars of Impact + Every contribution supports our three core initiatives + + {PILLARS.map((pillar) => ( + + + ))} + + + ); +} diff --git a/src/components/home/panels/panel.tsx b/src/components/home/panels/panel.tsx new file mode 100644 index 0000000..9ffce92 --- /dev/null +++ b/src/components/home/panels/panel.tsx @@ -0,0 +1,234 @@ +import Link from "next/link"; +import type { ReactNode } from "react"; + +import { cn } from "@/lib/cn"; +import type { PanelTone } from "./types"; + +/* + * Panel surfaces keep constant colors in both themes; only the page gutter is + * theme-aware. Tone-specific row/eyebrow colors flow down as CSS variables so + * the row primitives stay tone-agnostic, mirroring the prototype's CSS. + */ +const TONE_CLASSES: Record = { + cyan: cn( + "bg-[#58C4DC]", + "[--panel-rule:rgba(22,24,29,0.2)]", + "[--panel-hover:rgba(255,255,255,0.25)]", + "[--panel-eyebrow:rgba(22,24,29,0.65)]", + "[--panel-sub:rgba(22,24,29,0.7)]", + ), + paper: cn( + "bg-[#F6F7F9]", + "[--panel-rule:#16181D]", + "[--panel-hover:#FFFFFF]", + "[--panel-eyebrow:#5E687E]", + "[--panel-sub:#5E687E]", + ), +}; + +const FOCUS_RING = cn( + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-[#16181D]", +); + +export function Panel({ + tone, + id, + labelledBy, + compact = false, + className, + children, +}: { + tone: PanelTone; + id?: string; + labelledBy: string; + compact?: boolean; + className?: string; + children: ReactNode; +}) { + return ( +
+ {children} +
+ ); +} + +export function PanelEyebrow({ + id, + as: Tag = "h2", + children, +}: { + id?: string; + as?: "h2" | "p"; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +export function PanelSub({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function PanelActions({ children }: { children: ReactNode }) { + return
{children}
; +} + +export function PanelButton({ + href, + variant, + children, +}: { + href: string; + variant: "ink" | "outline"; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +export function PanelPlainLink({ href, children }: { href: string; children: ReactNode }) { + return ( + + {children} + + ); +} + +export function RowList({ className, children }: { className?: string; children: ReactNode }) { + return
{children}
; +} + +export function Row({ + href, + external = false, + bare = false, + className, + children, +}: { + href: string; + external?: boolean; + /** Two-column row without the icon column (founding member rows). */ + bare?: boolean; + className?: string; + children: ReactNode; +}) { + const rowClassName = cn( + "group panels-anim grid items-center gap-x-5 text-[#16181D] hover:bg-[var(--panel-hover)]", + FOCUS_RING, + bare + ? "grid-cols-[minmax(0,1fr)_auto]" + : "grid-cols-[24px_1fr] gap-y-2 md:grid-cols-[24px_minmax(0,1fr)_auto] md:gap-y-0", + className, + ); + + if (href.startsWith("http")) { + return ( + + {children} + + ); + } + + return ( + + {children} + + ); +} + +export function RowRight({ + bare = false, + className, + children, +}: { + bare?: boolean; + className?: string; + children: ReactNode; +}) { + return ( + + {children} + + ); +} + +export function RowArrow() { + return ( + + ); +} + +const ORBIT_SCALES = [12.7, 20.9, 29.1, 37.2]; +const ORBIT_ROTATIONS = [0, 60, 120]; + +/* + * Concentric React-orbit line art. vector-effect="non-scaling-stroke" keeps + * every ring at a true 1px stroke no matter how far the group is scaled up. + */ +export function OrbitMarks({ className }: { className?: string }) { + return ( + + ); +} diff --git a/src/components/home/panels/panels-footer.tsx b/src/components/home/panels/panels-footer.tsx new file mode 100644 index 0000000..b509bb0 --- /dev/null +++ b/src/components/home/panels/panels-footer.tsx @@ -0,0 +1,53 @@ +import Link from "next/link"; + +import { cn } from "@/lib/cn"; + +// The `!` marks are load-bearing: globals.css has unlayered `a:not(.inline-flex) { color: inherit }` +// which otherwise beats layered text utilities on links. +const FOOTER_LINK_CLASS = cn( + "text-[13px] text-[#087EA4]! dark:text-[#58C4DC]!", + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid", + "focus-visible:outline-[#087EA4] dark:focus-visible:outline-[#58C4DC]", +); + +export function PanelsFooter() { + return ( + // pt-8 plus the page's panel gap adds up to the prototype's 48px footer offset. +
+ +

+ Copyright © The Linux Foundation®. All rights reserved. The Linux Foundation has + registered trademarks and uses trademarks. For more information, including terms + of use, privacy policy, and trademark usage, please see our{" "} + + Policies page + + . +

+ +
+ ); +} diff --git a/src/components/home/panels/types.ts b/src/components/home/panels/types.ts new file mode 100644 index 0000000..4833071 --- /dev/null +++ b/src/components/home/panels/types.ts @@ -0,0 +1,25 @@ +import type { LucideIcon } from "lucide-react"; + +export type PanelTone = "cyan" | "paper"; + +export type Cta = { + label: string; + href: string; + external?: boolean; +}; + +export type MetricRow = { + icon: LucideIcon; + label: string; + value: string; + href: string; +}; + +export type ContentRow = { + icon: LucideIcon; + title: string; + description: string; + cta: Cta; +}; + +export type MemberName = string; From db86bdfad3eccbaba8d780d8fce534bbe886fa0f Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:17:37 -0700 Subject: [PATCH 02/28] Feat: compose homepage from Panels sections page.tsx now renders the panel components on a theme-aware gutter. The previous home components stay on disk: /about and the RFDS barrel still import them. Signed-off-by: lauren --- src/app/page.tsx | 53 +++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 30 deletions(-) diff --git a/src/app/page.tsx b/src/app/page.tsx index 4e3efbc..bcbbf64 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,37 +1,30 @@ import type { Metadata } from "next"; -import { Footer } from "@/components/layout/footer"; -import { FoundationHero } from "@/components/home/foundation-hero"; -import { MissionStatement } from "@/components/home/mission-statement"; -import { ThreePillars } from "@/components/home/three-pillars"; -import { BecomeContributor } from "@/components/home/become-contributor"; -import { JoinMovementCTA } from "@/components/home/join-movement-cta"; -import { FoundingMembers } from "@/components/home/founding-members"; + +import { PanelContribute } from "@/components/home/panels/panel-contribute"; +import { PanelHero } from "@/components/home/panels/panel-hero"; +import { PanelJoin } from "@/components/home/panels/panel-join"; +import { PanelMembers } from "@/components/home/panels/panel-members"; +import { PanelMetrics } from "@/components/home/panels/panel-metrics"; +import { PanelMission } from "@/components/home/panels/panel-mission"; +import { PanelPillars } from "@/components/home/panels/panel-pillars"; +import { PanelsFooter } from "@/components/home/panels/panels-footer"; export const metadata: Metadata = { - title: "React Foundation", - description: "Supporting the React ecosystem through community funding and governance.", + title: "React Foundation", + description: "Supporting the React ecosystem through community funding and governance.", }; export default function FoundationHome() { - return ( -
- {/* Background gradient */} -
-
-
- -
-
- - - - - - -
-
- -
-
- ); + return ( +
+ + + + + + + + +
+ ); } From 9919aeeecd1887d28374229701a5034070ac0cc3 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:22:33 -0700 Subject: [PATCH 03/28] Style: remove hero eyebrow line Signed-off-by: lauren --- src/components/home/panels/panel-hero.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/components/home/panels/panel-hero.tsx b/src/components/home/panels/panel-hero.tsx index 18146e1..571f183 100644 --- a/src/components/home/panels/panel-hero.tsx +++ b/src/components/home/panels/panel-hero.tsx @@ -1,14 +1,13 @@ -import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelPlainLink } from "./panel"; +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelPlainLink } from "./panel"; export function PanelHero() { return (
- Community-driven · Transparent · Impactful

Building the future of React, together.

From 245287acfc8c6b6e888f0e23b05d7178f2d78a9e Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:25:17 -0700 Subject: [PATCH 04/28] Style: inset row content so hover arrows clear the edge Signed-off-by: lauren --- src/components/home/panels/panel.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/home/panels/panel.tsx b/src/components/home/panels/panel.tsx index 9ffce92..3278c67 100644 --- a/src/components/home/panels/panel.tsx +++ b/src/components/home/panels/panel.tsx @@ -140,7 +140,9 @@ export function Row({ children: ReactNode; }) { const rowClassName = cn( - "group panels-anim grid items-center gap-x-5 text-[#16181D] hover:bg-[var(--panel-hover)]", + // Inset content from the row edges, with a matching negative margin so + // resting text keeps the panel's alignment and only the hover surface grows. + "group panels-anim -mx-3 grid items-center gap-x-5 rounded-lg px-3 text-[#16181D] hover:bg-[var(--panel-hover)]", FOCUS_RING, bare ? "grid-cols-[minmax(0,1fr)_auto]" From b9cc898c5e98197c8b7cb6579cbdac33c7f07706 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:27:43 -0700 Subject: [PATCH 05/28] Style: widen row inset to 16px Signed-off-by: lauren --- src/components/home/panels/panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/home/panels/panel.tsx b/src/components/home/panels/panel.tsx index 3278c67..72f8566 100644 --- a/src/components/home/panels/panel.tsx +++ b/src/components/home/panels/panel.tsx @@ -142,7 +142,7 @@ export function Row({ const rowClassName = cn( // Inset content from the row edges, with a matching negative margin so // resting text keeps the panel's alignment and only the hover surface grows. - "group panels-anim -mx-3 grid items-center gap-x-5 rounded-lg px-3 text-[#16181D] hover:bg-[var(--panel-hover)]", + "group panels-anim -mx-4 grid items-center gap-x-5 rounded-lg px-4 text-[#16181D] hover:bg-[var(--panel-hover)]", FOCUS_RING, bare ? "grid-cols-[minmax(0,1fr)_auto]" From 70295e5f8811808d525dc3c6fbe6cafc0061d19a Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:33:22 -0700 Subject: [PATCH 06/28] Copy: edit homepage prose The old copy repeated the same contribute-code-teach-organize list in three sections and leaned on mission-speak. Each section now says one concrete thing: the hero says what the foundation funds, the mission says why it exists, pillars say where money goes, contribute lists the four ways in. No new factual claims. Signed-off-by: lauren --- src/components/home/panels/panel-contribute.tsx | 15 +++++++-------- src/components/home/panels/panel-hero.tsx | 6 +++--- src/components/home/panels/panel-join.tsx | 7 +++---- src/components/home/panels/panel-members.tsx | 10 +++++----- src/components/home/panels/panel-mission.tsx | 11 +++++------ src/components/home/panels/panel-pillars.tsx | 10 +++++----- 6 files changed, 28 insertions(+), 31 deletions(-) diff --git a/src/components/home/panels/panel-contribute.tsx b/src/components/home/panels/panel-contribute.tsx index ba023c7..dae54fa 100644 --- a/src/components/home/panels/panel-contribute.tsx +++ b/src/components/home/panels/panel-contribute.tsx @@ -8,28 +8,28 @@ const CONTRIBUTE_PATHWAYS: ContentRow[] = [ icon: Code, title: "Contribute to Repos", description: - "Submit code, RFCs, proposals, documentation, or bug reports to React and 54+ ecosystem libraries. Your contributions directly improve the tools millions of developers use.", + "Submit code, RFCs, documentation, or bug reports to React and 54+ ecosystem libraries.", cta: { label: "Browse React Repos", href: "https://github.com/facebook/react" }, }, { icon: CircleDollarSign, title: "Support Financially", description: - "Financial support is one way to help fund maintainers, educational resources, and accessibility initiatives. This includes store purchases, direct donations, and sponsorships.", + "Store purchases, donations, and sponsorships all go toward maintainers, education, and accessibility work.", cta: { label: "Learn More", href: "/store" }, }, { icon: Heart, title: "Sponsor a Library", description: - "Directly sponsor your favorite React ecosystem library. Choose from 54 libraries including Redux, TanStack Query, React Router, and more.", + "Put money behind a specific library. Choose from 54, including Redux, TanStack Query, and React Router.", cta: { label: "Browse Libraries", href: "/impact#libraries" }, }, { icon: UserPlus, title: "Become a Member", description: - "Join the React Foundation as an official member. Get voting rights on funding decisions, exclusive updates, and recognition in our community.", + "Members get a vote on funding decisions, updates before anyone else, and recognition in the community.", cta: { label: "Apply Now", href: "https://enrollment.lfx.linuxfoundation.org/?project=react-foundation", @@ -41,11 +41,10 @@ const CONTRIBUTE_PATHWAYS: ContentRow[] = [ export function PanelContribute() { return ( - Become a Contributor + Become a contributor - Join the movement to sustain and grow the React ecosystem. Contribute code, - organize communities, create educational content, or support financially — - every pathway helps build a stronger ecosystem. + Write code, give money, sponsor a library you depend on, or join as a member. + Every path funds the same work. {CONTRIBUTE_PATHWAYS.map((pathway) => ( diff --git a/src/components/home/panels/panel-hero.tsx b/src/components/home/panels/panel-hero.tsx index 571f183..9b8a128 100644 --- a/src/components/home/panels/panel-hero.tsx +++ b/src/components/home/panels/panel-hero.tsx @@ -12,9 +12,9 @@ export function PanelHero() { Building the future of React, together.

- The React Foundation is a community-driven initiative dedicated to sustaining - and advancing the React ecosystem. Join thousands of contributors who code, - teach, organize, and support the tools millions of developers rely on. + The React Foundation funds the work that keeps React healthy: maintenance, + documentation, teaching, and the communities around it. Thousands of + contributors already pitch in. Join them.

diff --git a/src/components/home/panels/panel-join.tsx b/src/components/home/panels/panel-join.tsx index 0840b37..b0bcf75 100644 --- a/src/components/home/panels/panel-join.tsx +++ b/src/components/home/panels/panel-join.tsx @@ -5,11 +5,10 @@ export function PanelJoin() {
- Join the Movement + Get started

- Whether you contribute code, organize meetups, create educational content, - or support financially, there are many ways to participate in building a - sustainable future for the React ecosystem. + Write code, teach, organize, or give. However you contribute, React stays + maintained and free for everyone.

diff --git a/src/components/home/panels/panel-members.tsx b/src/components/home/panels/panel-members.tsx index 70c925b..9d52ffb 100644 --- a/src/components/home/panels/panel-members.tsx +++ b/src/components/home/panels/panel-members.tsx @@ -9,10 +9,10 @@ const MEMBER_COLUMNS: MemberName[][] = [ export function PanelMembers() { return ( - Founding Members + Founding members - We're grateful to our founding members who believe in sustaining the React - ecosystem and supporting open source maintainers. + Eight companies funded the foundation from day one. We're grateful to every + one of them.
{MEMBER_COLUMNS.map((column, columnIndex) => ( @@ -36,9 +36,9 @@ export function PanelMembers() {
-

Become a member

+

Membership

- Help fund React maintainers, education, and ecosystem support. + Help fund React maintainers, education, and accessibility work.

diff --git a/src/components/home/panels/panel-mission.tsx b/src/components/home/panels/panel-mission.tsx index 4430d3c..6a807bc 100644 --- a/src/components/home/panels/panel-mission.tsx +++ b/src/components/home/panels/panel-mission.tsx @@ -3,13 +3,12 @@ import { Panel, PanelEyebrow } from "./panel"; export function PanelMission() { return ( - Our Mission + Our mission

- We exist to ensure the React ecosystem{" "} - thrives for generations to come. Through - code contributions, community organizing, educational content creation, and - sustainable funding, we support maintainers, educators, and community organizers - who build the tools millions of developers rely on. + React now belongs to its community, not any single company. The foundation + exists to make that permanent: funded + maintainers, solid documentation, and places where the next generation of React + developers learns.

); diff --git a/src/components/home/panels/panel-pillars.tsx b/src/components/home/panels/panel-pillars.tsx index 382a0c8..fc0394a 100644 --- a/src/components/home/panels/panel-pillars.tsx +++ b/src/components/home/panels/panel-pillars.tsx @@ -8,21 +8,21 @@ const PILLARS: ContentRow[] = [ icon: HandHeart, title: "Fund Maintainers", description: - "Direct financial support for the developers maintaining the libraries you depend on every day. Maintainers receive funding through multiple channels including code contributions, sponsorships, and community support.", + "Direct financial support for the developers who maintain the libraries you depend on every day.", cta: { label: "See Impact", href: "/impact" }, }, { icon: BookOpen, title: "Education & Resources", description: - "Supporting tutorials, documentation, workshops, and learning materials that help developers master React and its ecosystem.", + "Tutorials, documentation, workshops, and learning materials that help developers master React.", cta: { label: "Learn More", href: "/impact" }, }, { icon: Globe, title: "Global Accessibility", description: - "Ensuring React remains accessible and inclusive for developers worldwide, regardless of location, background, or resources.", + "React stays free and learnable for developers everywhere, regardless of location, background, or resources.", cta: { label: "Our Commitment", href: "/impact" }, }, ]; @@ -30,8 +30,8 @@ const PILLARS: ContentRow[] = [ export function PanelPillars() { return ( - Three Pillars of Impact - Every contribution supports our three core initiatives + What we fund + Every contribution lands in one of three programs. {PILLARS.map((pillar) => ( From 5d5ccfbe974db43957c0af4740d9030f6ceb7b84 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:36:26 -0700 Subject: [PATCH 07/28] Style: square row hover corners Signed-off-by: lauren --- src/components/home/panels/panel.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/home/panels/panel.tsx b/src/components/home/panels/panel.tsx index 72f8566..d28ebc5 100644 --- a/src/components/home/panels/panel.tsx +++ b/src/components/home/panels/panel.tsx @@ -142,7 +142,7 @@ export function Row({ const rowClassName = cn( // Inset content from the row edges, with a matching negative margin so // resting text keeps the panel's alignment and only the hover surface grows. - "group panels-anim -mx-4 grid items-center gap-x-5 rounded-lg px-4 text-[#16181D] hover:bg-[var(--panel-hover)]", + "group panels-anim -mx-4 grid items-center gap-x-5 px-4 text-[#16181D] hover:bg-[var(--panel-hover)]", FOCUS_RING, bare ? "grid-cols-[minmax(0,1fr)_auto]" From 28fd20a2987fc7826bc6479d6c9e14a939dc82b8 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:40:41 -0700 Subject: [PATCH 08/28] Feat: float the header as a panel on the homepage Route-aware variant inside the shared Header: on / it becomes a floating rounded panel aligned to the page's panel grid, with a single-line lockup and an ink sign-in button; every other route keeps the existing classes byte-for-byte. The panel surface is theme-aware (paper/ink-soft) because it hosts theme-dependent controls like the toggle and avatar. Signed-off-by: lauren --- src/components/layout/header.tsx | 76 ++++++++++++++++++++++++-------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 6162f5e..3be949f 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -18,6 +18,8 @@ export function Header() { const [isAdmin, setIsAdmin] = useState(false); const isStorePage = pathname?.startsWith("/store"); const isComingSoonPage = pathname === "/coming-soon"; + // The redesigned homepage renders the header as a floating panel; other routes keep the classic bar. + const isHome = pathname === "/"; // Check admin status when user session changes useEffect(() => { @@ -45,33 +47,61 @@ export function Header() { }, [session?.user?.email]); return ( -
-
+
+
{/* Logo */} -
-
- + {isHome ? ( + +
React Foundation logo - -
-
- -

- React Foundation -

-

- {isStorePage ? "Official Store" : "Supporting the Ecosystem"} -

- +
+

React Foundation

+ + ) : ( +
+
+ + React Foundation logo + +
+
+ +

+ React Foundation +

+

+ {isStorePage ? "Official Store" : "Supporting the Ecosystem"} +

+ +
-
+ )} {/* Desktop Navigation (hidden on mobile) */}
@@ -150,6 +180,14 @@ export function Header() { href="/profile" className="transition hover:border-primary/50 hover:shadow-lg hover:shadow-primary/20" /> + ) : isHome ? ( + // Ink button per the panels language; `!` outranks the unlayered `a.inline-flex { color: currentColor }` rule. + + Sign in + ) : ( Sign in From 3c8a7f86ff3ae29853a2c8af6c8cb82a93e9e63a Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:49:27 -0700 Subject: [PATCH 09/28] Refactor: promote panel primitives to src/components/panels The rest of the site is adopting the Panels language, so the primitives and the panels footer move out of home/. Home sections stay put. Signed-off-by: lauren --- src/app/page.tsx | 2 +- src/components/home/panels/panel-contribute.tsx | 4 ++-- src/components/home/panels/panel-hero.tsx | 2 +- src/components/home/panels/panel-join.tsx | 2 +- src/components/home/panels/panel-members.tsx | 4 ++-- src/components/home/panels/panel-metrics.tsx | 4 ++-- src/components/home/panels/panel-mission.tsx | 2 +- src/components/home/panels/panel-pillars.tsx | 4 ++-- src/components/{home => }/panels/panel.tsx | 0 src/components/{home => }/panels/panels-footer.tsx | 0 src/components/{home => }/panels/types.ts | 0 11 files changed, 12 insertions(+), 12 deletions(-) rename src/components/{home => }/panels/panel.tsx (100%) rename src/components/{home => }/panels/panels-footer.tsx (100%) rename src/components/{home => }/panels/types.ts (100%) diff --git a/src/app/page.tsx b/src/app/page.tsx index bcbbf64..0d879d7 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -7,7 +7,7 @@ import { PanelMembers } from "@/components/home/panels/panel-members"; import { PanelMetrics } from "@/components/home/panels/panel-metrics"; import { PanelMission } from "@/components/home/panels/panel-mission"; import { PanelPillars } from "@/components/home/panels/panel-pillars"; -import { PanelsFooter } from "@/components/home/panels/panels-footer"; +import { PanelsFooter } from "@/components/panels/panels-footer"; export const metadata: Metadata = { title: "React Foundation", diff --git a/src/components/home/panels/panel-contribute.tsx b/src/components/home/panels/panel-contribute.tsx index dae54fa..a1cc935 100644 --- a/src/components/home/panels/panel-contribute.tsx +++ b/src/components/home/panels/panel-contribute.tsx @@ -1,7 +1,7 @@ import { CircleDollarSign, Code, Heart, UserPlus } from "lucide-react"; -import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; -import type { ContentRow } from "./types"; +import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; +import type { ContentRow } from "@/components/panels/types"; const CONTRIBUTE_PATHWAYS: ContentRow[] = [ { diff --git a/src/components/home/panels/panel-hero.tsx b/src/components/home/panels/panel-hero.tsx index 9b8a128..d3fd62b 100644 --- a/src/components/home/panels/panel-hero.tsx +++ b/src/components/home/panels/panel-hero.tsx @@ -1,4 +1,4 @@ -import { OrbitMarks, Panel, PanelActions, PanelButton, PanelPlainLink } from "./panel"; +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelPlainLink } from "@/components/panels/panel"; export function PanelHero() { return ( diff --git a/src/components/home/panels/panel-join.tsx b/src/components/home/panels/panel-join.tsx index b0bcf75..eb20264 100644 --- a/src/components/home/panels/panel-join.tsx +++ b/src/components/home/panels/panel-join.tsx @@ -1,4 +1,4 @@ -import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelPlainLink } from "./panel"; +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelPlainLink } from "@/components/panels/panel"; export function PanelJoin() { return ( diff --git a/src/components/home/panels/panel-members.tsx b/src/components/home/panels/panel-members.tsx index 9d52ffb..8e5739f 100644 --- a/src/components/home/panels/panel-members.tsx +++ b/src/components/home/panels/panel-members.tsx @@ -1,5 +1,5 @@ -import { Panel, PanelButton, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; -import type { MemberName } from "./types"; +import { Panel, PanelButton, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; +import type { MemberName } from "@/components/panels/types"; const MEMBER_COLUMNS: MemberName[][] = [ ["Meta", "Amazon", "Microsoft", "Huawei"], diff --git a/src/components/home/panels/panel-metrics.tsx b/src/components/home/panels/panel-metrics.tsx index cbdb230..2346f3a 100644 --- a/src/components/home/panels/panel-metrics.tsx +++ b/src/components/home/panels/panel-metrics.tsx @@ -1,7 +1,7 @@ import { Globe, Layers, ShieldCheck, Users } from "lucide-react"; -import { Panel, PanelEyebrow, Row, RowArrow, RowList, RowRight } from "./panel"; -import type { MetricRow } from "./types"; +import { Panel, PanelEyebrow, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; +import type { MetricRow } from "@/components/panels/types"; const METRICS: MetricRow[] = [ { icon: Layers, label: "Ecosystem libraries", value: "54+", href: "/libraries" }, diff --git a/src/components/home/panels/panel-mission.tsx b/src/components/home/panels/panel-mission.tsx index 6a807bc..fff2747 100644 --- a/src/components/home/panels/panel-mission.tsx +++ b/src/components/home/panels/panel-mission.tsx @@ -1,4 +1,4 @@ -import { Panel, PanelEyebrow } from "./panel"; +import { Panel, PanelEyebrow } from "@/components/panels/panel"; export function PanelMission() { return ( diff --git a/src/components/home/panels/panel-pillars.tsx b/src/components/home/panels/panel-pillars.tsx index fc0394a..a856055 100644 --- a/src/components/home/panels/panel-pillars.tsx +++ b/src/components/home/panels/panel-pillars.tsx @@ -1,7 +1,7 @@ import { BookOpen, Globe, HandHeart } from "lucide-react"; -import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "./panel"; -import type { ContentRow } from "./types"; +import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; +import type { ContentRow } from "@/components/panels/types"; const PILLARS: ContentRow[] = [ { diff --git a/src/components/home/panels/panel.tsx b/src/components/panels/panel.tsx similarity index 100% rename from src/components/home/panels/panel.tsx rename to src/components/panels/panel.tsx diff --git a/src/components/home/panels/panels-footer.tsx b/src/components/panels/panels-footer.tsx similarity index 100% rename from src/components/home/panels/panels-footer.tsx rename to src/components/panels/panels-footer.tsx diff --git a/src/components/home/panels/types.ts b/src/components/panels/types.ts similarity index 100% rename from src/components/home/panels/types.ts rename to src/components/panels/types.ts From 6aba885dc56e17f1022963f74c513ff9c5ef26ee Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 00:59:49 -0700 Subject: [PATCH 10/28] Feat: restyle membership, legal, and sign-in pages to Panels Marketing copy, legal text, and auth logic unchanged; legal pages drop the prose classes (globals forces prose colors per theme, which breaks on constant-color panels) in favor of explicit ink-on-paper type. Signed-off-by: lauren --- src/app/auth/signin/page.tsx | 176 ++++++++------- src/app/become-a-member/page.tsx | 367 +++++++++++++++---------------- src/app/privacy/page.tsx | 331 ++++++++++++++-------------- src/app/terms/page.tsx | 189 ++++++++-------- 4 files changed, 523 insertions(+), 540 deletions(-) diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index b3328e8..ab9f35d 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -5,94 +5,102 @@ import { useSearchParams } from "next/navigation"; import Image from "next/image"; import Link from "next/link"; -export default function SignInPage() { - const searchParams = useSearchParams(); - const callbackUrl = searchParams?.get("callbackUrl") || "/"; +import { Panel } from "@/components/panels/panel"; +import { cn } from "@/lib/cn"; + +const FOCUS_RING = cn( + "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-[#16181D]", +); - return ( -
- {/* Background gradient */} -
-
-
+export default function SignInPage() { + const searchParams = useSearchParams(); + const callbackUrl = searchParams?.get("callbackUrl") || "/"; -
- {/* Logo and Title */} -
-
- React Foundation -
-

Welcome Back

-

- Sign in to access your React Foundation account -

-
+ return ( +
+
+ +
+
+ React Foundation +
+

+ Welcome Back +

+

+ Sign in to access your React Foundation account +

+
- {/* Sign In Options */} -
- {/* GitHub */} - +
+ - {/* GitLab */} - + -

- We verify your contributions to the React ecosystem -

-
+

+ We verify your contributions to the React ecosystem +

+
+
- {/* Back Link */} -
- - ← Back to home - -
-
-
- ); +
+ + ← Back to home + +
+
+
+ ); } diff --git a/src/app/become-a-member/page.tsx b/src/app/become-a-member/page.tsx index 433f3f1..e1b60e9 100644 --- a/src/app/become-a-member/page.tsx +++ b/src/app/become-a-member/page.tsx @@ -1,213 +1,190 @@ import type { Metadata } from "next"; -import Link from "next/link"; -import { ButtonLink } from "@/components/ui/button"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { Footer } from "@/components/layout/footer"; + +import { + Panel, + PanelActions, + PanelButton, + PanelEyebrow, + PanelPlainLink, + PanelSub, + RowList, +} from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; const ENROLLMENT_URL = - "https://enrollment.lfx.linuxfoundation.org/?project=react-foundation"; + "https://enrollment.lfx.linuxfoundation.org/?project=react-foundation"; const memberReasons = [ - { - title: "Sustain core infrastructure", - description: - "Help fund the maintainers, tooling, and shared services that millions of developers and teams rely on every day.", - }, - { - title: "Invest in ecosystem health", - description: - "Support programs that strengthen libraries, education, security, accessibility, and long-term project resilience.", - }, - { - title: "Shape responsible growth", - description: - "Join a member community aligned around transparent governance and a healthy future for React across companies and communities.", - }, + { + title: "Sustain core infrastructure", + description: + "Help fund the maintainers, tooling, and shared services that millions of developers and teams rely on every day.", + }, + { + title: "Invest in ecosystem health", + description: + "Support programs that strengthen libraries, education, security, accessibility, and long-term project resilience.", + }, + { + title: "Shape responsible growth", + description: + "Join a member community aligned around transparent governance and a healthy future for React across companies and communities.", + }, ]; const membershipBenefits = [ - "Visible support for React's independent ecosystem stewardship", - "Participation in foundation member conversations and priorities", - "Opportunities to collaborate on ecosystem sustainability programs", - "Connection with maintainers, educators, tool authors, and platform teams", + "Visible support for React's independent ecosystem stewardship", + "Participation in foundation member conversations and priorities", + "Opportunities to collaborate on ecosystem sustainability programs", + "Connection with maintainers, educators, tool authors, and platform teams", ]; const investmentAreas = [ - "Maintainer support", - "Community education", - "Ecosystem tooling", - "Governance operations", - "Accessibility and inclusion", - "Long-term project resilience", + "Maintainer support", + "Community education", + "Ecosystem tooling", + "Governance operations", + "Accessibility and inclusion", + "Long-term project resilience", ]; export const metadata: Metadata = { - title: "Become a Member | React Foundation", - description: - "Become a React Foundation member and help sustain the ecosystem, maintainers, and community programs that support React's future.", + title: "Become a Member | React Foundation", + description: + "Become a React Foundation member and help sustain the ecosystem, maintainers, and community programs that support React's future.", }; export default function BecomeMemberPage() { - return ( -
-
-
-
- -
-
-
-
-
-

- Help sustain the future of React. -

-

- React is more than a library. It is an ecosystem of maintainers, - educators, tools, frameworks, and community spaces that help teams - build for the web and beyond. Members help keep that - ecosystem healthy, independent, and durable. -

-
-
- - Join as a member - - - See what membership supports - -
-
- - -
- - -
-
-

- Members fund the shared work behind React. -

-

- Your support helps the foundation invest where individual projects - and volunteer maintainers should not have to carry the burden alone. -

-
- -
- {memberReasons.map((reason) => ( -
-

- {reason.title} -

-

- {reason.description} -

-
- ))} -
-
-
- - -
-
- Member Value -

- Built for organizations that depend on React. -

-

- Membership is for companies, platforms, agencies, and teams who - want React to remain a strong open ecosystem with clear stewardship - and practical support for the people doing the work. -

-
- -
- {membershipBenefits.map((benefit) => ( -
- {benefit} -
- ))} -
-
-
- - -
-
-

- Practical support for a broad ecosystem. -

-

- React Foundation membership helps create capacity for work that - benefits the whole ecosystem, not just one product roadmap or one - organization's priorities. -

-
- -
- {investmentAreas.map((area) => ( -
- {area} -
- ))} -
-
-
- - -
- Start Membership -

- Join the organizations helping React stay healthy for everyone. -

-

- Begin with the Linux Foundation enrollment form, or return to the - foundation site to learn more about our mission and governance. -

-
- - Join now - - - Learn about the foundation - -
-
-
-
-
- -
-
- ); + return ( +
+ +
+
+

+ Help sustain the future of React. +

+

+ React is more than a library. It is an ecosystem of maintainers, + educators, tools, frameworks, and community spaces that help teams + build for the web and beyond. Members help keep that ecosystem + healthy, independent, and durable. +

+ + + Join as a member + + + See what membership supports + + +
+ + +
+
+ + + Membership +

+ Members fund the shared work behind React. +

+ + Your support helps the foundation invest where individual projects + and volunteer maintainers should not have to carry the burden alone. + + + {memberReasons.map((reason) => ( +
+

{reason.title}

+

+ {reason.description} +

+
+ ))} +
+
+ + +
+
+ Member value +

+ Built for organizations that depend on React. +

+ + Membership is for companies, platforms, agencies, and teams who + want React to remain a strong open ecosystem with clear stewardship + and practical support for the people doing the work. + +
+ +
+ {membershipBenefits.map((benefit) => ( +
+ {benefit} +
+ ))} +
+
+
+ + + Investment areas +

+ Practical support for a broad ecosystem. +

+ + React Foundation membership helps create capacity for work that + benefits the whole ecosystem, not just one product roadmap or one + organization's priorities. + + + {investmentAreas.map((area) => ( +
+ {area} +
+ ))} +
+
+ + +
+ Start membership +

+ Join the organizations helping React stay healthy for everyone. +

+

+ Begin with the Linux Foundation enrollment form, or return to the + foundation site to learn more about our mission and governance. +

+
+ + Join now + + Learn about the foundation +
+
+
+ + +
+ ); } diff --git a/src/app/privacy/page.tsx b/src/app/privacy/page.tsx index 8b4f69f..2f355a1 100644 --- a/src/app/privacy/page.tsx +++ b/src/app/privacy/page.tsx @@ -1,174 +1,173 @@ /* eslint-disable react/no-unescaped-entities */ import type { Metadata } from "next"; -import { Footer } from "@/components/layout/footer"; + +import { Panel } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; export const metadata: Metadata = { - title: "Privacy Policy", - description: "Privacy Policy for React Foundation", + title: "Privacy Policy", + description: "Privacy Policy for React Foundation", }; export default function PrivacyPage() { - return ( -
-
-
-
- -
-
-

Privacy Policy

-

- Last updated: October 21, 2025 -

- -
-
-

1. Introduction

-

- React Foundation ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and use our services. -

-

- Please read this Privacy Policy carefully. If you do not agree with the terms of this Privacy Policy, please do not access the site. -

-
- -
-

2. Information We Collect

- -

2.1 Personal Information

-

- When you authenticate using GitHub or GitLab OAuth, we collect: -

-
    -
  • Your name
  • -
  • Your email address
  • -
  • Your GitHub or GitLab username
  • -
  • Your public profile information from the respective platform
  • -
- -

2.2 Local Storage

-

- We use browser local storage to save your contributor username preference for convenience. This data is stored only on your device and is not transmitted to our servers. -

- -

2.3 Session Data

-

- We use session cookies to maintain your authenticated state. These are essential for the functionality of the authentication system. -

-
- -
-

3. How We Use Your Information

-

- We use the information we collect in the following ways: -

-
    -
  • To provide, operate, and maintain our website
  • -
  • To authenticate and authorize your access to features
  • -
  • To display your contributor status and progress
  • -
  • To personalize your experience on our website
  • -
  • To communicate with you about updates and announcements
  • -
-
- -
-

4. Data Sharing and Disclosure

-

- We do not share, sell, rent, or trade your personal information with third parties for their commercial purposes. -

-

- We may share information only in the following limited circumstances: -

-
    -
  • Service Providers: We use third-party services (GitHub, GitLab for authentication; Shopify for e-commerce) that may have access to your information to perform tasks on our behalf.
  • -
  • Legal Requirements: We may disclose your information if required to do so by law or in response to valid requests by public authorities.
  • -
-
- -
-

5. Third-Party Services

- -

5.1 Authentication

-

- We use GitHub and GitLab OAuth for authentication. When you authenticate, you are subject to their respective privacy policies: -

- - -

5.2 E-commerce

-

- Our online store is powered by Shopify. When you make a purchase, Shopify collects and processes your payment and shipping information. Please review Shopify's Privacy Policy for more information. -

-
- -
-

6. Tracking and Analytics

-

- We do not use any tracking technologies, analytics tools, or third-party advertising services. We do not track your browsing behavior across websites or collect data for advertising purposes. -

-
- -
-

7. Data Security

-

- We implement appropriate technical and organizational security measures to protect your personal information. However, please note that no method of transmission over the internet or method of electronic storage is 100% secure. -

-

- Your authentication is handled by NextAuth.js, a secure authentication library, and we rely on GitHub and GitLab's OAuth implementations for secure authentication. -

-
- -
-

8. Data Retention

-

- We retain your personal information only for as long as necessary to provide you with our services and as described in this Privacy Policy. Session data is retained only for the duration of your authenticated session. -

-
- -
-

9. Your Rights

-

- You have the right to: -

-
    -
  • Access the personal information we hold about you
  • -
  • Request correction of inaccurate information
  • -
  • Request deletion of your personal information
  • -
  • Revoke OAuth access through your GitHub or GitLab account settings
  • -
  • Clear local storage data stored in your browser
  • -
-
- -
-

10. Children's Privacy

-

- Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us. -

-
- -
-

11. Changes to This Privacy Policy

-

- We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date at the top of this Privacy Policy. -

-

- You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page. -

-
- -
-

12. Contact Us

-

- If you have any questions about this Privacy Policy, please contact us through our GitHub repository or official communication channels. -

-
-
-
-
- -
-
- ); + return ( +
+ +

+ Privacy Policy +

+

+ Last updated: October 21, 2025 +

+ +
+

1. Introduction

+

+ React Foundation ("we", "our", or "us") is committed to protecting your privacy. This Privacy Policy explains how we collect, use, disclose, and safeguard your information when you visit our website and use our services. +

+

+ Please read this Privacy Policy carefully. If you do not agree with the terms of this Privacy Policy, please do not access the site. +

+
+ +
+

2. Information We Collect

+ +

2.1 Personal Information

+

+ When you authenticate using GitHub or GitLab OAuth, we collect: +

+
    +
  • Your name
  • +
  • Your email address
  • +
  • Your GitHub or GitLab username
  • +
  • Your public profile information from the respective platform
  • +
+ +

2.2 Local Storage

+

+ We use browser local storage to save your contributor username preference for convenience. This data is stored only on your device and is not transmitted to our servers. +

+ +

2.3 Session Data

+

+ We use session cookies to maintain your authenticated state. These are essential for the functionality of the authentication system. +

+
+ +
+

3. How We Use Your Information

+

+ We use the information we collect in the following ways: +

+
    +
  • To provide, operate, and maintain our website
  • +
  • To authenticate and authorize your access to features
  • +
  • To display your contributor status and progress
  • +
  • To personalize your experience on our website
  • +
  • To communicate with you about updates and announcements
  • +
+
+ +
+

4. Data Sharing and Disclosure

+

+ We do not share, sell, rent, or trade your personal information with third parties for their commercial purposes. +

+

+ We may share information only in the following limited circumstances: +

+
    +
  • Service Providers: We use third-party services (GitHub, GitLab for authentication; Shopify for e-commerce) that may have access to your information to perform tasks on our behalf.
  • +
  • Legal Requirements: We may disclose your information if required to do so by law or in response to valid requests by public authorities.
  • +
+
+ +
+

5. Third-Party Services

+ +

5.1 Authentication

+

+ We use GitHub and GitLab OAuth for authentication. When you authenticate, you are subject to their respective privacy policies: +

+ + +

5.2 E-commerce

+

+ Our online store is powered by Shopify. When you make a purchase, Shopify collects and processes your payment and shipping information. Please review Shopify's Privacy Policy for more information. +

+
+ +
+

6. Tracking and Analytics

+

+ We do not use any tracking technologies, analytics tools, or third-party advertising services. We do not track your browsing behavior across websites or collect data for advertising purposes. +

+
+ +
+

7. Data Security

+

+ We implement appropriate technical and organizational security measures to protect your personal information. However, please note that no method of transmission over the internet or method of electronic storage is 100% secure. +

+

+ Your authentication is handled by NextAuth.js, a secure authentication library, and we rely on GitHub and GitLab's OAuth implementations for secure authentication. +

+
+ +
+

8. Data Retention

+

+ We retain your personal information only for as long as necessary to provide you with our services and as described in this Privacy Policy. Session data is retained only for the duration of your authenticated session. +

+
+ +
+

9. Your Rights

+

+ You have the right to: +

+
    +
  • Access the personal information we hold about you
  • +
  • Request correction of inaccurate information
  • +
  • Request deletion of your personal information
  • +
  • Revoke OAuth access through your GitHub or GitLab account settings
  • +
  • Clear local storage data stored in your browser
  • +
+
+ +
+

10. Children's Privacy

+

+ Our Service does not address anyone under the age of 13. We do not knowingly collect personally identifiable information from children under 13. If you are a parent or guardian and you are aware that your child has provided us with personal information, please contact us. +

+
+ +
+

11. Changes to This Privacy Policy

+

+ We may update our Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy on this page and updating the "Last updated" date at the top of this Privacy Policy. +

+

+ You are advised to review this Privacy Policy periodically for any changes. Changes to this Privacy Policy are effective when they are posted on this page. +

+
+ +
+

12. Contact Us

+

+ If you have any questions about this Privacy Policy, please contact us through our GitHub repository or official communication channels. +

+
+
+ + +
+ ); } diff --git a/src/app/terms/page.tsx b/src/app/terms/page.tsx index 1392c53..316654f 100644 --- a/src/app/terms/page.tsx +++ b/src/app/terms/page.tsx @@ -1,114 +1,113 @@ /* eslint-disable react/no-unescaped-entities */ import type { Metadata } from "next"; -import { Footer } from "@/components/layout/footer"; + +import { Panel } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; export const metadata: Metadata = { - title: "Terms of Service", - description: "Terms of Service for React Foundation", + title: "Terms of Service", + description: "Terms of Service for React Foundation", }; export default function TermsPage() { - return ( -
-
-
-
- -
-
-

Terms of Service

-

- Last updated: October 21, 2025 -

+ return ( +
+ +

+ Terms of Service +

+

+ Last updated: October 21, 2025 +

-
-
-

1. Acceptance of Terms

-

- By accessing and using the React Foundation website (the "Service"), you accept and agree to be bound by the terms and provision of this agreement. If you do not agree to these Terms of Service, please do not use the Service. -

-
+
+

1. Acceptance of Terms

+

+ By accessing and using the React Foundation website (the "Service"), you accept and agree to be bound by the terms and provision of this agreement. If you do not agree to these Terms of Service, please do not use the Service. +

+
-
-

2. Description of Service

-

- React Foundation provides a platform to support the React ecosystem through community funding, transparent governance, and official merchandise. The Service includes: -

-
    -
  • Information about React Foundation and its mission
  • -
  • An online store for official React Foundation merchandise
  • -
  • User authentication via GitHub and GitLab
  • -
  • Contributor progress tracking and status information
  • -
  • Community updates and announcements
  • -
-
+
+

2. Description of Service

+

+ React Foundation provides a platform to support the React ecosystem through community funding, transparent governance, and official merchandise. The Service includes: +

+
    +
  • Information about React Foundation and its mission
  • +
  • An online store for official React Foundation merchandise
  • +
  • User authentication via GitHub and GitLab
  • +
  • Contributor progress tracking and status information
  • +
  • Community updates and announcements
  • +
+
-
-

3. User Accounts

-

- To access certain features of the Service, you may be required to authenticate using your GitHub or GitLab account. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. -

-
+
+

3. User Accounts

+

+ To access certain features of the Service, you may be required to authenticate using your GitHub or GitLab account. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. +

+
-
-

4. Acceptable Use

-

- You agree to use the Service only for lawful purposes and in a way that does not infringe the rights of, restrict, or inhibit anyone else's use and enjoyment of the Service. Prohibited behavior includes: -

-
    -
  • Harassing or causing distress or inconvenience to any other user
  • -
  • Transmitting obscene or offensive content
  • -
  • Disrupting the normal flow of dialogue within the Service
  • -
  • Attempting to gain unauthorized access to the Service or its systems
  • -
-
+
+

4. Acceptable Use

+

+ You agree to use the Service only for lawful purposes and in a way that does not infringe the rights of, restrict, or inhibit anyone else's use and enjoyment of the Service. Prohibited behavior includes: +

+
    +
  • Harassing or causing distress or inconvenience to any other user
  • +
  • Transmitting obscene or offensive content
  • +
  • Disrupting the normal flow of dialogue within the Service
  • +
  • Attempting to gain unauthorized access to the Service or its systems
  • +
+
-
-

5. Intellectual Property

-

- The Service and its original content, features, and functionality are owned by React Foundation and are protected by international copyright, trademark, patent, trade secret, and other intellectual property laws. The React logo and other marks are trademarks of Meta Platforms, Inc., and are used with permission. -

-
+
+

5. Intellectual Property

+

+ The Service and its original content, features, and functionality are owned by React Foundation and are protected by international copyright, trademark, patent, trade secret, and other intellectual property laws. The React logo and other marks are trademarks of Meta Platforms, Inc., and are used with permission. +

+
-
-

6. Purchases and Payment

-

- If you wish to purchase any product or service made available through the Service, you may be asked to supply certain information relevant to your purchase. Payment processing is handled by third-party payment processors. We are not responsible for the security of payment information submitted to these third parties. -

-
+
+

6. Purchases and Payment

+

+ If you wish to purchase any product or service made available through the Service, you may be asked to supply certain information relevant to your purchase. Payment processing is handled by third-party payment processors. We are not responsible for the security of payment information submitted to these third parties. +

+
-
-

7. Limitation of Liability

-

- In no event shall React Foundation, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential, or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from your access to or use of or inability to access or use the Service. -

-
+
+

7. Limitation of Liability

+

+ In no event shall React Foundation, nor its directors, employees, partners, agents, suppliers, or affiliates, be liable for any indirect, incidental, special, consequential, or punitive damages, including without limitation, loss of profits, data, use, goodwill, or other intangible losses, resulting from your access to or use of or inability to access or use the Service. +

+
-
-

8. Disclaimer

-

- Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and "AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether express or implied, including, but not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement, or course of performance. -

-
+
+

8. Disclaimer

+

+ Your use of the Service is at your sole risk. The Service is provided on an "AS IS" and "AS AVAILABLE" basis. The Service is provided without warranties of any kind, whether express or implied, including, but not limited to, implied warranties of merchantability, fitness for a particular purpose, non-infringement, or course of performance. +

+
-
-

9. Changes to Terms

-

- We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material, we will provide at least 30 days' notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion. -

-
+
+

9. Changes to Terms

+

+ We reserve the right, at our sole discretion, to modify or replace these Terms at any time. If a revision is material, we will provide at least 30 days' notice prior to any new terms taking effect. What constitutes a material change will be determined at our sole discretion. +

+
-
-

10. Contact Information

-

- If you have any questions about these Terms of Service, please contact us through our GitHub repository or official communication channels. -

-
-
-
-
+
+

10. Contact Information

+

+ If you have any questions about these Terms of Service, please contact us through our GitHub repository or official communication channels. +

+
+ -
-
- ); + +
+ ); } From 77b5c0fbff9c27ef7415f3cbd5c893e9132931ec Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 01:06:38 -0700 Subject: [PATCH 11/28] Feat: restyle updates and authors routes to Panels MDX bodies render on the theme-aware gutter rather than inside a constant-color panel, so the forced prose colors in globals.css stay correct in both themes. Also removes a stopPropagation handler that sat on an anchor inside a server component by un-nesting the social links. Signed-off-by: lauren --- src/app/authors/[slug]/page.tsx | 330 ++++++++++++++++---------------- src/app/authors/page.tsx | 181 +++++++++--------- src/app/updates/[slug]/page.tsx | 172 ++++++++--------- src/app/updates/layout.tsx | 27 ++- src/app/updates/page.tsx | 124 ++++++------ 5 files changed, 418 insertions(+), 416 deletions(-) diff --git a/src/app/authors/[slug]/page.tsx b/src/app/authors/[slug]/page.tsx index ba356fc..0e21171 100644 --- a/src/app/authors/[slug]/page.tsx +++ b/src/app/authors/[slug]/page.tsx @@ -1,182 +1,180 @@ import type { Metadata } from "next"; -import { notFound } from "next/navigation"; import Image from "next/image"; -import Link from "next/link"; -import { Pill } from "@/components/ui/pill"; -import { Footer } from "@/components/layout/footer"; -import { getAuthorBySlug, getAllAuthors } from "@/lib/authors"; +import { notFound } from "next/navigation"; + +import { Panel, PanelEyebrow, PanelPlainLink, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; +import { getAllAuthors, getAuthorBySlug, type Author } from "@/lib/authors"; import { getAllUpdates } from "@/lib/updates"; type AuthorPageProps = { - params: Promise<{ - slug: string; - }>; + params: Promise<{ + slug: string; + }>; }; export async function generateStaticParams() { - const authors = getAllAuthors(); - return authors.map((author) => ({ - slug: author.slug, - })); + const authors = getAllAuthors(); + + return authors.map((author) => ({ + slug: author.slug, + })); } export async function generateMetadata({ - params, + params, }: AuthorPageProps): Promise { - const { slug } = await params; - const author = getAuthorBySlug(slug); - - if (!author) { - return { - title: "Author not found", - }; - } - - return { - title: author.name, - description: `${author.title} - ${author.bio}`, - }; + const { slug } = await params; + const author = getAuthorBySlug(slug); + + if (!author) { + return { + title: "Author not found", + }; + } + + return { + title: author.name, + description: `${author.title} - ${author.bio}`, + }; +} + +const SOCIAL_LINKS: Array<{ + key: "github" | "twitter" | "linkedin" | "website"; + label: string; +}> = [ + { key: "github", label: "GitHub" }, + { key: "twitter", label: "Twitter" }, + { key: "linkedin", label: "LinkedIn" }, + { key: "website", label: "Website" }, +]; + +function formatDate(date: string) { + return new Date(date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +function AuthorSocialLinks({ author }: { author: Author }) { + const hasSocialLinks = SOCIAL_LINKS.some((link) => author[link.key]); + + if (!hasSocialLinks) { + return null; + } + + return ( +
+ {SOCIAL_LINKS.map((link) => { + const href = author[link.key]; + + if (!href) { + return null; + } + + return ( + + {link.label} → + + ); + })} +
+ ); } export default async function AuthorPage({ params }: AuthorPageProps) { - const { slug } = await params; - const author = getAuthorBySlug(slug); - - if (!author) { - notFound(); - } - - // Get updates by this author - const authorUpdates = getAllUpdates().filter( - (update) => update.metadata.author === slug - ); - - return ( -
-
-
-
- -
-
- {/* Author Profile */} -
-
- {author.avatar && ( -
- {author.name} -
- )} -
-

- {author.name} -

-

{author.title}

-
-

- {author.bio} -

- - {/* Social Links */} -
- {author.github && ( - - GitHub → - - )} - {author.twitter && ( - - Twitter → - - )} - {author.linkedin && ( - - LinkedIn → - - )} - {author.website && ( - - Website → - - )} -
-
-
- - {/* Author's Updates */} - {authorUpdates.length > 0 && ( -
-

- Updates by {author.name} -

-
- {authorUpdates.map((update) => ( - - -

- {update.metadata.title} -

-

- {update.metadata.description} -

- - ))} -
-
- )} - - {/* Back Link */} -
- - ← All authors - -
-
-
- -
-
- ); + const { slug } = await params; + const author = getAuthorBySlug(slug); + + if (!author) { + notFound(); + } + + const authorUpdates = getAllUpdates().filter((update) => update.metadata.author === slug); + + return ( +
+
+ +
+ {author.avatar && ( +
+ {author.name} +
+ )} +
+ Author +

+ {author.name} +

+

{author.title}

+

+ {author.bio} +

+
+ +
+
+
+
+ + {authorUpdates.length > 0 && ( + + Updates by {author.name} + + {authorUpdates.map((update) => ( + + +
+

+ {update.metadata.title} +

+

+ {update.metadata.description} +

+
+ + Read update + +
+ ))} +
+
+ )} + + +

+ Author navigation +

+ All authors +
+
+ +
+ ); } diff --git a/src/app/authors/page.tsx b/src/app/authors/page.tsx index 704707e..b6f21b4 100644 --- a/src/app/authors/page.tsx +++ b/src/app/authors/page.tsx @@ -1,102 +1,105 @@ import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { getAllAuthors } from "@/lib/authors"; -import { Footer } from "@/components/layout/footer"; + +import { Panel, PanelEyebrow, PanelSub } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; +import { getAllAuthors, type Author } from "@/lib/authors"; export const metadata: Metadata = { - title: "Authors", - description: "Meet the people behind the React Foundation.", + title: "Authors", + description: "Meet the people behind the React Foundation.", }; -export default function AuthorsPage() { - const authors = getAllAuthors(); +const SOCIAL_LINKS: Array<{ + key: "github" | "twitter" | "linkedin" | "website"; + label: string; +}> = [ + { key: "github", label: "GitHub" }, + { key: "twitter", label: "Twitter" }, + { key: "linkedin", label: "LinkedIn" }, + { key: "website", label: "Website" }, +]; + +function AuthorSocialLinks({ author }: { author: Author }) { + const hasSocialLinks = SOCIAL_LINKS.some((link) => author[link.key]); + + if (!hasSocialLinks) { + return null; + } - return ( -
-
-
-
+ return ( +
+ {SOCIAL_LINKS.map((link) => { + const href = author[link.key]; -
-
- {/* Hero */} -
- Meet the Team · Contributors · Leadership -
-

- Authors -

-

- Meet the people building and leading the React Foundation. -

-
-
+ if (!href) { + return null; + } - {/* Authors Grid */} - -
- {authors.map((author, idx) => ( - - - {author.avatar && ( -
-
- {author.name} -
-
- )} -

- {author.name} -

-

{author.title}

-

{author.bio}

+ return ( + + {link.label} + + ); + })} +
+ ); +} + +export default function AuthorsPage() { + const authors = getAllAuthors(); - {/* Social Links */} -
- {author.github && ( - e.stopPropagation()} - > - GitHub - - )} - {author.twitter && ( - e.stopPropagation()} - > - Twitter - - )} -
- - - ))} - - - -
+ return ( +
+
+ + Authors +

+ Authors +

+ Meet the people building and leading the React Foundation. +
-
-
- ); + + People +
+ {authors.map((author) => ( +
+ + {author.avatar && ( +
+ {author.name} +
+ )} +

+ {author.name} +

+

{author.title}

+

{author.bio}

+ + +
+ ))} +
+
+ + +
+ ); } diff --git a/src/app/updates/[slug]/page.tsx b/src/app/updates/[slug]/page.tsx index 02db4ef..dc22ccd 100644 --- a/src/app/updates/[slug]/page.tsx +++ b/src/app/updates/[slug]/page.tsx @@ -1,110 +1,110 @@ import type { Metadata } from "next"; -import { notFound } from "next/navigation"; -import { Pill } from "@/components/ui/pill"; -import { getUpdateBySlug, getAllUpdates } from "@/lib/updates"; -import { getAuthorBySlug } from "@/lib/authors"; -import { MDXRemote } from "next-mdx-remote/rsc"; import Image from "next/image"; import Link from "next/link"; +import { notFound } from "next/navigation"; +import { MDXRemote } from "next-mdx-remote/rsc"; + +import { Panel, PanelEyebrow, PanelPlainLink } from "@/components/panels/panel"; +import { getAuthorBySlug } from "@/lib/authors"; +import { getAllUpdates, getUpdateBySlug } from "@/lib/updates"; type UpdatePageProps = { - params: Promise<{ - slug: string; - }>; + params: Promise<{ + slug: string; + }>; }; export async function generateStaticParams() { - const updates = getAllUpdates(); - return updates.map((update) => ({ - slug: update.slug, - })); + const updates = getAllUpdates(); + + return updates.map((update) => ({ + slug: update.slug, + })); } export async function generateMetadata({ - params, + params, }: UpdatePageProps): Promise { - const { slug } = await params; - const update = getUpdateBySlug(slug); + const { slug } = await params; + const update = getUpdateBySlug(slug); - if (!update) { - return { - title: "Update not found", - }; - } + if (!update) { + return { + title: "Update not found", + }; + } + + return { + title: update.metadata.title, + description: update.metadata.description, + }; +} - return { - title: update.metadata.title, - description: update.metadata.description, - }; +function formatDate(date: string) { + return new Date(date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); } export default async function UpdatePage({ params }: UpdatePageProps) { - const { slug } = await params; - const update = getUpdateBySlug(slug); + const { slug } = await params; + const update = getUpdateBySlug(slug); - if (!update) { - notFound(); - } + if (!update) { + notFound(); + } - const author = getAuthorBySlug(update.metadata.author); + const author = getAuthorBySlug(update.metadata.author); - return ( -
- {/* Post Header */} -
- - Update ·{" "} - {new Date(update.metadata.date).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - })} - -

- {update.metadata.title} -

+ return ( +
+ + {formatDate(update.metadata.date)} +

+ {update.metadata.title} +

- {/* Author Info */} - {author && ( -
- {author.avatar && ( -
- {author.name} -
- )} -
- - {author.name} - -

{author.title}

-
-
- )} -
+ {author && ( +
+ {author.avatar && ( +
+ {author.name} +
+ )} +
+ + {author.name} + +

{author.title}

+
+
+ )} + - {/* MDX Content with prose */} -
- -
+
+ +
- {/* Back Link */} -
- - ← Back to all updates - -
-
- ); + +

+ Update navigation +

+ Back to all updates +
+ + ); } diff --git a/src/app/updates/layout.tsx b/src/app/updates/layout.tsx index 7fe651f..00f828e 100644 --- a/src/app/updates/layout.tsx +++ b/src/app/updates/layout.tsx @@ -1,21 +1,16 @@ -import { Footer } from "@/components/layout/footer"; +import type { ReactNode } from "react"; + +import { PanelsFooter } from "@/components/panels/panels-footer"; export default function UpdatesLayout({ - children, + children, }: { - children: React.ReactNode; + children: ReactNode; }) { - return ( -
-
-
-
- -
- {children} -
- -
-
- ); + return ( +
+ {children} + +
+ ); } diff --git a/src/app/updates/page.tsx b/src/app/updates/page.tsx index 0e26738..8843443 100644 --- a/src/app/updates/page.tsx +++ b/src/app/updates/page.tsx @@ -1,69 +1,75 @@ import type { Metadata } from "next"; -import Link from "next/link"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { getAllUpdates } from "@/lib/updates"; + +import { Panel, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; import { getAuthorBySlug } from "@/lib/authors"; +import { getAllUpdates } from "@/lib/updates"; export const metadata: Metadata = { - title: "Updates", - description: "Latest news and announcements from the React Foundation.", + title: "Updates", + description: "Latest news and announcements from the React Foundation.", }; +function formatDate(date: string) { + return new Date(date).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + export default function UpdatesPage() { - const updates = getAllUpdates(); - return ( -
- {/* Hero */} -
- Latest News · Announcements · Community Updates -
-

- Updates -

-

- Stay informed about the latest news, announcements, and initiatives from - the React Foundation. -

-
-
+ const updates = getAllUpdates(); + + return ( +
+ + Updates +

+ Updates +

+ + Stay informed about the latest news, announcements, and initiatives from the + React Foundation. + +
- {/* Updates List */} - -
- {updates.map((update, idx) => { - const author = getAuthorBySlug(update.metadata.author); + + Latest posts + + {updates.map((update) => { + const author = getAuthorBySlug(update.metadata.author); - return ( - - -
- - · - {author?.name || update.metadata.author} -
-

- {update.metadata.title} -

-

{update.metadata.description}

-
- Read more → -
- -
- ); - })} -
-
-
- ); + return ( + + +
+

+ {update.metadata.title} +

+

+ {update.metadata.description} +

+
+ + {author?.name || update.metadata.author} + +
+ ); + })} + + +
+ ); } From c3f0eac8d6a69b1cb44acd02d7280cfd85856a35 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 01:06:39 -0700 Subject: [PATCH 12/28] Feat: restyle governance pages and library dashboard to Panels Board and TSC keep their member data verbatim in flat window-pane grids; the libraries page keeps the live RIS hook flow and wraps the read-only rankings component in a panel. Signed-off-by: lauren --- src/app/about/board-of-directors/page.tsx | 384 +++++-------- .../technical-steering-committee/page.tsx | 508 +++++++----------- src/app/libraries/page.tsx | 182 ++++--- 3 files changed, 439 insertions(+), 635 deletions(-) diff --git a/src/app/about/board-of-directors/page.tsx b/src/app/about/board-of-directors/page.tsx index 510fb0e..4d4c8bc 100644 --- a/src/app/about/board-of-directors/page.tsx +++ b/src/app/about/board-of-directors/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { ButtonLink } from "@/components/ui/button"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { Footer } from "@/components/layout/footer"; +import { BadgeCheck, CircleDollarSign, CircleUserRound, FileText, MessageCircle, ShieldCheck } from "lucide-react"; + +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelSub, RowList } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; export const metadata: Metadata = { title: "Board of Directors | React Foundation", @@ -62,246 +62,156 @@ const boardMembers: BoardMember[] = [ }, ]; +const philosophyValues = [ + { + title: "Transparent", + description: "All decisions and finances publicly documented", + }, + { + title: "Accountable", + description: "Regular reporting and community oversight", + }, + { + title: "Community-First", + description: "Decisions guided by ecosystem needs", + }, +]; + +const responsibilities = [ + { + icon: ShieldCheck, + title: "Strategic Direction", + description: + "Setting long-term goals, priorities, and initiatives that serve the React ecosystem's growth and sustainability.", + }, + { + icon: CircleDollarSign, + title: "Financial Oversight", + description: + "Ensuring responsible fund management, transparent distribution to maintainers, and regular financial reporting.", + }, + { + icon: MessageCircle, + title: "Community Engagement", + description: + "Maintaining open dialogue with maintainers, contributors, and the broader React community to inform decisions.", + }, + { + icon: FileText, + title: "Governance & Compliance", + description: + "Establishing policies, ensuring legal compliance, and maintaining the foundation's integrity and mission alignment.", + }, +]; + export default function BoardOfDirectorsPage() { return ( -
- {/* Hero Gradient */} -
-
-
- -
-
- {/* Hero Section */} -
- Governance · Leadership · Transparency -
-

- Board of Directors -

-

- Our Board of Directors provides strategic guidance, ensures financial oversight, - and maintains the foundation's commitment to transparency and community-first values. -

+
+ + +
+ Governance · Leadership · Transparency +

+ Board of Directors +

+

+ The Board provides strategic guidance, financial oversight, and a clear commitment + to transparent, community-first governance. +

+
+
+ + + Our governance philosophy +

+ The React Foundation operates with complete transparency and community accountability. + The Board keeps decisions focused on the ecosystem's best interests, with quarterly + reports, open financials, and active community feedback loops. +

+
+ {philosophyValues.map((value) => ( +
+
-
- - {/* Mission Statement */} - -
-

- Our Governance Philosophy -

-

- The React Foundation operates with complete transparency and community accountability. - Our Board ensures that every decision serves the ecosystem's best interests, with - quarterly reports, open financials, and active community feedback loops. -

-
-
-
-
-
-

Transparent

-

- All decisions and finances publicly documented -

-
-
-
-
-
-

Accountable

-

- Regular reporting and community oversight -

-
-
-
-
-
-

Community-First

-

- Decisions guided by ecosystem needs -

-
-
-
-
- - {/* Board Members Grid */} - -
-
-

- Meet the Board -

-

- Leaders committed to building a sustainable future for React -

+ ))} +
+ + + + Meet the board + Leaders committed to building a sustainable future for React. +
+ {boardMembers.map((member) => ( +
+
+
- -
- {boardMembers.map((member) => ( -
- {/* Card Gradient Background */} -
- -
- {/* Headshot Placeholder */} -
-
-
- - - -
-
-
- - {/* Member Info */} -
-

- {member.name} -

-

- {member.title} -

-

- {member.role} -

-
- - {/* Bio */} -

- {member.bio} -

- - {/* Expertise Tags */} -
- {member.expertise.map((skill, skillIndex) => ( - - {skill} - - ))} -
-
-
+

{member.name}

+

{member.title}

+

{member.role}

+

{member.bio}

+
+ {member.expertise.map((skill) => ( + + {skill} + ))}
-
-
- - {/* Responsibilities */} - -
-

- Board Responsibilities -

-
-
-
- - - -
-

- Strategic Direction -

-

- Setting long-term goals, priorities, and initiatives that serve the - React ecosystem's growth and sustainability. -

-
- -
-
- - - -
-

- Financial Oversight -

-

- Ensuring responsible fund management, transparent distribution to maintainers, - and regular financial reporting. -

-
- -
-
- - - -
-

- Community Engagement -

-

- Maintaining open dialogue with maintainers, contributors, and the broader - React community to inform decisions. -

-
- -
-
- - - -
-

- Governance & Compliance -

-

- Establishing policies, ensuring legal compliance, and maintaining the - foundation's integrity and mission alignment. -

-
-
-
-
- - {/* CTA Section */} - -
-

- Learn More About Our Governance -

-

- Explore our transparent operations, quarterly reports, and how we're - building a sustainable future for the React ecosystem. -

-
- - About the Foundation - - - View Our Impact - + + ))} +
+ + + + Board responsibilities + The Board keeps governance, funding, and community accountability aligned. + + {responsibilities.map((responsibility) => ( +
+
-
-
-
- -
+
+ ))} + + + + +
+
+ Learn more about our governance +

+ Explore transparent operations, quarterly reports, and the work behind a sustainable + React ecosystem. +

+
+ + + About the Foundation + + + View Our Impact + + +
+
+ +
); } diff --git a/src/app/about/technical-steering-committee/page.tsx b/src/app/about/technical-steering-committee/page.tsx index 595fd35..14810b2 100644 --- a/src/app/about/technical-steering-committee/page.tsx +++ b/src/app/about/technical-steering-committee/page.tsx @@ -1,8 +1,8 @@ import type { Metadata } from "next"; -import { ButtonLink } from "@/components/ui/button"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { Footer } from "@/components/layout/footer"; +import { BadgeCheck, BookOpen, CircleUserRound, Code2, Globe2, Lightbulb, ShieldCheck, Wrench, Zap } from "lucide-react"; + +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, PanelSub, RowList } from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; export const metadata: Metadata = { title: "Technical Steering Committee | React Foundation", @@ -76,328 +76,212 @@ const committeeMembers: CommitteeMember[] = [ }, ]; +const missionValues = [ + { + title: "Excellence", + description: "Maintaining high technical standards across all projects", + }, + { + title: "Innovation", + description: "Supporting experimentation and emerging technologies", + }, + { + title: "Collaboration", + description: "Fostering cooperation between ecosystem projects", + }, +]; + +const responsibilities = [ + { + icon: Code2, + title: "Technical Standards", + description: + "Establishing and maintaining technical standards, API design guidelines, and best practices for ecosystem libraries.", + }, + { + icon: Lightbulb, + title: "Innovation Support", + description: + "Evaluating emerging technologies, experimental features, and new patterns that could benefit the React ecosystem.", + }, + { + icon: BookOpen, + title: "Documentation & Education", + description: + "Ensuring comprehensive technical documentation, guides, and educational resources for the community.", + }, + { + icon: Globe2, + title: "Ecosystem Coordination", + description: + "Facilitating collaboration between projects, resolving technical conflicts, and promoting interoperability.", + }, + { + icon: ShieldCheck, + title: "Security & Quality", + description: + "Establishing security protocols, vulnerability response processes, and quality assurance standards.", + }, + { + icon: Zap, + title: "Performance & Optimization", + description: + "Guiding performance optimization strategies, benchmarking practices, and establishing performance budgets.", + }, +]; + +const workingGroups = [ + { + title: "Framework Interop", + description: "Ensuring libraries work seamlessly across React frameworks", + }, + { + title: "Testing Standards", + description: "Developing unified testing approaches and tools", + }, + { + title: "Server Components", + description: "Exploring patterns for RSC adoption in libraries", + }, + { + title: "Type Safety", + description: "Improving TypeScript integration across ecosystem", + }, + { + title: "Accessibility", + description: "Establishing a11y standards and best practices", + }, + { + title: "Build Tooling", + description: "Optimizing build systems and developer experience", + }, +]; + export default function TechnicalSteeringCommitteePage() { return ( -
- {/* Hero Gradient */} -
-
-
+
+ + +
+ Technical excellence · Innovation · Open standards +

+ Technical Steering Committee +

+

+ The TSC drives technical excellence across React libraries, tools, and frameworks + through standards, best practices, and support for responsible innovation. +

+
+
-
-
- {/* Hero Section */} -
- Technical Excellence · Innovation · Open Standards -
-

- Technical Steering Committee -

-

- Our Technical Steering Committee (TSC) drives technical excellence across the - React ecosystem, establishing standards, best practices, and supporting innovation - in libraries, tools, and frameworks. -

+ + Our technical mission +

+ The TSC keeps technical decisions aligned with the ecosystem's long-term health: + maintainer guidance, interoperability standards, and innovation that preserves + stability and developer trust. +

+
+ {missionValues.map((value) => ( +
+
-
+ ))} +
+ - {/* Mission Statement */} - -
-

- Our Technical Mission -

-

- The TSC ensures technical decisions align with the ecosystem's long-term health, - supporting maintainers with guidance, establishing interoperability standards, and - fostering innovation while maintaining stability and developer trust. -

-
-
-
-
-
-

Excellence

-

- Maintaining high technical standards across all projects -

-
-
-
-
-
-

Innovation

-

- Supporting experimentation and emerging technologies -

-
-
-
-
-
-

Collaboration

-

- Fostering cooperation between ecosystem projects -

-
+ + Meet the committee + Technical experts dedicated to React ecosystem excellence. +
+ {committeeMembers.map((member) => ( +
+
+
-
-
- - {/* Committee Members Grid */} - -
-
-

- Meet the Committee -

-

- Technical experts dedicated to React ecosystem excellence -

-
- -
- {committeeMembers.map((member) => ( -
- {/* Card Gradient Background */} -
- -
- {/* Headshot Placeholder */} -
-
-
- - - -
-
-
- - {/* Member Info */} -
-

- {member.name} -

-

- {member.title} -

-

- {member.role} -

-
- - {/* Bio */} -

- {member.bio} -

- - {/* Expertise Tags */} -
- {member.expertise.map((skill, skillIndex) => ( - - {skill} - - ))} -
-
-
+

{member.name}

+

{member.title}

+

{member.role}

+

{member.bio}

+
+ {member.expertise.map((skill) => ( + + {skill} + ))}
-
-
- - {/* Responsibilities */} - -
-

- Committee Responsibilities -

-
-
-
- - - -
-

- Technical Standards -

-

- Establishing and maintaining technical standards, API design guidelines, - and best practices for ecosystem libraries. -

-
- -
-
- - - -
-

- Innovation Support -

-

- Evaluating emerging technologies, experimental features, and new patterns - that could benefit the React ecosystem. -

-
- -
-
- - - -
-

- Documentation & Education -

-

- Ensuring comprehensive technical documentation, guides, and educational - resources for the community. -

-
+ + ))} +
+ -
-
- - - -
-

- Ecosystem Coordination -

-

- Facilitating collaboration between projects, resolving technical conflicts, - and promoting interoperability. -

-
- -
-
- - - -
-

- Security & Quality -

-

- Establishing security protocols, vulnerability response processes, and - quality assurance standards. -

-
- -
-
- - - -
-

- Performance & Optimization -

-

- Guiding performance optimization strategies, benchmarking practices, and - establishing performance budgets. -

-
+ + Committee responsibilities + The committee supports standards, security, coordination, and performance across the ecosystem. + + {responsibilities.map((responsibility) => ( +
+
-
+
+ ))} + + - {/* Working Groups */} - -
-

- Technical Working Groups -

-

- The TSC organizes focused working groups to tackle specific technical challenges, - explore new patterns, and develop proposals for ecosystem-wide adoption. -

-
-
-

Framework Interop

-

- Ensuring libraries work seamlessly across React frameworks -

-
-
-

Testing Standards

-

- Developing unified testing approaches and tools -

-
-
-

Server Components

-

- Exploring patterns for RSC adoption in libraries -

-
-
-

Type Safety

-

- Improving TypeScript integration across ecosystem -

-
-
-

Accessibility

-

- Establishing a11y standards and best practices -

-
-
-

Build Tooling

-

- Optimizing build systems and developer experience -

-
+ + Technical working groups + + The TSC organizes focused working groups to tackle technical challenges and develop ecosystem-wide proposals. + + + {workingGroups.map((group) => ( +
+
-
+
+ ))} + + - {/* CTA Section */} - -
-

- Get Involved in Technical Discussions -

-

- Join our technical discussions, propose new standards, and help shape the - future of the React ecosystem. -

-
- - About the Foundation - - - View Our Impact - -
-
-
- -
+ +
+
+ Get involved in technical discussions +

+ Join technical discussions, propose standards, and help shape the future of the React ecosystem. +

+
+ + + About the Foundation + + + View Our Impact + + +
+
-
+
); } diff --git a/src/app/libraries/page.tsx b/src/app/libraries/page.tsx index 1944bee..51d8758 100644 --- a/src/app/libraries/page.tsx +++ b/src/app/libraries/page.tsx @@ -12,13 +12,14 @@ import { useCollectionStatus, } from '@/lib/ris'; import { RISLibraryRankings } from '@/components/ris/ris-library-rankings'; -import { RFDS } from '@/components/rfds'; +import { Panel, PanelButton, PanelEyebrow, PanelPlainLink, PanelSub, RowList } from '@/components/panels/panel'; +import { PanelsFooter } from '@/components/panels/panels-footer'; import Link from 'next/link'; export default function LibrariesPage() { // Try to fetch real data from API const { allocation: realAllocation, isLoading, isError } = useRISAllocationFromAPI(); - const { lastUpdated, currentQuarter } = useCollectionStatus(); + const { lastUpdated } = useCollectionStatus(); // Fall back to sample data if real data not available const useSampleData = isError || !realAllocation; @@ -31,28 +32,28 @@ export default function LibrariesPage() { const avgRIS = allocation.libraries.reduce((sum, lib) => sum + lib.ris, 0) / totalLibraries; return ( -
-
- {/* Header */} -
-
- - ← How Scoring Works - -
-

+
+ +
+ Library impact dashboard +

Library Impact Dashboard

-

- React ecosystem libraries ranked by their impact score +

+ React ecosystem libraries ranked by their impact score.

+
+ How Scoring Works +
+
- {/* Summary Stats */} -
+ + By the numbers + -
+ + - {/* Component Stats */} -
-

- Component Score Averages -

-
- {componentStats.map((stat) => ( -
-
{stat.label}
-
- {(stat.avg * 100).toFixed(1)}% -
-
- Range: {(stat.min * 100).toFixed(0)}% - {(stat.max * 100).toFixed(0)}% -
+ + Component score averages + Average RIS components across the currently loaded library set. +
+ {componentStats.map((stat) => ( +
+
{stat.label}
+
+ {(stat.avg * 100).toFixed(1)}%
- ))} -
+
+ Range: {(stat.min * 100).toFixed(0)}% - {(stat.max * 100).toFixed(0)}% +
+
+ ))}
+ - {/* Loading State */} - {isLoading && ( -
-
-

Loading RIS data...

+ {isLoading && ( + +
+ Loading data +
+

Loading RIS data...

- )} +
+ )} - {/* Data Source Banner */} - {!isLoading && ( -
+
{useSampleData ? '⚠️' : '✓'} -
+
{useSampleData ? ( <> -

Using Sample Data

+

Using Sample Data

Real RIS data not yet available. This dashboard shows sample data demonstrating how the React Impact Score (RIS) system evaluates libraries across 5 key components.

To use real data, configure your GitHub PAT and Redis URL, then run data collection - via POST /api/ris/collect + via POST /api/ris/collect

) : ( <> -

Using Real Data

+

Using Real Data

This dashboard displays actual metrics collected from GitHub, NPM, CDN providers, and OSSF Scorecard for {allocation.libraries.length} React ecosystem libraries.

{lastUpdated && ( -

+

Last updated: {new Date(lastUpdated).toLocaleString()}

)} )}

- + Learn more about how scoring works →

- )} + + )} - {/* Library Rankings */} -
-
-

- Library Rankings -

-
- {totalLibraries} libraries · Total pool: ${allocation.total_pool_usd.toLocaleString()} -
+ +
+
+ Library rankings + Scores and allocation estimates for the current RIS period.
+
+ {totalLibraries} libraries · Total pool: ${allocation.total_pool_usd.toLocaleString()} +
+
+
+
- {/* Methodology Note */} -
-

- Scores are calculated using winsorized normalization to reduce outlier impact, - with EMA smoothing for quarter-to-quarter stability. -

-

- All data is transparent and reproducible. See the{' '} - - scoring documentation - {' '} - for full methodology. -

+ +
+
+ Methodology +

+ Scores are calculated using winsorized normalization to reduce outlier impact, + with EMA smoothing for quarter-to-quarter stability. +

+

+ All data is transparent and reproducible. See the scoring documentation for full methodology. +

+
+ + View scoring docs +
-
+ + +
); } @@ -198,13 +207,14 @@ function StatCard({ highlight?: boolean; }) { return ( - +
+
+

{label}

+

{subtext}

+
+
+ {value} +
+
); } From 83530c1a60851e63a914c12f400e93b481583274 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 01:12:30 -0700 Subject: [PATCH 13/28] Feat: restyle about and impact to Panels The four about-only home components move onto the panel primitives; EcosystemLibraries pins the theme variables to light values around the read-only LibraryCard so it stays legible on a constant paper panel in dark mode. TOC wiring, section ids, hrefs, and the mailto handler are preserved verbatim. Signed-off-by: lauren --- src/app/about/page.tsx | 460 ++++++++------------ src/app/impact/page.tsx | 320 +++++++------- src/components/home/become-contributor.tsx | 65 +-- src/components/home/ecosystem-libraries.tsx | 109 ++--- src/components/home/executive-message.tsx | 200 ++++----- src/components/home/founding-members.tsx | 198 ++------- 6 files changed, 556 insertions(+), 796 deletions(-) diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 11497b1..e3ee321 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,13 +1,23 @@ import type { Metadata } from "next"; -import Link from "next/link"; -import { ButtonLink } from "@/components/ui/button"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { Footer } from "@/components/layout/footer"; +import { Code, Landmark } from "lucide-react"; + import { BecomeContributor } from "@/components/home/become-contributor"; import { EcosystemLibraries } from "@/components/home/ecosystem-libraries"; -import { FoundingMembers } from "@/components/home/founding-members"; import { ExecutiveMessage } from "@/components/home/executive-message"; +import { FoundingMembers } from "@/components/home/founding-members"; +import { + OrbitMarks, + Panel, + PanelActions, + PanelButton, + PanelEyebrow, + PanelSub, + Row, + RowArrow, + RowList, + RowRight, +} from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; import { RFDS } from "@/components/rfds"; const sections = [ @@ -25,309 +35,207 @@ export const metadata: Metadata = { description: "Learn about the React Foundation's mission, governance, and how we support the ecosystem.", }; +const MISSION_POINTS = [ + { + title: "Sustainable Funding", + description: "Creating reliable revenue streams that support open source maintainers", + }, + { + title: "Full Transparency", + description: "Quarterly reports showing exactly how funds are distributed", + }, + { + title: "Community First", + description: "Decisions driven by community needs and maintainer feedback", + }, +]; + +const HOW_IT_WORKS_STEPS = [ + { + title: "Contribute to the Ecosystem", + description: + "Submit code, documentation, RFCs, and bug reports to React and 54+ ecosystem libraries. Your contributions directly improve the tools millions of developers use every day.", + }, + { + title: "Join the Community", + description: + "Organize meetups, create educational content, teach workshops, or help other developers learn React. Community organizers and educators are essential to ecosystem growth.", + }, + { + title: "Support Through the Store", + description: + "One way to fund the ecosystem is through our official merchandise store. 100% of profits support maintainers, educators, and community organizers based on transparent impact metrics.", + }, + { + title: "Transparent Impact", + description: + "Quarterly impact reports detail exactly how funds support maintainers, education, and accessibility initiatives. Full transparency in how contributions make a difference.", + }, +]; + +const GOVERNANCE_BODIES = [ + { + icon: Landmark, + href: "/about/board-of-directors", + title: "Board of Directors", + subtitle: "Strategic Leadership · Financial Oversight · Governance", + description: + "Our Board provides strategic guidance, ensures financial oversight, and maintains the foundation's commitment to transparency and community-first values.", + }, + { + icon: Code, + href: "/about/technical-steering-committee", + title: "Technical Steering Committee", + subtitle: "Technical Excellence · Innovation · Open Standards", + description: + "The TSC drives technical excellence across the React ecosystem, establishing standards, best practices, and supporting innovation in libraries and tools.", + }, +]; + +const GOVERNANCE_PRINCIPLES = [ + { title: "Open Financials", description: "Every dollar tracked and reported publicly" }, + { title: "Community Input", description: "Major decisions informed by maintainer feedback" }, + { title: "Quarterly Reports", description: "Detailed impact metrics published every quarter" }, + { title: "Open Source Values", description: "Built on the same principles as the ecosystem we support" }, +]; + export default function AboutPage() { return ( -
-
-
-
- -
-
-
- {/* Hero */} -
- Our Story · Our Mission · Our Values -
-

+
+
+
+ + +
+ Our story · Our mission · Our values +

About React Foundation

-

+

We're building a sustainable future for the React ecosystem through - community funding, transparent governance, and unwavering support for - the maintainers who make it all possible. + community funding, transparent governance, and support for the + maintainers who make it all possible.

-

+ - {/* Executive Message */} -
+
- {/* Mission */} -
- -
-

- Our Mission -

-

- The React Foundation exists to ensure the React ecosystem thrives for - generations to come. We provide direct financial support to maintainers, - fund educational initiatives, and ensure accessibility for developers - worldwide. -

-
-
-
-
-
-
-

Sustainable Funding

-

- Creating reliable revenue streams that support open source maintainers -

-
+ + Our mission +

+ The React Foundation exists to ensure the React ecosystem{" "} + thrives for generations to come. We + provide direct financial support to maintainers, fund educational + initiatives, and ensure accessibility for developers worldwide. +

+ + {MISSION_POINTS.map((point) => ( +
+

{point.title}

+

{point.description}

-
-
-
-
-
-

Full Transparency

-

- Quarterly reports showing exactly how funds are distributed -

-
-
-
-
-
-
-
-

Community First

-

- Decisions driven by community needs and maintainer feedback + ))} + + + + + How it works +

+ {HOW_IT_WORKS_STEPS.map((step, index) => ( +
+ + {index + 1} + +
+

{step.title}

+

+ {step.description}

-
-
-
-
- - {/* How It Works */} -
- -
-

- How It Works -

-
-
-
- 1 -
-

- Contribute to the Ecosystem -

-

- Submit code, documentation, RFCs, and bug reports to React and 54+ - ecosystem libraries. Your contributions directly improve the tools - millions of developers use every day. -

-
- -
-
- 2 -
-

- Join the Community -

-

- Organize meetups, create educational content, teach workshops, or - help other developers learn React. Community organizers and educators - are essential to ecosystem growth. -

-
- -
-
- 3 -
-

- Support Through the Store -

-

- One way to fund the ecosystem is through our official merchandise store. - 100% of profits support maintainers, educators, and community organizers - based on transparent impact metrics. -

-
- -
-
- 4 -
-

- Transparent Impact -

-

- Quarterly impact reports detail exactly how funds support maintainers, - education, and accessibility initiatives. Full transparency in how - contributions make a difference. -

-
-
-
-
-
+ ))} +
+ - {/* Founding Members */} -
+
- {/* Ecosystem Libraries */} -
- +
+
- {/* Governance / Communities */} -
- -
-

- Transparent Governance -

-

- The React Foundation operates with complete transparency. All funding - decisions, impact reports, and financial details are published quarterly - for community review and feedback. -

- - {/* Governance Bodies */} -
- -
-
- - - -
-
-

- Board of Directors -

-

- Strategic Leadership · Financial Oversight · Governance -

-
- - - + + Transparent governance + + The React Foundation operates with complete transparency. All funding + decisions, impact reports, and financial details are published quarterly + for community review and feedback. + + + {GOVERNANCE_BODIES.map((body) => ( + + + ))} + +
+ {GOVERNANCE_PRINCIPLES.map((principle) => ( +
-
-
- - - -
-
-

- Technical Steering Committee -

-

- Technical Excellence · Innovation · Open Standards -

-
- - - -
-

- The TSC drives technical excellence across the React ecosystem, establishing - standards, best practices, and supporting innovation in libraries and tools. -

- -
- - {/* Governance Principles */} -
-
-

Open Financials

-

- Every dollar tracked and reported publicly -

+

{principle.title}

+

{principle.description}

-
-

Community Input

-

- Major decisions informed by maintainer feedback -

-
-
-

Quarterly Reports

-

- Detailed impact metrics published every quarter -

-
-
-

Open Source Values

-

- Built on the same principles as the ecosystem we support -

-
-
-
-
-
+ ))} +
+ - {/* Become a Contributor */} -
- +
-
- {/* CTA */} - -
-

- Ready to Make an Impact? -

-

- Start supporting the React ecosystem today. Every contribution helps build - a sustainable future for open source. -

-
- - Shop the Store - - - View Our Impact - -
-
-
+ + Ready to make an impact? +

+ Start supporting the React ecosystem today. Every contribution helps build + a sustainable future for open source. +

+ + + Shop the Store + + + View Our Impact + + +
- {/* Table of Contents Sidebar */} -
-
+
); } diff --git a/src/app/impact/page.tsx b/src/app/impact/page.tsx index 487b859..a8c382c 100644 --- a/src/app/impact/page.tsx +++ b/src/app/impact/page.tsx @@ -1,9 +1,16 @@ import type { Metadata } from "next"; -import { ButtonLink } from "@/components/ui/button"; -import { Pill } from "@/components/ui/pill"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; -import { Footer } from "@/components/layout/footer"; +import { BarChart3, BookOpen, CircleDollarSign, Globe, MessageSquare, Users } from "lucide-react"; + import { EcosystemLibraries } from "@/components/home/ecosystem-libraries"; +import { + OrbitMarks, + Panel, + PanelActions, + PanelButton, + PanelEyebrow, + PanelSub, +} from "@/components/panels/panel"; +import { PanelsFooter } from "@/components/panels/panels-footer"; import { ecosystemLibraries } from "@/lib/maintainer-tiers"; export const metadata: Metadata = { @@ -11,186 +18,151 @@ export const metadata: Metadata = { description: "See how React Foundation funding supports the ecosystem with transparent quarterly reports.", }; +const REPORT_CONTENTS = [ + { + icon: CircleDollarSign, + title: "Revenue Details", + description: "Total revenue generated from all sources", + }, + { + icon: Users, + title: "Maintainer Funding", + description: "Breakdown of funding by library and maintainer", + }, + { + icon: BookOpen, + title: "Education Initiatives", + description: "Tutorials, docs, and learning resources supported", + }, + { + icon: Globe, + title: "Accessibility", + description: "Global accessibility improvements funded", + }, + { + icon: BarChart3, + title: "Impact Metrics", + description: "Downloads, usage, and ecosystem growth data", + }, + { + icon: MessageSquare, + title: "Community Feedback", + description: "Testimonials from maintainers and contributors", + }, +]; + +const DISTRIBUTION_STEPS = [ + { + title: "Contribution Tracking", + description: + "We track pull requests, issues, and commits across all 54 supported libraries using GitHub's GraphQL API.", + }, + { + title: "Score Calculation", + description: + "Contributions are weighted (PRs × 8 + Issues × 3 + Commits × 1) to calculate fair distribution ratios.", + }, + { + title: "Fund Distribution", + description: + "100% of profits are distributed quarterly based on contribution scores and library impact metrics.", + }, +]; + export default function ImpactPage() { return ( -
-
-
-
+
+ + +
+ 100% transparent · Quarterly reports · Real impact +

+ Our Impact +

+

+ Full transparency on how your support funds the React ecosystem. Every + contribution is tracked and reported publicly. +

+
+
-
-
- {/* Hero */} -
- 100% Transparent · Quarterly Reports · Real Impact -
-

- Our Impact -

-

- Full transparency on how your support funds the React ecosystem. Every - contribution is tracked and reported publicly. -

-
-
- - {/* Coming Soon */} - -
-
-
- - - -
-

- First Report Coming Soon -

-

- Our inaugural quarterly impact report will be published once the store - launches. Each report will provide complete transparency into fund - distribution. + + First report coming soon + + Our inaugural quarterly impact report will be published once the store + launches. Each report will provide complete transparency into fund + distribution. + +

+ {REPORT_CONTENTS.map((item) => ( +
+
+ ))} +
+ -
-
-
💰
-

Revenue Details

-

- Total revenue generated from all sources -

-
-
-
👥
-

Maintainer Funding

-

- Breakdown of funding by library and maintainer -

-
-
-
📚
-

Education Initiatives

-

- Tutorials, docs, and learning resources supported -

-
-
-
🌍
-

Accessibility

-

- Global accessibility improvements funded -

-
-
-
📊
-

Impact Metrics

-

- Downloads, usage, and ecosystem growth data -

-
-
-
💬
-

Community Feedback

-

- Testimonials from maintainers and contributors -

-
-
-
-
- - {/* Ecosystem Libraries */} - - - {/* How Funds are Distributed */} - -
-

- How Funds are Distributed -

-

- We use a transparent, metrics-based approach to ensure fair distribution - of funds to maintainers across the React ecosystem. -

- -
-
-
- 1 -
-

- Contribution Tracking -

-

- We track pull requests, issues, and commits across all 54 supported - libraries using GitHub's GraphQL API. -

-
- -
-
- 2 -
-

Score Calculation

-

- Contributions are weighted (PRs × 8 + Issues × 3 + Commits × 1) to - calculate fair distribution ratios. -

-
+ -
-
- 3 -
-

Fund Distribution

-

- 100% of profits are distributed quarterly based on contribution scores - and library impact metrics. -

-
+ + How funds are distributed + + We use a transparent, metrics-based approach to ensure fair distribution of + funds to maintainers across the React ecosystem. + +
+ {DISTRIBUTION_STEPS.map((step, index) => ( +
+ + {index + 1} + +
+

{step.title}

+

+ {step.description} +

-
-
+
+ ))} +
+ - {/* CTA */} - -
-

- Support the Ecosystem -

-

- Every purchase directly supports React ecosystem maintainers. Shop the - store to make an impact today. -

-
- - Shop the Store - - - Learn More - -
-
-
- -
+ + Support the ecosystem +

+ Every purchase directly supports React ecosystem maintainers. Shop the store + to make an impact today. +

+ + + Shop the Store + + + Learn More + + +
-
+
); } diff --git a/src/components/home/become-contributor.tsx b/src/components/home/become-contributor.tsx index 3ab5988..aec35e8 100644 --- a/src/components/home/become-contributor.tsx +++ b/src/components/home/become-contributor.tsx @@ -1,9 +1,10 @@ 'use client'; import React from "react"; -import { RFDS } from "@/components/rfds"; import Link from "next/link"; +import { Panel, PanelEyebrow, PanelSub } from "@/components/panels/panel"; + type Action = { href: string; label: string; external?: boolean }; const contributorData: { @@ -131,6 +132,9 @@ const contributorData: { }, ]; +const ACTION_LINK_CLASS = + "panels-anim text-[15px] font-semibold hover:underline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-[#16181D]"; + export function BecomeContributor() { const handleContactClick = () => { const parts = ['hello', 'react', 'foundation']; @@ -138,34 +142,31 @@ export function BecomeContributor() { }; return ( -
-
-
-

- Become a Contributor -

-

- Join the movement to sustain and grow the React ecosystem. Contribute code, - organize communities, create educational content, or support financially — - every pathway helps build a stronger ecosystem. -

-
+ + Become a contributor + + Contribute code, organize communities, create educational content, or support + financially. Every pathway helps sustain and grow the React ecosystem. + -
+
{contributorData.map((item) => ( - {item.icon}} - title={item.title} - description={item.description} - actions={ - <> + className="grid grid-cols-[24px_minmax(0,1fr)] items-start gap-x-5 py-6" + > + +
+

{item.title}

+

+ {item.description} +

+
{item.primaryAction.label} @@ -173,29 +174,29 @@ export function BecomeContributor() { {item.secondaryAction && ( {item.secondaryAction.label} )} - - } - /> +
+
+
))}
-
-

+

+

Questions about contributing?{" "}

-
+ ); } diff --git a/src/components/home/ecosystem-libraries.tsx b/src/components/home/ecosystem-libraries.tsx index 0eb756a..925494f 100644 --- a/src/components/home/ecosystem-libraries.tsx +++ b/src/components/home/ecosystem-libraries.tsx @@ -1,5 +1,4 @@ -import { ButtonLink } from "@/components/ui/button"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; +import { Panel, PanelEyebrow, PanelSub } from "@/components/panels/panel"; import { ecosystemLibraries } from "@/lib/maintainer-tiers"; import { LibraryCard } from "@/components/ui/library-card"; import { libraryDisplayNames } from "@/lib/library-icons"; @@ -16,7 +15,7 @@ interface EcosystemLibrariesProps { export function EcosystemLibraries({ id = "libraries", - title = "Supported Ecosystem", + title = "Supported ecosystem", description, risScores, showRIS = false, @@ -26,7 +25,7 @@ export function EcosystemLibraries({ const risScoreMap = risScores ? new Map(risScores.map((score) => [score.repo, score.ris])) : new Map(); - + // Use dynamic count from ecosystemLibraries if description not provided const libraryCount = ecosystemLibraries.length; const defaultDescription = `We track contributions across all ${libraryCount} critical React ecosystem libraries:`; @@ -99,60 +98,64 @@ owner/repo: ]; return ( - -
-

{title}

-

{displayDescription}

+ + {title} + {displayDescription} -
- {categorizedLibraries.map((cat) => { - const libs = ecosystemLibraries.filter((l) => l.category === cat.category); - if (libs.length === 0) return null; + {/* + LibraryCard is shared (read-only here) and styles itself with semantic theme + tokens. Panels keep constant colors in both themes, so the wrapper pins the + tokens it consumes to light-theme values: cards stay legible on paper when + the site theme is dark. + */} +
+ {categorizedLibraries.map((cat) => { + const libs = ecosystemLibraries.filter((l) => l.category === cat.category); + if (libs.length === 0) return null; - return ( -
-

- {cat.name} · {libs.length} {libs.length === 1 ? "library" : "libraries"} -

-
- {libs.map((lib, idx) => ( - - ))} -
+ return ( +
+

+ {cat.name} · {libs.length} {libs.length === 1 ? "library" : "libraries"} +

+
+ {libs.map((lib, idx) => ( + + ))}
- ); - })} -
+
+ ); + })} +
-
-

- Total: {ecosystemLibraries.length} libraries tracked · All contributions - verified via GitHub +

+ Total: {ecosystemLibraries.length} libraries tracked · All contributions + verified via GitHub +

+ + {showMissingLibraryIssue ? ( +
+

+ Don't see a library?

+ + Add a missing library +
- - {showMissingLibraryIssue ? ( -
-

- Don't see a library? -

- - Add a missing library - -
- ) : null} -
-
+ ) : null} + ); } diff --git a/src/components/home/executive-message.tsx b/src/components/home/executive-message.tsx index 37aca45..97a071d 100644 --- a/src/components/home/executive-message.tsx +++ b/src/components/home/executive-message.tsx @@ -1,110 +1,102 @@ -import { ScrollReveal } from "@/components/ui/scroll-reveal"; import Image from "next/image"; +import { Panel, PanelEyebrow } from "@/components/panels/panel"; + export function ExecutiveMessage() { return ( - -
-
-
-
- Seth Webster, Executive Director -
-
-

- A Message from Our Executive Director -

-
- -
-

- You know, every so often, something comes along in software that changes not - just how we build — but why we build. -

- -

React did that.

- -

- It gave us more than a way to render UIs. It gave us a new way to think — - about composition, about state, about expressing ideas. But even more than - that, it gave us a new way to connect with one another. -

- -

- From the very beginning, React has been about people. About curiosity shared - in the open. About mentorship that crosses companies, countries, and time - zones. About a community that believes — deeply — that if we help others - bring their ideas to life, ours will follow. -

- -

- That belief has powered one of the most influential movements in modern - software history. React has shaped how the web is built, how mobile is built, - even how we as developers think about creativity itself. And yet, for all its - reach and impact, its heart has never changed: it's still about people - building together. -

- -

That's why we created the React Foundation.

- -

- The Foundation exists to make sure React's future is independent, - community-driven, and open — forever. It's here to protect the culture - that brought us all this way, and to nurture what comes next: the next - generation of maintainers, educators, experimenters, and dreamers. -

- -

- We're doing that by working hand-in-hand with incredible partners — - Meta, Microsoft, Amazon, Vercel, Expo, Callstack, Software Mansion, and many - more — but more importantly, by working with you, the global community of - developers who make React what it is. -

- -

- - Because this isn't just about governance. It's about legacy. -
- It's about ensuring that the ideas we build together endure. -
-

- -

- I've had the privilege of leading React at Meta for many years, and now, - as the Executive Director of the React Foundation, I carry the same North Star - that's guided me all along: helping others bring their ideas to life. -

- -

- If React has ever inspired you — to learn, to teach, to build, to share — - then you are already part of this story. The Foundation is here to help that - story grow, together. -

- -

- Thank you for everything you've built so far — and for everything - you're about to. -

- -

- Let's make the next chapter one that lasts for generations. -

-
- -
-
-

Seth Webster

-

Executive Director, React Foundation

-
-
-
-
+ + A message from our executive director + +
+ Seth Webster, Executive Director +
+ +

+ You know, every so often, something comes along in software that changes not + just how we build — but why we build. +

+ +
+

React did that.

+ +

+ It gave us more than a way to render UIs. It gave us a new way to think — + about composition, about state, about expressing ideas. But even more than + that, it gave us a new way to connect with one another. +

+ +

+ From the very beginning, React has been about people. About curiosity shared + in the open. About mentorship that crosses companies, countries, and time + zones. About a community that believes — deeply — that if we help others + bring their ideas to life, ours will follow. +

+ +

+ That belief has powered one of the most influential movements in modern + software history. React has shaped how the web is built, how mobile is built, + even how we as developers think about creativity itself. And yet, for all its + reach and impact, its heart has never changed: it's still about people + building together. +

+ +

That's why we created the React Foundation.

+ +

+ The Foundation exists to make sure React's future is independent, + community-driven, and open — forever. It's here to protect the culture + that brought us all this way, and to nurture what comes next: the next + generation of maintainers, educators, experimenters, and dreamers. +

+ +

+ We're doing that by working hand-in-hand with incredible partners — + Meta, Microsoft, Amazon, Vercel, Expo, Callstack, Software Mansion, and many + more — but more importantly, by working with you, the global community of + developers who make React what it is. +

+ +

+ + Because this isn't just about governance. It's about legacy. +
+ It's about ensuring that the ideas we build together endure. +
+

+ +

+ I've had the privilege of leading React at Meta for many years, and now, + as the Executive Director of the React Foundation, I carry the same North Star + that's guided me all along: helping others bring their ideas to life. +

+ +

+ If React has ever inspired you — to learn, to teach, to build, to share — + then you are already part of this story. The Foundation is here to help that + story grow, together. +

+ +

+ Thank you for everything you've built so far — and for everything + you're about to. +

+ +

+ Let's make the next chapter one that lasts for generations. +

+
+ +
+

Seth Webster

+

Executive Director, React Foundation

+
+
); } diff --git a/src/components/home/founding-members.tsx b/src/components/home/founding-members.tsx index 3d906cf..d5bacad 100644 --- a/src/components/home/founding-members.tsx +++ b/src/components/home/founding-members.tsx @@ -1,166 +1,50 @@ -import Image from "next/image"; -import { ButtonLink } from "@/components/ui/button"; -import { ScrollReveal } from "@/components/ui/scroll-reveal"; +import { Panel, PanelButton, PanelEyebrow, PanelSub, Row, RowArrow, RowList, RowRight } from "@/components/panels/panel"; -type ImageMember = { - name: string; - src: string; - filter?: string; - /** Normalized max-width to balance visual weight across logos */ - maxWidth?: number; - /** Override container height (px). Defaults to 40 (h-10). */ - height?: number; -}; - -type TextMember = { - name: string; - text: string; - /** Font size class to balance visual weight with image logos */ - textSize?: string; -}; - -type Member = ImageMember | TextMember; - -function isTextMember(m: Member): m is TextMember { - return "text" in m; -} - -const WHITE_FILTER = "brightness(0) invert(1)"; - -const FOUNDING_MEMBERS: Member[] = [ - { - name: "Meta", - src: "/assets/founding-members/meta.svg", - filter: WHITE_FILTER, - maxWidth: 90, - }, - { - name: "Amazon Developer", - src: "/assets/founding-members/amazon.svg", - filter: WHITE_FILTER, - maxWidth: 89, - }, - { - name: "Microsoft", - src: "/assets/founding-members/microsoft.svg", - filter: WHITE_FILTER, - maxWidth: 132, - height: 58, - }, - { - name: "Huawei", - src: "/assets/founding-members/huawei.svg", - filter: WHITE_FILTER, - maxWidth: 90, - }, - { - name: "Software Mansion", - src: "/assets/founding-members/software-mansion.svg", - maxWidth: 120, - }, - { - name: "Expo", - src: "/assets/founding-members/expo.svg", - filter: WHITE_FILTER, - maxWidth: 90, - }, - { - name: "Callstack", - src: "/assets/founding-members/callstack.svg", - maxWidth: 120, - }, - { - name: "Vercel", - src: "/assets/founding-members/vercel.svg", - filter: WHITE_FILTER, - maxWidth: 90, - }, +const MEMBER_COLUMNS: string[][] = [ + ["Meta", "Amazon Developer", "Microsoft", "Huawei"], + ["Software Mansion", "Expo", "Callstack", "Vercel"], ]; export function FoundingMembers() { return ( - -
-
-

- Founding Members -

-

- We're grateful to our founding members who believe in - sustaining the React ecosystem and supporting open source - maintainers. -

-
- -
- {/* Background */} -
-
- - {/* Logo grid */} -
- {FOUNDING_MEMBERS.map((member) => ( -
- {isTextMember(member) ? ( - - {member.text} - - ) : ( -
- {`${member.name} -
- )} -
+ + Founding members + + We're grateful to our founding members who believe in sustaining the React + ecosystem and supporting open source maintainers. + + +
+ {MEMBER_COLUMNS.map((column, columnIndex) => ( + 0 ? "max-md:border-t max-md:border-[color:var(--panel-rule)]" : undefined + } + > + {column.map((name) => ( + + {name} + + + + ))} -
-
- -
-
-
-

- Become a member -

-

- Help fund React maintainers, education, and ecosystem support. -

-
- - Become a member - -
+ + ))} +
+ +
+
+

Become a member

+

+ Help fund React maintainers, education, and ecosystem support. +

-
-
+ + Become a member + +
+ ); } From 83f60e9e76d50a457a276f36deef4eaf476f1618 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 01:20:11 -0700 Subject: [PATCH 14/28] Feat: restyle communities routes to Panels Filters, sort, debounced URL sync, SWR fetches, Leaflet wiring, and Suspense boundaries are untouched (diff-audited); the filters/list section hand-rolls its paper surface because Panel's overflow-hidden would clip the sticky rail and dropdowns. Signed-off-by: lauren --- src/app/communities/[slug]/page.tsx | 635 +++++++++--------- src/app/communities/page.tsx | 223 +++--- .../communities/AddCommunityCTA.tsx | 4 +- .../communities/CommunityFilters.tsx | 107 ++- src/components/communities/CommunityList.tsx | 201 ++---- src/components/communities/CommunityMap.tsx | 89 +-- .../communities/CommunitySortDropdown.tsx | 10 +- src/components/communities/CommunityStats.tsx | 45 +- 8 files changed, 585 insertions(+), 729 deletions(-) diff --git a/src/app/communities/[slug]/page.tsx b/src/app/communities/[slug]/page.tsx index db0a3dc..6d1bcba 100644 --- a/src/app/communities/[slug]/page.tsx +++ b/src/app/communities/[slug]/page.tsx @@ -5,15 +5,25 @@ import { notFound } from 'next/navigation'; import Link from 'next/link'; +import { Cake, Calendar, CircleUserRound, MapPin, Users } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; import { getCommunityHostLabel } from '@/lib/community-host'; import { getCommunityBySlug } from '@/lib/redis-communities'; -import { RFDS } from '@/components/rfds'; +import { OrbitMarks, Panel, PanelActions, PanelButton, PanelEyebrow, RowList } from '@/components/panels/panel'; +import { PanelsFooter } from '@/components/panels/panels-footer'; import type { Metadata } from 'next'; interface CommunityPageProps { params: Promise<{ slug: string }>; } +const FOCUS_RING = + 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-[#16181D]'; + +const INK_BUTTON = `panels-anim inline-flex items-center justify-center rounded-xl border border-[#16181D] bg-[#16181D] px-6 py-3.5 text-[15px] font-semibold leading-[1.2] text-[#F6F7F9]! hover:border-[#07090D] hover:bg-[#07090D] ${FOCUS_RING}`; + +const OUTLINE_BUTTON = `panels-anim inline-flex items-center justify-center rounded-xl border border-[#16181D] bg-transparent px-6 py-3.5 text-[15px] font-semibold leading-[1.2] text-[#16181D] hover:bg-[rgba(22,24,29,0.08)] ${FOCUS_RING}`; + export async function generateMetadata({ params, }: CommunityPageProps): Promise { @@ -43,363 +53,338 @@ export default async function CommunityPage({ params }: CommunityPageProps) { const tierInfo = getTierInfo(community.cois_tier); return ( -
- {/* Hero Section */} -
-
-
- {/* Breadcrumb */} - - - {/* Header */} -
-
-
-

- {community.name} -

- {community.verified && ( - - ✓ - - )} -
+
+ + +
+ -

- {community.city} - {community.region && `, ${community.region}`}, {community.country} -

+
+
+
+

+ {community.name} +

+ {community.verified && ( + + ✓ Verified + + )} +
- {/* Event Types */} -
- {community.event_types.map((type) => ( - - {type} - - ))} -
+

+ {community.city} + {community.region && `, ${community.region}`}, {community.country} +

+ +
+ {community.event_types.map((type) => ( + + {type} + + ))}
+
- {/* CoIS Tier Badge */} - {tierInfo && ( -
-
{tierInfo.icon}
-
- {tierInfo.label} -
-
- CoIS Tier + {tierInfo && ( +
+

+ CoIS Tier +

+
+ {tierInfo.label} +
+ {community.cois_score && ( +
+ {community.cois_score.toFixed(2)}
- {community.cois_score && ( -
- {community.cois_score.toFixed(2)} + )} +
+ )} +
+ + + {community.meetup_url && ( + + Join on {getCommunityHostLabel(community.meetup_url)} → + + )} + {community.website && ( + + Visit Website → + + )} + {community.discord_url && ( + + Join Discord + + )} + +
+ + +
+
+ + About this community +

+ {community.description} +

+
+ + + By the numbers + + + + + + + + + {community.organizers && community.organizers.length > 0 && ( + + Organizers + + {community.organizers.map((organizer) => ( +
+
+
- )} -
- )} -
+
+
+ {organizer.name} +
+
+ {organizer.role} +
+ {organizer.twitter_handle && ( + + @{organizer.twitter_handle} + + )} +
+
+ ))} + + + )} - {/* Quick Actions */} - + + + {community.last_event_date && ( + + Latest activity +

+ Last event:{' '} + + {new Date(community.last_event_date).toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + })} + +

+
+ )}
-
- - {/* Main Content */} -
-
-
- {/* Main Column */} -
- {/* About */} -
-

- About This Community -

-

- {community.description} -

-
- {/* Stats Grid */} -
- - - +

+ Community details +

+ +
+

Status

+
+ +
+
+ +
+

+ Quick Info +

+ + + {community.coordinates && ( + - -
- - {/* Organizers */} - {community.organizers && community.organizers.length > 0 && ( -
-

- Organizers -

-
- {community.organizers.map((organizer) => ( -
-
- 👤 -
-
-
- {organizer.name} -
-
- {organizer.role} -
- {organizer.twitter_handle && ( - - @{organizer.twitter_handle} - - )} -
-
- ))} -
-
)} + +
- {/* Languages */} -
-

- Languages -

-
- - {community.primary_language} - - {community.secondary_languages?.map((lang) => ( - - {lang} - - ))} -
-
+ {(community.twitter_handle || + community.linkedin_url || + community.slack_url) && ( +
+

+ Connect +

+ + {community.twitter_handle && ( + + Twitter → + + )} + {community.linkedin_url && ( + + LinkedIn → + + )} + {community.slack_url && ( + + Slack → + + )} + +
+ )} - {/* Last Event */} - {community.last_event_date && ( -
-

- Latest Activity -

-

- Last event:{' '} - - {new Date(community.last_event_date).toLocaleDateString('en-US', { - year: 'numeric', - month: 'long', - day: 'numeric', - })} - -

-
- )} +
+

+ Want to Organize? +

+

+ Start your own React community and earn CoIS rewards +

+
+ + Learn More +
+
+ +
- {/* Sidebar */} - -
-
- +
); } -function StatBox({ +function StatRow({ + icon: Icon, label, value, - icon, capitalize, }: { + icon: LucideIcon; label: string; value: string; - icon: string; capitalize?: boolean; }) { return ( - +
+
); } function StatusBadge({ status }: { status: string }) { const config = { - active: { bg: 'bg-green-500/10', text: 'text-green-600 dark:text-green-400', label: 'Active' }, - new: { bg: 'bg-blue-500/10', text: 'text-blue-600 dark:text-blue-400', label: 'New' }, - paused: { bg: 'bg-yellow-500/10', text: 'text-yellow-600 dark:text-yellow-400', label: 'Paused' }, - inactive: { bg: 'bg-gray-500/10', text: 'text-gray-600 dark:text-gray-400', label: 'Inactive' }, - }[status] || { bg: 'bg-muted', text: 'text-muted-foreground', label: status }; + active: { bg: 'bg-[#44AC99]', text: 'text-[#16181D]', label: 'Active' }, + new: { bg: 'bg-[#087EA4]', text: 'text-white', label: 'New' }, + paused: { bg: 'bg-[#C76A15]', text: 'text-[#16181D]', label: 'Paused' }, + inactive: { bg: 'bg-[#5E687E]', text: 'text-white', label: 'Inactive' }, + }[status] || { bg: 'border border-[rgba(22,24,29,0.2)]', text: 'text-[#5E687E]', label: status }; return ( -
+
{config.label}
); @@ -407,9 +392,9 @@ function StatusBadge({ status }: { status: string }) { function InfoRow({ label, value }: { label: string; value: string }) { return ( -
- {label}: - {value} +
+ {label} + {value}
); } @@ -418,30 +403,22 @@ function getTierInfo(tier?: string) { switch (tier) { case 'platinum': return { - icon: '💎', label: 'Platinum', - textColor: 'text-cyan-400', description: 'Top 5% - Elite community builder', }; case 'gold': return { - icon: '🏆', label: 'Gold', - textColor: 'text-yellow-400', description: 'Top 15% - Outstanding community', }; case 'silver': return { - icon: '🥈', label: 'Silver', - textColor: 'text-gray-400', description: 'Top 30% - Excellent community', }; case 'bronze': return { - icon: '🥉', label: 'Bronze', - textColor: 'text-orange-400', description: 'Top 50% - Valued community', }; default: diff --git a/src/app/communities/page.tsx b/src/app/communities/page.tsx index 2a1783e..e5d01c9 100644 --- a/src/app/communities/page.tsx +++ b/src/app/communities/page.tsx @@ -10,6 +10,15 @@ import { CommunityList } from '@/components/communities/CommunityList'; import { CommunityStats } from '@/components/communities/CommunityStats'; import { CommunitySortDropdown } from '@/components/communities/CommunitySortDropdown'; import { AddCommunityCTA } from '@/components/communities/AddCommunityCTA'; +import { + OrbitMarks, + Panel, + PanelActions, + PanelButton, + PanelEyebrow, + PanelSub, +} from '@/components/panels/panel'; +import { PanelsFooter } from '@/components/panels/panels-footer'; import './leaflet.css'; export const metadata = { @@ -19,128 +28,115 @@ export const metadata = { export default function CommunitiesPage() { return ( -
- {/* Hero Section */} -
-
-
-

- Find Your React Community -

-

- Connect with React developers through meetups, conferences, and - study groups around the world. -

- - -
+
+ + +
+

+ Find Your React Community +

+

+ Connect with React developers through meetups, conferences, and + study groups around the world. +

+ + + Explore Communities + + + Start a Community + +
-
- - {/* Stats Bar - Dynamic from Redis */} -
-
- }> - + + + + By the numbers + }> + + + + + + Communities worldwide + Click any marker to learn more about a community +
+ }> +
-
+ + + + {/* + * Not the Panel primitive: its overflow-hidden would defeat the sticky + * filters and clip the sort dropdown menus, so this section recreates the + * paper surface (and its tone variables) with overflow left visible. + */} +
+ All communities +
+ - {/* Map Section */} -
-
-
-

- Communities Worldwide -

-

- Click any marker to learn more about a community -

-
+
+
+ +
-
- }> - + }> + -
- - {/* Add Community CTA */} - +
- {/* Main Content: Filters + List */} -
-
-
- {/* Filters Sidebar */} - - - {/* Community List */} -
-
-

- All Communities -

- -
- - }> - - -
+ +
+
+

+ Don't See a Community Near You? +

+

+ Starting a React community is easier than you think. We provide + resources, templates, and support to help you succeed. +

+ + + Start Your Own Community + +
-
+ - {/* CTA Section */} -
-
-

- Don't See a Community Near You? -

-

- Starting a React community is easier than you think. We provide - resources, templates, and support to help you succeed. -

- - Start Your Own Community - -
-
+
); } function StatsSkeleton() { return ( -
+
{[1, 2, 3, 4].map((i) => ( -
-
-
+
+
+
))}
@@ -149,31 +145,28 @@ function StatsSkeleton() { function FiltersSkeleton() { return ( -
-
-
-
-
+
+
+
+
+
); } function MapSkeleton() { return ( -
-
-
🗺️
-

Loading map...

-
+
+

Loading map...

); } function ListSkeleton() { return ( -
+
{[1, 2, 3, 4, 5].map((i) => ( -
+
))}
); diff --git a/src/components/communities/AddCommunityCTA.tsx b/src/components/communities/AddCommunityCTA.tsx index e2d4ae0..bc94bfa 100644 --- a/src/components/communities/AddCommunityCTA.tsx +++ b/src/components/communities/AddCommunityCTA.tsx @@ -8,11 +8,11 @@ import Link from 'next/link'; export function AddCommunityCTA() { return (
-

+

Don't see your community listed?{' '} Add it now → diff --git a/src/components/communities/CommunityFilters.tsx b/src/components/communities/CommunityFilters.tsx index 174529b..e0914fa 100644 --- a/src/components/communities/CommunityFilters.tsx +++ b/src/components/communities/CommunityFilters.tsx @@ -7,7 +7,7 @@ import { useEffect, useRef, useState, type RefObject } from 'react'; import { useRouter, useSearchParams } from 'next/navigation'; -import { RFDS } from '@/components/rfds'; +import { Search } from 'lucide-react'; import { SortDropdown } from '@/components/ui/sort-dropdown'; import type { CommunityFilters as Filters, CommunityStatusFilter, EventType } from '@/types/community'; @@ -19,6 +19,9 @@ const STATUS_OPTIONS: Array<{ value: CommunityStatusFilter; label: string }> = [ { value: 'inactive', label: 'Inactive' }, ]; +const FOCUS_RING = + 'focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-solid focus-visible:outline-[#16181D]'; + function useClearDebounceOnUnmount(timer: RefObject | null>) { useEffect(() => { function clearDebounce() { @@ -126,27 +129,24 @@ export function CommunityFilters() { (filters.status && filters.status !== 'all'); return ( -

- {/* Header */} +
-

Filters

+

Filters

{hasActiveFilters && ( - Clear all - + )}
- {/* Status shortcuts */}
- +
{STATUS_OPTIONS.map((option) => { const selected = (filters.status || 'all') === option.value; @@ -157,8 +157,8 @@ export function CommunityFilters() { type="button" onClick={() => updateFilter('status', option.value)} className={selected - ? 'rounded-full bg-primary px-3 py-1.5 text-sm font-medium text-primary-foreground transition' - : 'rounded-full border border-border bg-card px-3 py-1.5 text-sm font-medium text-muted-foreground transition hover:border-primary hover:text-primary' + ? `panels-anim rounded-full border border-[#16181D] bg-[#16181D] px-3 py-1.5 text-[13px] font-medium text-[#F6F7F9]! ${FOCUS_RING}` + : `panels-anim rounded-full border border-[rgba(22,24,29,0.2)] px-3 py-1.5 text-[13px] font-medium text-[#5E687E] hover:bg-[rgba(22,24,29,0.08)] hover:text-[#16181D] ${FOCUS_RING}` } > {option.label} @@ -168,34 +168,43 @@ export function CommunityFilters() {
- {/* Search */}
-
- {/* Event Types */}
- +
{(['meetup', 'conference', 'workshop', 'hackathon', 'virtual'] as EventType[]).map( (type) => ( - @@ -204,44 +213,30 @@ export function CommunityFilters() {
- {/* CoIS Tier */}
+ + CoIS Tier + updateFilter('cois_tier', value as Filters['cois_tier'])} />
- {/* Toggles */} - {/*
- - -
*/} - {/* Apply button (for mobile) */} - +
); } diff --git a/src/components/communities/CommunityList.tsx b/src/components/communities/CommunityList.tsx index 8ab0392..d23f71c 100644 --- a/src/components/communities/CommunityList.tsx +++ b/src/components/communities/CommunityList.tsx @@ -1,15 +1,14 @@ /** * Community List Component - * List view of communities with cards + * List view of communities with ruled rows */ 'use client'; import useSWR from 'swr'; -import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; -import { getCommunityHostLabel } from '@/lib/community-host'; -import { VerificationBadge } from './VerificationBadge'; +import type { ReactNode } from 'react'; +import { Row, RowArrow, RowList, RowRight } from '@/components/panels/panel'; import type { Community } from '@/types/community'; // Fetcher for SWR @@ -135,9 +134,9 @@ export function CommunityList() { if (isLoading) { return ( -
+
{[1, 2, 3].map((i) => ( -
+
))}
); @@ -145,162 +144,82 @@ export function CommunityList() { if (error) { return ( -
-

Failed to load communities

-

Please try refreshing the page

+
+

Failed to load communities

+

Please try refreshing the page

); } return ( -
-

+

+

Showing {communities.length} React communities worldwide

- {communities.map((community) => ( - - ))} + + {communities.map((community) => ( + + ))} + {communities.length === 0 && ( -
-

No communities found

+
+

No communities found

)}
); } -function CommunityCard({ community }: { community: Community }) { - const tierBadge = getTierBadge(community.cois_tier); - +function CommunityRow({ community }: { community: Community }) { return ( -
-
- {/* Main Info */} -
-
-
-
-

- {community.name} -

- -
-

- {community.city} - {community.region && `, ${community.region}`}, {community.country} -

-
- {tierBadge && ( - - {tierBadge.icon} {tierBadge.label} - - )} -
- -

- {community.description} -

- - {/* Stats */} -
-
- 👥 - {community.member_count.toLocaleString()} members -
-
- 📅 - {community.meeting_frequency} -
- {community.last_event_date && ( -
- 🎯 - Last event: {new Date(community.last_event_date).toLocaleDateString()} -
- )} -
- - {/* Event Types */} -
- {community.event_types.map((type) => ( - - {type} - - ))} -
-
- - {/* Actions */} -
- - View Details - - {community.meetup_url ? ( - - - - - Join on {getCommunityHostLabel(community.meetup_url)} - - ) : community.website && ( - - Visit Website → - + +
+
+

{community.name}

+ {getVerificationLabel(community)} + {community.cois_tier && community.cois_tier !== 'none' && ( + + {community.cois_tier} + )} + {community.event_types.map((type) => ( + + {type} + + ))}
+

+ {community.city} + {community.region && `, ${community.region}`}, {community.country} +

-
+ + + {community.member_count.toLocaleString()} members + + + + ); } -function getTierBadge(tier?: string): { icon: string; label: string; className: string } | null { - switch (tier) { - case 'platinum': - return { - icon: '💎', - label: 'Platinum', - className: 'bg-gradient-to-r from-cyan-400 to-blue-400 text-white', - }; - case 'gold': - return { - icon: '🏆', - label: 'Gold', - className: 'bg-gradient-to-r from-yellow-400 to-orange-400 text-white', - }; - case 'silver': - return { - icon: '🥈', - label: 'Silver', - className: 'bg-gradient-to-r from-gray-300 to-gray-400 text-white', - }; - case 'bronze': - return { - icon: '🥉', - label: 'Bronze', - className: 'bg-gradient-to-r from-orange-300 to-orange-400 text-white', - }; - default: - return null; - } +function MonoChip({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +function getVerificationLabel(community: Community): string { + const status = + community.verification_status || (community.verified ? 'verified' : 'pending'); + + return { + verified: 'Verified', + pending: 'Pending Review', + rejected: 'Not Verified', + }[status]; } diff --git a/src/components/communities/CommunityMap.tsx b/src/components/communities/CommunityMap.tsx index 77217ec..c5181b1 100644 --- a/src/components/communities/CommunityMap.tsx +++ b/src/components/communities/CommunityMap.tsx @@ -233,13 +233,12 @@ export function CommunityMap() { if (!isClient || isLoading || !L) { return ( -
+
-
🗺️
-

+

{isLoading ? 'Loading communities...' : !L ? 'Loading map library...' : 'Loading map...'}

-

+

{isLoading ? `Fetching ${communities.length} communities` : 'Initializing Leaflet'}

@@ -254,15 +253,10 @@ export function CommunityMap() { console.log('🗺️ CommunityMap: Rendering map, isClient:', isClient); return ( -
- {/* Debug indicator */} - {/*
- MAP CONTAINER VISIBLE -
*/} - +
{/* Map Legend */} -
-

CoIS Tier

+
+

CoIS Tier

@@ -299,57 +293,53 @@ export function CommunityMap() { title={`${community.name} - ${community.cois_tier || community.status}`} > -
- {/* Header with tier badge */} +
{community.cois_tier && ( -
- - {getTierIcon(community.cois_tier)} {community.cois_tier} +
+ +
)} -

+

{community.name}

-

+

{community.city} {community.region && `, ${community.region}`}, {community.country}

-

+

{community.description}

-
+
-
Members
-
+
Members
+
{community.member_count.toLocaleString()}
-
Frequency
-
+
Frequency
+
{community.meeting_frequency}
-
+
{community.event_types.map((type) => ( {type} @@ -359,11 +349,7 @@ export function CommunityMap() {
View Details @@ -372,11 +358,7 @@ export function CommunityMap() { href={community.meetup_url} target="_blank" rel="noopener noreferrer" - className="flex-shrink-0 rounded-lg px-3 py-2.5 text-sm font-semibold transition hover:opacity-90" - style={{ - backgroundColor: '#ED1C40', - color: '#ffffff' - }} + className="panels-anim flex-shrink-0 rounded-xl bg-[#ED1C40] px-3 py-2.5 text-sm font-semibold text-white! hover:bg-[#C8173A]" title={`Join on ${getCommunityHostLabel(community.meetup_url)}`} > @@ -415,21 +397,6 @@ function getTierColorHex(tier?: string, status?: string): string { } } -function getTierBadgeColor(tier: string): string { - switch (tier) { - case 'platinum': - return 'bg-gradient-to-r from-cyan-400 to-blue-400'; - case 'gold': - return 'bg-gradient-to-r from-yellow-400 to-orange-400'; - case 'silver': - return 'bg-gradient-to-r from-gray-300 to-gray-400'; - case 'bronze': - return 'bg-gradient-to-r from-orange-300 to-orange-400'; - default: - return 'bg-primary'; - } -} - function getTierIcon(tier: string, status?: string): string { // Different icons for inactive/paused if (status === 'inactive') return '⏸'; @@ -457,7 +424,7 @@ function TierLegend({ tier }: { tier: string }) { className="w-3 h-3 rounded-full" style={{ backgroundColor: getTierColorHex(tier) }} /> - {tier} + {tier}
); } diff --git a/src/components/communities/CommunitySortDropdown.tsx b/src/components/communities/CommunitySortDropdown.tsx index f8fb604..b8be536 100644 --- a/src/components/communities/CommunitySortDropdown.tsx +++ b/src/components/communities/CommunitySortDropdown.tsx @@ -9,11 +9,11 @@ import { useRouter, useSearchParams } from 'next/navigation'; import { SortDropdown } from '@/components/ui/sort-dropdown'; const SORT_OPTIONS = [ - { value: 'members', label: '👥 Most Members' }, - { value: 'activity', label: '🔥 Most Active' }, - { value: 'cois', label: '🏆 Highest CoIS' }, - { value: 'name', label: '🔤 Name (A-Z)' }, - { value: 'recent', label: '📅 Recently Active' }, + { value: 'members', label: 'Most Members' }, + { value: 'activity', label: 'Most Active' }, + { value: 'cois', label: 'Highest CoIS' }, + { value: 'name', label: 'Name (A-Z)' }, + { value: 'recent', label: 'Recently Active' }, ]; export function CommunitySortDropdown() { diff --git a/src/components/communities/CommunityStats.tsx b/src/components/communities/CommunityStats.tsx index edbd0c8..db2a27f 100644 --- a/src/components/communities/CommunityStats.tsx +++ b/src/components/communities/CommunityStats.tsx @@ -6,7 +6,9 @@ 'use client'; import useSWR from 'swr'; -import { RFDS } from '@/components/rfds'; +import { Activity, Globe, MapPin, Users } from 'lucide-react'; +import type { LucideIcon } from 'lucide-react'; +import { RowList } from '@/components/panels/panel'; const fetcher = (url: string) => fetch(url).then((res) => res.json()); @@ -15,11 +17,11 @@ export function CommunityStats() { if (isLoading || !data) { return ( -
+
{[1, 2, 3, 4].map((i) => ( -
-
-
+
+
+
))}
@@ -28,9 +30,9 @@ export function CommunityStats() { if (error || !data.success) { return ( -
+

Failed to load stats -

+

); } @@ -41,37 +43,40 @@ export function CommunityStats() { const showActiveStat = activePercentage >= 0.75; return ( -
- + - - {showActiveStat && ( - )} -
+ ); } -function StatCard({ number, label }: { number: string; label: string }) { +function StatRow({ icon: Icon, number, label }: { icon: LucideIcon; number: string; label: string }) { return ( - +
+
); } From 56715dd49b0297c9f2fbc6ecf61480eade895199 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 01:23:00 -0700 Subject: [PATCH 15/28] Feat: extend floating panel header to all foundation routes The isHome condition becomes a foundation-route prefix list, renamed isPanelsRoute; store, admin, profile, and coming-soon keep the classic bar. Also inlines the sign-in Coming Soon badge so it stops overlapping the GitLab label. Signed-off-by: lauren --- src/app/auth/signin/page.tsx | 2 +- src/components/layout/header.tsx | 27 +++++++++++++++++++++------ 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/src/app/auth/signin/page.tsx b/src/app/auth/signin/page.tsx index ab9f35d..121200c 100644 --- a/src/app/auth/signin/page.tsx +++ b/src/app/auth/signin/page.tsx @@ -81,7 +81,7 @@ export default function SignInPage() { Continue with GitLab - + Coming Soon diff --git a/src/components/layout/header.tsx b/src/components/layout/header.tsx index 3be949f..786ebf2 100644 --- a/src/components/layout/header.tsx +++ b/src/components/layout/header.tsx @@ -18,8 +18,23 @@ export function Header() { const [isAdmin, setIsAdmin] = useState(false); const isStorePage = pathname?.startsWith("/store"); const isComingSoonPage = pathname === "/coming-soon"; - // The redesigned homepage renders the header as a floating panel; other routes keep the classic bar. - const isHome = pathname === "/"; + // Redesigned foundation routes render the header as a floating panel; store, + // admin, profile, and coming-soon keep the classic bar until their own pass. + const PANELS_ROUTE_PREFIXES = [ + "/about", + "/impact", + "/libraries", + "/updates", + "/authors", + "/communities", + "/become-a-member", + "/privacy", + "/terms", + "/auth", + ]; + const isPanelsRoute = + pathname === "/" || + PANELS_ROUTE_PREFIXES.some((prefix) => pathname?.startsWith(prefix)); // Check admin status when user session changes useEffect(() => { @@ -49,20 +64,20 @@ export function Header() { return (
{/* Logo */} - {isHome ? ( + {isPanelsRoute ? (
- ) : isHome ? ( + ) : isPanelsRoute ? ( // Ink button per the panels language; `!` outranks the unlayered `a.inline-flex { color: currentColor }` rule. Date: Fri, 12 Jun 2026 01:30:04 -0700 Subject: [PATCH 16/28] Fix: restore external actions on community list rows The restyle collapsed each card to a single detail link and dropped the Meetup and Website actions. The row is now a div hover surface (anchors cannot nest) with the detail link on the title block and arrow, and the external links restored alongside. Signed-off-by: lauren --- src/components/communities/CommunityList.tsx | 57 ++++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/components/communities/CommunityList.tsx b/src/components/communities/CommunityList.tsx index d23f71c..6f57535 100644 --- a/src/components/communities/CommunityList.tsx +++ b/src/components/communities/CommunityList.tsx @@ -6,9 +6,10 @@ 'use client'; import useSWR from 'swr'; +import Link from 'next/link'; import { useSearchParams } from 'next/navigation'; import type { ReactNode } from 'react'; -import { Row, RowArrow, RowList, RowRight } from '@/components/panels/panel'; +import { RowArrow, RowList } from '@/components/panels/panel'; import type { Community } from '@/types/community'; // Fetcher for SWR @@ -172,10 +173,19 @@ export function CommunityList() { ); } +/* + * Not the shared Row: the row hosts secondary external links, and anchors + * cannot nest inside the Row anchor, so the hover surface is a div and the + * detail link wraps only the title block and the trailing arrow. + */ function CommunityRow({ community }: { community: Community }) { + const detailHref = `/communities/${community.slug}`; return ( - -
+
+

{community.name}

{getVerificationLabel(community)} @@ -194,14 +204,39 @@ function CommunityRow({ community }: { community: Community }) { {community.city} {community.region && `, ${community.region}`}, {community.country}

-
- - - {community.member_count.toLocaleString()} members - - - - + + + {community.meetup_url && ( + + Meetup ↗ + + )} + {community.website && ( + + Website ↗ + + )} + + + {community.member_count.toLocaleString()} members + + + + +
); } From d60eadffff0a4fdaf610bdc45d98cd7acf66b0bb Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 10:07:18 -0700 Subject: [PATCH 17/28] Fix: constant ink-on-white colors for LibraryCard The variable-pinning wrapper did not hold and names rendered near-white on paper in dark mode. The card's only app consumer is the ecosystem section on about and impact, both constant-color panels, so the card takes constant colors directly and the wrapper goes away. Signed-off-by: lauren --- src/components/home/ecosystem-libraries.tsx | 8 +------- src/components/ui/library-card.tsx | 10 +++++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/components/home/ecosystem-libraries.tsx b/src/components/home/ecosystem-libraries.tsx index 925494f..98d90d4 100644 --- a/src/components/home/ecosystem-libraries.tsx +++ b/src/components/home/ecosystem-libraries.tsx @@ -102,13 +102,7 @@ owner/repo: {title} {displayDescription} - {/* - LibraryCard is shared (read-only here) and styles itself with semantic theme - tokens. Panels keep constant colors in both themes, so the wrapper pins the - tokens it consumes to light-theme values: cards stay legible on paper when - the site theme is dark. - */} -
+
{categorizedLibraries.map((cat) => { const libs = ecosystemLibraries.filter((l) => l.category === cat.category); if (libs.length === 0) return null; diff --git a/src/components/ui/library-card.tsx b/src/components/ui/library-card.tsx index 5f06062..e0b21ef 100644 --- a/src/components/ui/library-card.tsx +++ b/src/components/ui/library-card.tsx @@ -25,20 +25,20 @@ export function LibraryCard({ href={`https://github.com/${owner}/${name}`} target="_blank" rel="noopener noreferrer" - className="group block rounded-xl border border-border/10 bg-background/[0.03] p-4 transition hover:border-cyan-400/30 hover:bg-background/[0.06]" + className="group block rounded-xl border border-[#EBECF0] bg-white p-4 transition hover:border-[#58C4DC] hover:bg-[#E6F7FF]" >
-
- +
+
- + {displayName}
{showRIS && risScore !== undefined && (
- RIS + RIS {formatRIS(risScore)} From 20273731f121769cfbe2d59bb6c4f90ebc274100 Mon Sep 17 00:00:00 2001 From: lauren Date: Fri, 12 Jun 2026 10:47:44 -0700 Subject: [PATCH 18/28] Feat: panels variant for the table of contents The about page TOC was the last old-design element on the redesigned pages: glass card, gradient icon tile, glow halo on the active dot. A variant prop adds a flat theme-aware skin matching the floating header chrome; the default stays byte-identical for scoring. Behavior (scroll spy, filter, collapse, mobile drawer) is untouched. Also drops the file's unused useRef import. Signed-off-by: lauren --- src/app/about/page.tsx | 2 +- src/components/rfds/TableOfContents.tsx | 130 +++++++++++++++++++----- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index e3ee321..5a07f00 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -232,7 +232,7 @@ export default function AboutPage() { - +
diff --git a/src/components/rfds/TableOfContents.tsx b/src/components/rfds/TableOfContents.tsx index ca14738..f225cb9 100644 --- a/src/components/rfds/TableOfContents.tsx +++ b/src/components/rfds/TableOfContents.tsx @@ -13,7 +13,7 @@ 'use client'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { motion, AnimatePresence } from 'framer-motion'; import { ListTree, ChevronRight, Search, PanelRightOpen, PanelRightClose, ArrowUp } from 'lucide-react'; @@ -27,6 +27,12 @@ interface TableOfContentsProps { sections: TOCSection[]; title?: string; className?: string; + /** + * "panels" matches the redesigned foundation pages: flat theme-aware chrome, + * no glass, gradients, or glow. "default" keeps the original look for the + * routes that still use it (scoring, theme-test). + */ + variant?: 'default' | 'panels'; } /** Utility: class name merger */ @@ -78,6 +84,7 @@ function TOCInner({ onItemClick, setQuery, query, + panels = false, }: { title: string; headings: TOCSection[]; @@ -85,38 +92,66 @@ function TOCInner({ onItemClick: (id: string) => void; setQuery?: (q: string) => void; query?: string; + panels?: boolean; }) { return (
{/* Header */}
-
- -
-
+ {!panels && ( +
+ +
+
+ )}
-

{title}

-

Jump to any section

+

+ {title} +

+ {!panels &&

Jump to any section

}
{/* Search */} {setQuery && ( )} {/* List */}