diff --git a/app/about/components/AboutContact.tsx b/app/about/components/AboutContact.tsx deleted file mode 100644 index 94dfc1438..000000000 --- a/app/about/components/AboutContact.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { Mail } from 'lucide-react'; -import Image from 'next/image'; - -export const AboutContact = () => { - return ( -
-
-
-
-

Contact Us

-
- - - hello@researchhub.com - -
-
-
- Contact ResearchHub -
-
-
-
- ); -}; diff --git a/app/about/components/AboutFAQ.tsx b/app/about/components/AboutFAQ.tsx deleted file mode 100644 index 3d6261c14..000000000 --- a/app/about/components/AboutFAQ.tsx +++ /dev/null @@ -1,240 +0,0 @@ -import { useState } from 'react'; -import { ChevronDown, ChevronRight, HelpCircle } from 'lucide-react'; -import Image from 'next/image'; - -// Custom FAQ Item Component -const FAQItem = ({ - question, - answer, - isOpen, - onToggle, -}: { - question: string; - answer: React.ReactNode; - isOpen: boolean; - onToggle: () => void; -}) => { - return ( -
- - {isOpen &&
{answer}
} -
- ); -}; - -const faqs = [ - { - question: 'Is content created on ResearchHub open?', - answer: ( -
-

- Yes. The scientific record is too important to be hidden behind paywalls. Science should - be open, not only for reading, but also for reusing. -

-

- That's why user contributions to ResearchHub are shared under the{' '} - - Creative Commons Attribution License - {' '} - (CC BY 4.0). This license allows anyone to reuse the content for any purpose, as long as - attribution is provided. We consider a hyperlink or URL back to the source page on - ResearchHub sufficient attribution. -

-

- The CC BY license is the gold standard of open licenses for scholarly content. It is used - by publishers such as{' '} - - PLOS - - ,{' '} - - eLife - - ,{' '} - - Distill - - , and{' '} - - BMC - - . By choosing CC BY, ResearchHub ensures user content can be reused to further science, by - way of text mining, sharing, translation, and many other forms of reuse. This sets - ResearchHub apart from other places to discuss science, like ResearchGate or Reddit, which - do not apply an open license to all user-contributed content. -

-
- ), - }, - { - question: 'What papers can I legally upload to ResearchHub?', - answer: ( -
-

- Users can create a ResearchHub page for any paper, allowing for summary and discussion. - However, due to copyright, only certain papers are eligible for fulltext PDF upload. - Specifically, please only upload fulltexts of papers released under one of the following - open licenses:{' '} - - CC BY - {' '} - or{' '} - - CC0 - - . -

-

- To determine whether an article is released under one of these licenses, check for any - text in the PDF stating a license or refer to the webpage where you downloaded the PDF. If - you are the author of a paper and would like to upload the fulltext to ResearchHub, apply - a supported license to the paper at an existing publicly available location. In other - words, PDFs uploaded to ResearchHub should be available elsewhere on the web with a - supported license. We do not currently publish original articles that are not available - elsewhere. -

-

- Disappointed that you cannot upload a paper's PDF due to copyright? We are too. While open - access publishing is growing in popularity, many papers are still published in toll access - journals without open licenses. We can change this by encouraging scientists to publish in - open access journals and use platforms like ResearchHub that remove legal barriers from - science. -

-
- ), - }, - { - question: 'Who created this site?', - answer: ( -
-

- ResearchHub is being developed by a small team of passionate founders working in San - Francisco, CA. -

-

- The idea for the site was initially proposed in this{' '} - - blog post - - . -

-
- ), - }, - { - question: 'How can I help?', - answer: ( -
-

- The easiest way to help the community grow is to sign in and start contributing content. -

-

- 1. Sign in -
- Create your account in just a few clicks, using Google Sign In. -

-

- 2. Upload a paper -
- Is there a research paper you thought was particularly insightful? -

-

- 3. Write/edit a summary -
- Is there a paper that you can help explain in plain English? -

-

- 4. Upvote (or downvote) a paper -
- Is there a paper already on the site that you have an opinion on? -

-

- 5. Start a discussion. -
- Is there a question you have about a paper? -

-

- 6. Follow us on Twitter -
- Hear our latest updates as we make progress. -

-
- ), - }, -]; - -export const AboutFAQ = () => { - const [openSections, setOpenSections] = useState(['faq-0']); - - const toggleSection = (section: string) => { - setOpenSections((prev) => - prev.includes(section) ? prev.filter((s) => s !== section) : [...prev, section] - ); - }; - - return ( -
-
-
- FAQ Banner -
- -
-
- -

Frequently Asked Questions

-
- -
- {faqs.map((faq, index) => ( - toggleSection(`faq-${index}`)} - /> - ))} -
-
-
-
- ); -}; diff --git a/app/about/components/AboutMission.tsx b/app/about/components/AboutMission.tsx deleted file mode 100644 index b428c3417..000000000 --- a/app/about/components/AboutMission.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import Image from 'next/image'; - -export const AboutMission = () => { - return ( -
-
-
-
-

A GitHub For Science

-

- ResearchHub's mission is to accelerate the pace of scientific research. Our goal is to - make a modern mobile and web application where people can collaborate on scientific - research in a more efficient way, similar to what GitHub has done for software - engineering. -

-

- Researchers should be able to publish articles (preprint or postprint) and discuss the - findings in a completely open and accessible forum dedicated solely to the relevant - article. -

-
-
- ResearchHub Platform -
-
- -
-
- ResearchHub Hubs -
-
-

- "Hubs" as an Alternative to Journals -

-

- Within the ResearchHub platform, research papers are stored and grouped in 'Hubs' by - area of research. Individual Hubs will essentially act as live journals within focused - areas, with highly upvoted posts (i.e. the paper and its associated summary and - discussion) moving to the top of each Hub. -

-
-
-
-
- ); -}; diff --git a/app/about/components/AboutResearchCoin.tsx b/app/about/components/AboutResearchCoin.tsx deleted file mode 100644 index 81d85ad2f..000000000 --- a/app/about/components/AboutResearchCoin.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import { Icon } from '@/components/ui/icons'; - -export const AboutResearchCoin = () => { - return ( -
-
-
-

ResearchCoin

-
-

- To help bring this nascent community together and incentivize contribution to the - platform, a newly created ERC20 token, ResearchCoin (RSC), has been created. To - incentivize users, ResearchHub issues tokens that users can earn and transfer to one - another by sharing, curating, and discussing topics within the platform. Users can - also transfer tokens to one another on the platform by creating "bounties" to - incentivize other users to engage with their post. Rewards for contributions are - proportionate to how valuable the community perceives the actions to be - as measured - by upvotes. -

-

- ResearchCoin is also linked to reputation on the platform--with reputation being - measured as a user's lifetime earnings of ResearchCoin minus erosion due to downvotes. - Reputation is linked to certain privileges in the app, as well as a mechanism for - moderation within the community. -

-

- Further details about ResearchCoin can also be found on the{' '} - - ResearchHub docs page - - . -

-
-
-
-
- ); -}; diff --git a/app/about/components/AboutTabs.tsx b/app/about/components/AboutTabs.tsx deleted file mode 100644 index 30efd7eb4..000000000 --- a/app/about/components/AboutTabs.tsx +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { useRouter } from 'next/navigation'; - -interface AboutTabsProps { - activeTab: string; -} - -export const AboutTabs = ({ activeTab }: AboutTabsProps) => { - const router = useRouter(); - - const tabs = [ - { - id: 'about', - title: 'About', - path: '/about', - }, - { - id: 'team', - title: 'Team', - path: '/team', - }, - ]; - - const handleTabClick = (path: string) => { - router.push(path); - }; - - return ( -
-
-
-
- {tabs.map((tab) => ( - - ))} -
-
-
-
- ); -}; diff --git a/app/about/components/AboutValues.tsx b/app/about/components/AboutValues.tsx deleted file mode 100644 index 7ed008998..000000000 --- a/app/about/components/AboutValues.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import { Globe, Star, Rocket } from 'lucide-react'; - -const values = [ - { - title: 'Accessible to everyone', - text: 'The scientific record is too important to be hidden behind paywalls and in ivory towers. ResearchHub is accessible to everybody, everywhere, with no content residing behind paywalls and no costs to participate. Summaries are written in plain English to improve accessibility.', - icon: , - }, - { - title: 'Collaborative', - text: 'Academic research is too siloed today. ResearchHub encourages academics and non-academics alike to interact in a public and collaborative manner. An incentive for such behavior is provided in the form of ResearchCoin.', - icon: , - }, - { - title: 'Efficient', - text: 'It can take 3-5 years today to go through the process of applying for funding, completing the research, submitting a paper to journals, having it reviewed, and finally getting it published. We believe research could be completed at least one order of magnitude more efficiently.', - icon: , - }, -]; - -export const AboutValues = () => { - return ( -
-
-

- We Believe Scientific Research Should Be: -

- -
- {values.map((value, index) => ( -
-
{value.icon}
-

{value.title}

-

{value.text}

-
- ))} -
-
-
- ); -}; diff --git a/app/about/components/PageBanner.tsx b/app/about/components/PageBanner.tsx deleted file mode 100644 index 56b039d4d..000000000 --- a/app/about/components/PageBanner.tsx +++ /dev/null @@ -1,46 +0,0 @@ -'use client'; - -import Image from 'next/image'; -import { useState, useEffect } from 'react'; - -interface PageBannerProps { - title: string; - subtitle: string; - backgroundImage?: string; -} - -export const PageBanner = ({ - title, - subtitle, - backgroundImage = '/about/background.png', -}: PageBannerProps) => { - const [isRevealed, setIsRevealed] = useState(false); - - useEffect(() => { - setTimeout(() => { - setIsRevealed(true); - }, 200); - }, []); - - return ( -
- {`${title} -
-

{title}

-

- {subtitle} -

-
-
- ); -}; diff --git a/app/about/layout.tsx b/app/about/layout.tsx index d63876c45..2cabfa283 100644 --- a/app/about/layout.tsx +++ b/app/about/layout.tsx @@ -1,30 +1,6 @@ import { Metadata } from 'next'; import { buildOpenGraphMetadata, SITE_CONFIG } from '@/lib/metadata'; -import { generateFAQStructuredData } from '@/lib/structured-data'; -import { LeftSidebar as MainLeftSidebar } from '../layouts/LeftSidebar'; - -const faqItems = [ - { - question: 'Is content created on ResearchHub open?', - answer: - 'Yes. User contributions to ResearchHub are shared under the Creative Commons Attribution License (CC BY 4.0), allowing anyone to reuse the content for any purpose with attribution.', - }, - { - question: 'What papers can I legally upload to ResearchHub?', - answer: - 'Users can create a ResearchHub page for any paper. However, only papers released under CC BY or CC0 open licenses are eligible for fulltext PDF upload.', - }, - { - question: 'Who created this site?', - answer: - 'ResearchHub is being developed by a small team of passionate founders working in San Francisco, CA.', - }, - { - question: 'How can I help?', - answer: - 'Sign in, upload a paper, write or edit a summary, upvote or downvote papers, start a discussion, or follow us on Twitter.', - }, -]; +import { generateOrganizationStructuredData } from '@/lib/structured-data'; export const metadata: Metadata = { ...buildOpenGraphMetadata({ @@ -37,27 +13,14 @@ export const metadata: Metadata = { template: `%s | ${SITE_CONFIG.name}`, }, other: { - 'application/ld+json': JSON.stringify(generateFAQStructuredData(faqItems)), + 'application/ld+json': JSON.stringify(generateOrganizationStructuredData()), }, }; export default function AboutLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( -
-
- {/* Main Left Sidebar - 70px fixed width (minimized) */} -
- -
- - {/* Main Content Area */} -
{children}
-
+
+
{children}
); } diff --git a/app/about/page.tsx b/app/about/page.tsx index 834f4f644..99fd79015 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,33 +1,43 @@ 'use client'; -import { AboutValues } from './components/AboutValues'; -import { AboutMission } from './components/AboutMission'; -import { AboutContact } from './components/AboutContact'; -import { AboutFAQ } from './components/AboutFAQ'; -import { AboutResearchCoin } from './components/AboutResearchCoin'; -import { usePathname } from 'next/navigation'; -import { PageBanner } from './components/PageBanner'; -import { AboutTabs } from './components/AboutTabs'; +import { SubtabsNav, type SubtabsSection } from '@/components/about/SubtabsNav'; +import { Hero } from '@/components/about/Hero'; +import { FundedAt } from '@/components/about/FundedAt'; +import { Problem } from '@/components/about/Problem'; +import { Solution } from '@/components/about/Solution'; +import { FundingMarketplace } from '@/components/about/FundingMarketplace'; +import { ResearchCoin } from '@/components/about/ResearchCoin'; +import { Endaoment } from '@/components/about/Endaoment'; +import { TeamSection } from '@/components/about/TeamSection'; +import { CTASection } from '@/components/about/CTASection'; +import { LandingPageFooter } from '@/components/landing/LandingPageFooter'; -const AboutPage = () => { - const pathname = usePathname(); +const sections: ReadonlyArray = [ + { id: 'about', label: 'About' }, + { id: 'team', label: 'Team' }, +]; +const AboutPage = () => { return ( -
- +
+ + +
+ + + + + + + +
- +
+ +
-
- - - - - -
+ +
); }; diff --git a/app/about/privacy/page.tsx b/app/about/privacy/page.tsx index c5690c53b..1be55f2cc 100644 --- a/app/about/privacy/page.tsx +++ b/app/about/privacy/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; +import Link from 'next/link'; +import { ArrowLeft } from 'lucide-react'; import { buildOpenGraphMetadata } from '@/lib/metadata'; -import { PageBanner } from '../components/PageBanner'; export const metadata: Metadata = buildOpenGraphMetadata({ title: 'Privacy Policy', @@ -11,7 +12,21 @@ export const metadata: Metadata = buildOpenGraphMetadata({ const PrivacyPage = () => { return (
- +
+
+ + + Back to About + +

+ Privacy Policy +

+

ResearchHub Privacy Policy

+
+
@@ -436,7 +451,7 @@ const PrivacyPage = () => { If you would like to get in contact with us, please reach out to{' '} hello@ResearchHub.com diff --git a/app/about/tos/page.tsx b/app/about/tos/page.tsx index e5f296bab..b39bc0a25 100644 --- a/app/about/tos/page.tsx +++ b/app/about/tos/page.tsx @@ -1,6 +1,7 @@ import { Metadata } from 'next'; +import Link from 'next/link'; +import { ArrowLeft } from 'lucide-react'; import { buildOpenGraphMetadata } from '@/lib/metadata'; -import { PageBanner } from '../components/PageBanner'; export const metadata: Metadata = buildOpenGraphMetadata({ title: 'Terms of Service', @@ -11,7 +12,21 @@ export const metadata: Metadata = buildOpenGraphMetadata({ const TOSPage = () => { return (
- +
+
+ + + Back to About + +

+ Terms of Service +

+

ResearchHub User Agreement

+
+
@@ -450,7 +465,7 @@ const TOSPage = () => { vulnerabilities. To report a security issue, please send an email to{' '} hello@researchhub.com @@ -584,7 +599,7 @@ const TOSPage = () => {

hello@researchhub.com @@ -677,7 +692,7 @@ const TOSPage = () => { and concerns here or by emailing us at{' '} hello@researchhub.com @@ -715,7 +730,7 @@ const TOSPage = () => { if you provide us with written notice of your desire to do so by email at{' '} hello@researchhub.com {' '} @@ -727,13 +742,13 @@ const TOSPage = () => { the American Arbitration Association ("AAA") under its Consumer Arbitration Rules (the "AAA Rules") then in effect, except as modified by these Terms. The AAA Rules are available at{' '} - + www.adr.org {' '} or by calling 1-800-778-7879. A party who wishes to start arbitration must submit a written Demand for Arbitration to AAA and give notice to the other party as specified in the AAA Rules. The AAA provides a form Demand for Arbitration at{' '} - + www.adr.org

@@ -775,7 +790,7 @@ const TOSPage = () => { (including by email to{' '} hello@researchhub.com @@ -897,7 +912,7 @@ const TOSPage = () => {

hello@researchhub.com diff --git a/app/team/components/TeamMembers.tsx b/app/team/components/TeamMembers.tsx deleted file mode 100644 index 6634c292f..000000000 --- a/app/team/components/TeamMembers.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import Image from 'next/image'; -import Link from 'next/link'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faLinkedin } from '@fortawesome/free-brands-svg-icons'; - -const teamMembers = [ - { - name: 'Brian Armstrong', - title: 'Chief Executive Officer', - image: '/team/brian.jpeg', - linkedin: 'https://www.linkedin.com/in/barmstrong/', - }, - { - name: 'Patrick Joyce', - title: 'Chief Operating Officer', - image: '/team/joyce.jpeg', - linkedin: 'https://www.linkedin.com/in/patrick-joyce-396b953b/', - }, - { - name: 'Kobe Attias', - title: 'Founding Engineer', - image: '/team/kobe.png', - linkedin: 'https://www.linkedin.com/in/kobe-attias-5a9a9421/', - }, -]; - -export const TeamMembers = () => { - return ( -

-

- We are a small team of builders and thinkers working towards making science better for - everyone. -

- -
- {teamMembers.map((member) => ( -
-
- {member.name} -
- - - {member.name} - -
{member.title}
-
- ))} -
-
- ); -}; diff --git a/app/team/page.tsx b/app/team/page.tsx index 6a42fabe2..afafedc17 100644 --- a/app/team/page.tsx +++ b/app/team/page.tsx @@ -1,26 +1,5 @@ -'use client'; +import { redirect } from 'next/navigation'; -import { MainPageHeader } from '@/components/ui/MainPageHeader'; -import { Icon } from '@/components/ui/icons'; -import { usePathname } from 'next/navigation'; -import { PageBanner } from '../about/components/PageBanner'; -import { AboutTabs } from '../about/components/AboutTabs'; -import { TeamMembers } from './components/TeamMembers'; - -const TeamPage = () => { - const pathname = usePathname(); - - return ( -
- - - - -
- -
-
- ); -}; - -export default TeamPage; +export default function TeamPage() { + redirect('/about#team'); +} diff --git a/components/about/AboutContainer.tsx b/components/about/AboutContainer.tsx new file mode 100644 index 000000000..6f93b9465 --- /dev/null +++ b/components/about/AboutContainer.tsx @@ -0,0 +1,24 @@ +import { ReactNode } from 'react'; +import { cn } from '@/utils/styles'; + +type ContainerWidth = 'narrow' | 'default' | 'wide'; + +interface AboutContainerProps { + children: ReactNode; + className?: string; + width?: ContainerWidth; +} + +const widthMap: Record = { + narrow: 'max-w-[780px]', + default: 'max-w-[1120px]', + wide: 'max-w-[1280px]', +}; + +export const AboutContainer = ({ children, className, width = 'default' }: AboutContainerProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/components/about/CTASection.tsx b/components/about/CTASection.tsx new file mode 100644 index 000000000..d613c920f --- /dev/null +++ b/components/about/CTASection.tsx @@ -0,0 +1,66 @@ +import Link from 'next/link'; +import { ArrowRight } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { cn } from '@/utils/styles'; + +interface CtaLink { + href: string; + label: string; + primary?: boolean; +} + +const links: CtaLink[] = [ + { href: 'mailto:hello@researchhub.com', label: 'hello@researchhub.com', primary: true }, + { href: '/auth/signup', label: 'Join ResearchHub' }, + { href: '/grants', label: 'Fund science' }, +]; + +export const CTASection = () => { + return ( +
+ +
+
+
+ + Get involved + +

+ Help us build the
+ hub for open science. +

+

+ Upload a paper, fund a proposal, or reach out. We're a small team working to + redesign science from the ground up. +

+
+
+ {links.map((link) => ( + + {link.label} + + + ))} +
+
+
+
+
+ ); +}; diff --git a/components/about/Endaoment.tsx b/components/about/Endaoment.tsx new file mode 100644 index 000000000..7ead82ece --- /dev/null +++ b/components/about/Endaoment.tsx @@ -0,0 +1,83 @@ +import Image from 'next/image'; +import { ExternalLink } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { MonoLabel } from './MonoLabel'; + +const stats = [ + { value: '$145.62M', label: 'Total impact' }, + { value: '1.8M', label: 'Eligible orgs' }, +]; + +export const Endaoment = () => { + return ( +
+ +
+
+
+ § 05 · Partnership + + 501(c)(3) Nonprofit + +
+

+ Partnered with Nonprofits. +

+

+ Endaoment is a US 501(c)(3) nonprofit providing crypto-native donor-advised funds for + science. Funders can give via DAF with the same speed and transparency as native RSC — + and receive tax-deductible receipts. +

+ + +
+ +
+
+ + Powered by + + Endaoment + +
+ {stats.map((stat) => ( +
+
+ {stat.value} +
+ + {stat.label} + +
+ ))} +
+
+
+
+
+
+ ); +}; diff --git a/components/about/Eyebrow.tsx b/components/about/Eyebrow.tsx new file mode 100644 index 000000000..2253910f3 --- /dev/null +++ b/components/about/Eyebrow.tsx @@ -0,0 +1,28 @@ +import { ReactNode } from 'react'; +import { cn } from '@/utils/styles'; + +interface EyebrowProps { + children: ReactNode; + className?: string; + tone?: 'default' | 'onDark'; +} + +/** + * Single source of Geist Mono styling on the About page. + * Other components should consume rather than reaching for the + * --font-geist-mono CSS variable directly. + */ +export const Eyebrow = ({ children, className, tone = 'default' }: EyebrowProps) => { + return ( +
+ {children} +
+ ); +}; diff --git a/components/about/FundedAt.tsx b/components/about/FundedAt.tsx new file mode 100644 index 000000000..a5881865d --- /dev/null +++ b/components/about/FundedAt.tsx @@ -0,0 +1,56 @@ +import Image from 'next/image'; +import { AboutContainer } from './AboutContainer'; +import { MonoLabel } from './MonoLabel'; +import { fundedInstitutions } from './data/fundedInstitutions'; + +export const FundedAt = () => { + return ( +
+ +
+ + + Funding researchers at + + +
+ +
+
+ {[0, 1].map((setIndex) => ( +
+ {fundedInstitutions.map((institution) => ( +
+ {institution.name} +
+ ))} +
+ ))} +
+
+
+
+ ); +}; diff --git a/components/about/FundingMarketplace.tsx b/components/about/FundingMarketplace.tsx new file mode 100644 index 000000000..49a2a30a6 --- /dev/null +++ b/components/about/FundingMarketplace.tsx @@ -0,0 +1,35 @@ +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { FundingPipeline } from './FundingPipeline'; + +export const FundingMarketplace = () => { + return ( +
+ +
+
+ § 03 · Our features +

+ Funding Marketplace +

+
+
+

+ The funding marketplace links funding agencies with a global pool of researchers. It + operates through a simple pipeline designed to promote transparency and + reproducibility. +

+
+
+ + +
+
+ ); +}; diff --git a/components/about/FundingPipeline.tsx b/components/about/FundingPipeline.tsx new file mode 100644 index 000000000..8e5cac634 --- /dev/null +++ b/components/about/FundingPipeline.tsx @@ -0,0 +1,173 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; +import { Check } from 'lucide-react'; +import { cn } from '@/utils/styles'; +import { MonoLabel } from './MonoLabel'; +import { pipelineSteps, type PipelineStep } from './data/pipelineSteps'; + +const StepProductImage = ({ step }: { step: PipelineStep }) => ( +
+ {step.imageAlt} +
+); + +export const FundingPipeline = () => { + const [active, setActive] = useState(0); + const current = pipelineSteps[active]; + const total = pipelineSteps.length; + const progress = total > 1 ? active / (total - 1) : 0; + + return ( +
+
+ + Funding pipeline + + + {String(total).padStart(2, '0')} steps + +
+ + {/* Desktop: horizontal stepper */} +
+
+
+
+
+ {pipelineSteps.map((step, index) => { + const isActive = index === active; + const isDone = index < active; + return ( + + ); + })} +
+
+ +
+
+
+ + Step {current.number} + +

+ {current.title} +

+

+ {current.description} +

+
+ +
+
+
+ + {/* Mobile: compact step selector with one active detail card */} +
+
+
+
+
+ {pipelineSteps.map((step, index) => { + const isActive = index === active; + const isDone = index < active; + return ( + + ); + })} +
+
+ +
+ + Step {current.number} + +

+ {current.title} +

+
+ +
+
+
+
+ ); +}; diff --git a/components/about/Hero.tsx b/components/about/Hero.tsx new file mode 100644 index 000000000..2a23ff838 --- /dev/null +++ b/components/about/Hero.tsx @@ -0,0 +1,103 @@ +import { ArrowRight } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { MonoLabel } from './MonoLabel'; + +const stats = [ + { value: '$1.5M+', label: 'Research funding distributed' }, + { value: '100k+', label: 'Researchers & readers' }, + { value: '<10d', label: 'Avg. peer review turnaround' }, + { value: '0–10%', label: 'University indirects' }, +]; + +const jumpLinks = [ + { label: 'The problem', href: '#problem' }, + { label: 'Funding Marketplace', href: '#funding' }, + { label: 'ResearchCoin', href: '#rsc' }, + { label: 'Team', href: '#team' }, +]; + +export const Hero = () => { + return ( +
+
+ + +
+ + + + About ResearchHub + + +
+ +

+ You've discovered
+ the future of science. +

+ +
+

+ ResearchHub is an online platform designed to enhance the speed and transparency of + science — built on a simple premise: that science provides the most benefit when + it's done quickly, openly, and with a high level of rigor and reproducibility. +

+
+ + Jump to + + +
+
+ +
+ {stats.map((stat) => ( +
+
+ {stat.value} +
+ + {stat.label} + +
+ ))} +
+
+
+ ); +}; diff --git a/components/about/MonoLabel.tsx b/components/about/MonoLabel.tsx new file mode 100644 index 000000000..1c40832be --- /dev/null +++ b/components/about/MonoLabel.tsx @@ -0,0 +1,20 @@ +import { ReactNode } from 'react'; +import { cn } from '@/utils/styles'; + +interface MonoLabelProps { + children: ReactNode; + className?: string; +} + +/** + * Geist Mono helper for inline labels (stat captions, step counters, tags) that + * are not section eyebrows. Consolidates inline font-family usage so callers + * never reference --font-geist-mono directly. + */ +export const MonoLabel = ({ children, className }: MonoLabelProps) => { + return ( + + {children} + + ); +}; diff --git a/components/about/Problem.tsx b/components/about/Problem.tsx new file mode 100644 index 000000000..5e8a7b663 --- /dev/null +++ b/components/about/Problem.tsx @@ -0,0 +1,102 @@ +import { X } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { MonoLabel } from './MonoLabel'; + +interface Issue { + key: string; + title: string; + description: string; +} + +const issues: Issue[] = [ + { + key: '01', + title: "Peer reviewers aren't paid", + description: + 'Slow reviews, inconsistent quality — scientists donate their time while publishers profit.', + }, + { + key: '02', + title: 'Publishing takes years', + description: + 'Convincing reviewers to work for free gets harder every year. Findings sit in limbo.', + }, + { + key: '03', + title: 'Paywalls gatekeep research', + description: + "Taxpayers fund studies they can't read. Science is locked behind journal subscriptions.", + }, + { + key: '04', + title: 'Grants are broken', + description: + 'Review processes drag on for months or years — and much of the money goes to indirect costs, not experiments.', + }, +]; + +export const Problem = () => { + return ( +
+ +
+
+ § 01 · The problem +

+ Science works, but barely. +

+

+ It's no secret — just ask any scientist. The system is antiquated, and science is + suffering as a result. There must be a better way. +

+
+
+
+ + Traditionally + +
+ {issues.map((issue) => ( +
+
+ + {issue.title} +
+
+ ))} +
+
+ +
+ {issues.map((issue) => ( +
+ + {issue.key} + +
+

+ {issue.title} +

+

+ {issue.description} +

+
+
+ ))} +
+
+
+
+
+ ); +}; diff --git a/components/about/ResearchCoin.tsx b/components/about/ResearchCoin.tsx new file mode 100644 index 000000000..71502e176 --- /dev/null +++ b/components/about/ResearchCoin.tsx @@ -0,0 +1,150 @@ +import { ExternalLink } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { MonoLabel } from './MonoLabel'; +import { Icon } from '@/components/ui/icons/Icon'; +import { colors } from '@/app/styles/colors'; + +interface RscUseCase { + number: string; + tag: string; + title: string; + body: string; + cta: string; + href: string; +} + +const useCases: RscUseCase[] = [ + { + number: '01', + tag: 'Endowments', + title: 'Earn high-yield Funding Credits', + body: 'Endowments let committed funds keep earning, so more of every dollar goes toward science over time.', + cta: 'How Endowments work', + href: '#', + }, + { + number: '02', + tag: 'Bounties', + title: 'Reward the work that moves research forward', + body: 'Researchers use RSC bounties to reward peer review, replication, data, and useful discussion.', + cta: 'Browse open bounties', + href: '/bounties', + }, + { + number: '03', + tag: 'Self-funding', + title: 'Fund your own research with what you earn', + body: 'Earn RSC from community contributions, then put it back into your own proposals and bounties.', + cta: 'Start a proposal', + href: '/grants', + }, +]; + +export const ResearchCoin = () => { + return ( +
+ +
+
+ § 04 · RSC +

+ The incentive layer for open science. +

+
+
+

+ ResearchCoin (RSC) is an ERC20 + token that rewards the work that actually advances science — funding it, reviewing it, + replicating it, discussing it. +

+
+
+ +
+
+ + ERC20 · Ethereum + +
+ ResearchCoin (RSC) +
+
+
+
+
+ +
+ {useCases.map((useCase) => ( +
+
+ {useCase.number} + + {useCase.tag} + +
+

+ {useCase.title} +

+

+ {useCase.body} +

+ + {useCase.cta} + +
+ ))} +
+ +
+
+
+ +
+
+
+ Pay with RSC, DAF, credit card, or Apple Pay. +
+
+ Funders choose their rails — we handle the conversion and custody. +
+
+
+ + Read the RSC docs + + +
+
+
+ ); +}; diff --git a/components/about/SectionHeader.tsx b/components/about/SectionHeader.tsx new file mode 100644 index 000000000..3f7dc5d40 --- /dev/null +++ b/components/about/SectionHeader.tsx @@ -0,0 +1,42 @@ +import { ReactNode } from 'react'; +import { cn } from '@/utils/styles'; +import { Eyebrow } from './Eyebrow'; + +interface SectionHeaderProps { + eyebrow?: ReactNode; + title: ReactNode; + lead?: ReactNode; + align?: 'left' | 'center'; + className?: string; +} + +export const SectionHeader = ({ + eyebrow, + title, + lead, + align = 'left', + className, +}: SectionHeaderProps) => { + const isCenter = align === 'center'; + return ( +
+ {eyebrow ? {eyebrow} : null} +

+ {title} +

+ {lead ? ( +

+ {lead} +

+ ) : null} +
+ ); +}; diff --git a/components/about/Solution.tsx b/components/about/Solution.tsx new file mode 100644 index 000000000..050f84dc7 --- /dev/null +++ b/components/about/Solution.tsx @@ -0,0 +1,101 @@ +import { Check, X } from 'lucide-react'; +import { AboutContainer } from './AboutContainer'; +import { SectionHeader } from './SectionHeader'; +import { MonoLabel } from './MonoLabel'; + +interface ComparisonRow { + old: string; + next: string; +} + +const rows: ComparisonRow[] = [ + { + old: 'Peer reviewers work for free', + next: 'Peer reviewers are paid for fast, rigorous reviews', + }, + { + old: 'Papers locked behind paywalls', + next: 'Research articles published openly — no paywalls, no delays', + }, + { + old: 'Grants take months or years to review', + next: 'Funding opportunities are easy to find and simple to apply to', + }, + { + old: 'Most grant money goes to indirect costs', + next: 'Capped indirect costs — more money to the actual experiments', + }, +]; + +export const Solution = () => { + return ( +
+ + + Imagine if we could redesign
+ the entire system{' '} + from the ground up. + + } + lead="These are some of the features you'll find on ResearchHub — the hub for open science, where researchers, funders, and the public meet to share and discuss research." + /> + +
+
+ + On ResearchHub + +
+ {rows.map((row) => ( +
+
+ + {row.next} +
+
+ ))} +
+
+ +
+
+ + → Today + + + → On ResearchHub + +
+ {rows.map((row, index) => ( +
+
+
+ + {row.old} +
+
+
+
+ + {row.next} +
+
+
+ ))} +
+
+
+
+ ); +}; diff --git a/components/about/SubtabsNav.tsx b/components/about/SubtabsNav.tsx new file mode 100644 index 000000000..865b016ab --- /dev/null +++ b/components/about/SubtabsNav.tsx @@ -0,0 +1,103 @@ +'use client'; + +import { useEffect, useState } from 'react'; +import Link from 'next/link'; +import { Logo } from '@/components/ui/Logo'; +import { cn } from '@/utils/styles'; + +export interface SubtabsSection { + id: string; + label: string; +} + +interface SubtabsNavProps { + sections: ReadonlyArray; + /** + * Approximate height of the sticky bar; used as the scroll-spy probe offset + * and the smooth-scroll target offset on click. Defaults to the desktop bar + * height; a few pixels of tolerance on mobile is intentional. + */ + offset?: number; +} + +const prefersReducedMotion = () => { + if (typeof window === 'undefined') return false; + return window.matchMedia('(prefers-reduced-motion: reduce)').matches; +}; + +export const SubtabsNav = ({ sections, offset = 56 }: SubtabsNavProps) => { + const [active, setActive] = useState(sections[0]?.id ?? ''); + + useEffect(() => { + if (typeof window === 'undefined' || sections.length === 0) return; + + const ids = sections.map((s) => s.id); + const handleScroll = () => { + const probeY = window.scrollY + offset + 40; + let current = ids[0]; + for (const id of ids) { + const el = document.getElementById(id); + if (el && el.offsetTop <= probeY) current = id; + } + setActive(current); + }; + + handleScroll(); + window.addEventListener('scroll', handleScroll, { passive: true }); + return () => window.removeEventListener('scroll', handleScroll); + }, [sections, offset]); + + const handleClick = (event: React.MouseEvent, id: string) => { + event.preventDefault(); + const el = document.getElementById(id); + if (!el) return; + const top = el.getBoundingClientRect().top + window.scrollY - offset; + window.scrollTo({ + top, + behavior: prefersReducedMotion() ? 'auto' : 'smooth', + }); + if (typeof window !== 'undefined' && window.history?.replaceState) { + window.history.replaceState(null, '', `#${id}`); + } + }; + + return ( + + ); +}; diff --git a/components/about/TeamSection.tsx b/components/about/TeamSection.tsx new file mode 100644 index 000000000..657452971 --- /dev/null +++ b/components/about/TeamSection.tsx @@ -0,0 +1,67 @@ +import Image from 'next/image'; +import { AboutContainer } from './AboutContainer'; +import { Eyebrow } from './Eyebrow'; +import { team, SocialKey } from './data/team'; +import { SOCIAL_LABELS, SOCIAL_ORDER, TeamSocialIcon } from './TeamSocialIcon'; + +const isSocialKey = (key: string): key is SocialKey => + (SOCIAL_ORDER as ReadonlyArray).includes(key); + +export const TeamSection = () => { + return ( +
+ + § 06 · Team +

+ A small team of passionate people. +

+
+ {team.map((member) => ( +
+
+ {member.name} +
+
+
+ {member.name} +
+
{member.role}
+
+ {SOCIAL_ORDER.filter((key) => member.links[key]).map((key) => { + if (!isSocialKey(key)) return null; + const href = member.links[key]!; + return ( + + + + ); + })} +
+
+
+ ))} +
+
+
+ ); +}; diff --git a/components/about/TeamSocialIcon.tsx b/components/about/TeamSocialIcon.tsx new file mode 100644 index 000000000..8e36ae885 --- /dev/null +++ b/components/about/TeamSocialIcon.tsx @@ -0,0 +1,50 @@ +import Image from 'next/image'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { faGithub, faLinkedin, faSquareXTwitter } from '@fortawesome/free-brands-svg-icons'; +import { cn } from '@/utils/styles'; +import { SocialKey } from './data/team'; + +interface TeamSocialIconProps { + kind: SocialKey; + className?: string; +} + +/** + * Each social icon renders in its own well-known brand color (X = black, + * LinkedIn = LinkedIn blue, GitHub = GitHub near-black). Brand marks are a + * deliberate exception to the project's palette-purity rule because their + * recognizability depends on the canonical brand color. + */ +const BRAND_ICON_CLASSES: Record = { + x: 'text-black', + linkedin: 'text-[#0A66C2]', + github: 'text-[#181717]', + rh: '', +}; + +export const TeamSocialIcon = ({ kind, className }: TeamSocialIconProps) => { + const brand = BRAND_ICON_CLASSES[kind]; + switch (kind) { + case 'x': + return ( + + ); + case 'linkedin': + return ; + case 'github': + return ; + case 'rh': + return ; + default: + return null; + } +}; + +export const SOCIAL_LABELS: Record = { + x: 'X / Twitter', + linkedin: 'LinkedIn', + github: 'GitHub', + rh: 'ResearchHub', +}; + +export const SOCIAL_ORDER: SocialKey[] = ['rh', 'linkedin', 'x', 'github']; diff --git a/components/about/data/fundedInstitutions.ts b/components/about/data/fundedInstitutions.ts new file mode 100644 index 000000000..206bb1ec1 --- /dev/null +++ b/components/about/data/fundedInstitutions.ts @@ -0,0 +1,17 @@ +export interface Institution { + name: string; + src: string; +} + +export const fundedInstitutions: Institution[] = [ + { name: 'Stanford University', src: '/universities/stanford-university.png' }, + { name: 'Harvard University', src: '/universities/harvard-university.png' }, + { name: 'Cornell University', src: '/universities/cornell-university.svg' }, + { name: 'Purdue University', src: '/universities/purdue-university.svg' }, + { name: 'UC Irvine', src: '/universities/uc-irvine.svg' }, + { name: 'UC San Diego', src: '/universities/uc-san-diego.svg' }, + { name: 'UC Santa Barbara', src: '/universities/uc-santa-barbara.svg' }, + { name: 'University of Maryland SOM', src: '/universities/university-of-maryland-som.svg' }, + { name: 'Indiana University', src: '/universities/indiana-university.svg' }, + { name: 'Iowa State University', src: '/universities/iowa-state-university.svg' }, +]; diff --git a/components/about/data/pipelineSteps.ts b/components/about/data/pipelineSteps.ts new file mode 100644 index 000000000..8864a6644 --- /dev/null +++ b/components/about/data/pipelineSteps.ts @@ -0,0 +1,42 @@ +export interface PipelineStep { + number: string; + title: string; + description: string; + imageAlt: string; + imageSrc: string; +} + +export const pipelineSteps: PipelineStep[] = [ + { + number: '01', + title: 'Public preregistration', + description: + 'Researchers publicly register their study design before starting — locking in methods to promote reproducibility.', + imageAlt: 'ResearchHub proposal card before funding', + imageSrc: '/about/preregistration_before.png', + }, + { + number: '02', + title: 'Open peer critique', + description: + 'Qualified domain researchers review preregistered proposals openly, with paid turnaround in under 10 days.', + imageAlt: 'ResearchHub peer review card', + imageSrc: '/about/peer_review.png', + }, + { + number: '03', + title: 'Capital commitment', + description: + 'Funders commit capital to proposals that pass review. Low overhead (0–10%) means more dollars to experiments.', + imageAlt: 'ResearchHub proposal funding progress complete', + imageSrc: '/about/proposal_funded.png', + }, + { + number: '04', + title: 'Linked results', + description: + 'Results are linked back to the original preregistration — a transparent, auditable record of the full research cycle.', + imageAlt: 'ResearchHub author project update', + imageSrc: '/about/proposal_update.png', + }, +]; diff --git a/components/about/data/team.ts b/components/about/data/team.ts new file mode 100644 index 000000000..6144fa80e --- /dev/null +++ b/components/about/data/team.ts @@ -0,0 +1,61 @@ +export type SocialKey = 'x' | 'github' | 'linkedin' | 'rh'; + +export interface TeamMember { + name: string; + role: string; + src: string; + links: Partial>; +} + +export const team: TeamMember[] = [ + { + name: 'Brian Armstrong', + role: 'Co-founder', + src: '/team/brian.jpeg', + links: { + x: 'https://x.com/brian_armstrong', + linkedin: 'https://www.linkedin.com/in/barmstrong/', + rh: '/', + }, + }, + { + name: 'Patrick Joyce', + role: 'Co-founder, CEO', + src: '/team/joyce.jpeg', + links: { + x: 'https://x.com/joycesticks', + linkedin: 'https://www.linkedin.com/in/patrick-joyce-396b953b/', + rh: '/', + }, + }, + { + name: 'Kobe Attias', + role: 'Founding Engineer', + src: '/team/kobe.png', + links: { + github: 'https://github.com/yattias', + linkedin: 'https://www.linkedin.com/in/kobe-attias-5a9a9421/', + rh: '/', + }, + }, + { + name: 'Taki Koustomitis', + role: 'Founding Engineer', + src: '/team/taki.jpeg', + links: { + github: 'https://github.com/koutst', + linkedin: 'https://www.linkedin.com/in/taki-k/', + rh: '/', + }, + }, + { + name: 'Tyler Diorio, PhD', + role: 'Chief of Staff', + src: '/team/tyler.png', + links: { + x: 'https://x.com/TylerDiorio', + linkedin: 'https://www.linkedin.com/in/tyler-diorio-351325aa/', + rh: '/', + }, + }, +]; diff --git a/components/ui/Button.tsx b/components/ui/Button.tsx index 2a2a5efe1..4b063f356 100644 --- a/components/ui/Button.tsx +++ b/components/ui/Button.tsx @@ -10,12 +10,12 @@ const buttonVariants = cva( { variants: { variant: { - default: 'bg-[#3971FF] text-white hover:bg-[#2C5EE8] focus-visible:ring-[#3971FF]', + default: 'bg-primary-500 text-white hover:bg-primary-600 focus-visible:ring-primary-500', secondary: 'bg-primary-200/80 text-primary-800 border border-primary-200 hover:bg-primary-200 hover:border-primary-300 focus-visible:ring-primary-500 shadow-sm', outlined: 'bg-white text-gray-700 border border-gray-300 hover:bg-gray-50', ghost: 'hover:bg-gray-100 text-gray-700', - link: 'p-0 h-auto text-[#3971FF] underline-offset-4 hover:underline focus-visible:ring-0 !px-0 !py-0', + link: 'p-0 h-auto text-primary-500 underline-offset-4 hover:underline focus-visible:ring-0 !px-0 !py-0', destructive: 'bg-red-600 text-white hover:bg-red-700', contribute: 'bg-white bg-orange-100 text-orange-600 border border-orange-100 hover:bg-orange-200 hover:border-orange-200', diff --git a/public/about/peer_review.png b/public/about/peer_review.png new file mode 100644 index 000000000..626acad46 Binary files /dev/null and b/public/about/peer_review.png differ diff --git a/public/about/preregistration_before.png b/public/about/preregistration_before.png new file mode 100644 index 000000000..9587f551e Binary files /dev/null and b/public/about/preregistration_before.png differ diff --git a/public/about/proposal_funded.png b/public/about/proposal_funded.png new file mode 100644 index 000000000..6b6ce1211 Binary files /dev/null and b/public/about/proposal_funded.png differ diff --git a/public/about/proposal_update.png b/public/about/proposal_update.png new file mode 100644 index 000000000..aa846fc37 Binary files /dev/null and b/public/about/proposal_update.png differ diff --git a/public/logos/endaoment-wordmark-color.png b/public/logos/endaoment-wordmark-color.png new file mode 100644 index 000000000..9c4c2e672 Binary files /dev/null and b/public/logos/endaoment-wordmark-color.png differ diff --git a/public/team/taki.jpeg b/public/team/taki.jpeg new file mode 100644 index 000000000..fb741d0a6 Binary files /dev/null and b/public/team/taki.jpeg differ diff --git a/public/team/tyler.png b/public/team/tyler.png new file mode 100644 index 000000000..7e6332ff2 Binary files /dev/null and b/public/team/tyler.png differ diff --git a/tailwind.config.ts b/tailwind.config.ts index 9b47e1429..b97651b9b 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -49,12 +49,17 @@ export default { '0%': { opacity: '0', transform: 'translateY(10px)' }, '100%': { opacity: '1', transform: 'translateY(0)' }, }, + 'logo-marquee': { + '0%': { transform: 'translateX(0)' }, + '100%': { transform: 'translateX(-50%)' }, + }, }, animation: { 'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite', 'pulse-dot': 'pulse-dot 2s cubic-bezier(0.4, 0, 0.6, 1) infinite', radiate: 'radiate-circle 2.5s cubic-bezier(0, 0, 0.2, 1) infinite', fadeIn: 'fadeIn 0.3s ease-out', + 'logo-marquee': 'logo-marquee 32s linear infinite', }, }, },