diff --git a/packages/api/src/utils/usernamePolicy.ts b/packages/api/src/utils/usernamePolicy.ts
index 20bd5fe32..853be928a 100644
--- a/packages/api/src/utils/usernamePolicy.ts
+++ b/packages/api/src/utils/usernamePolicy.ts
@@ -82,6 +82,7 @@ const RESERVED_NAMES = new Set([
'jobs',
'join',
'json',
+ 'landing',
'language',
'languages',
'lists',
diff --git a/packages/web/components/elements/images/CurvedUnderlineIcon.tsx b/packages/web/components/elements/images/CurvedUnderlineIcon.tsx
new file mode 100644
index 000000000..32e42e4a2
--- /dev/null
+++ b/packages/web/components/elements/images/CurvedUnderlineIcon.tsx
@@ -0,0 +1,25 @@
+type CurvedUnderlineIconProps = {
+ size?: string
+ fillColor?: string
+ }
+
+ export function CurvedUnderlineIcon({
+ fillColor = '#FF9B3E',
+ size,
+ }: CurvedUnderlineIconProps): JSX.Element {
+ return (
+
+ )
+ }
+
\ No newline at end of file
diff --git a/packages/web/components/templates/landing/LandingFooter.tsx b/packages/web/components/templates/landing/LandingFooter.tsx
new file mode 100644
index 000000000..9d0e87422
--- /dev/null
+++ b/packages/web/components/templates/landing/LandingFooter.tsx
@@ -0,0 +1,136 @@
+import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo';
+import Link from 'next/link'
+import Image from 'next/image'
+import { Box, HStack } from '../../elements/LayoutPrimitives';
+import { GithubLogo, DiscordLogo, TwitterLogo } from 'phosphor-react'
+
+const containerStyles = {
+ padding: '5vw',
+ background: '#252525',
+ py: 60,
+ pb: 105,
+ width: '100%',
+ '@md': {
+ paddingLeft: '6vw',
+ },
+ '@xl': {
+ paddingLeft: '140px',
+ }
+}
+
+const titleStyles = {
+ maxWidth: 330,
+ fontWeight: 'normal',
+ fontSize: 18,
+ lineHeight: '27px',
+ color: '#FFFFFF',
+ mb: 45,
+ '@mdDown': {
+ fontSize: '3vw'
+ }
+}
+
+const socialsContainerStyles = {
+ maxWidth: 140,
+ marginBottom: 79
+}
+
+const copyrightStyles = {
+ maxWidth: 330,
+ fontWeight: 'normal',
+ fontSize: 18,
+ lineHeight: '27px',
+ color: '#5F5E58'
+}
+
+const sectionOne = {
+ width: '60%'
+}
+const sectionTwo = {
+ width: '40%',
+ pt: 10
+}
+
+const contactStyles = {
+ fontWeight: '700',
+ fontSize: 36,
+ lineHeight: '39px',
+ color: 'white',
+ '@mdDown': {
+ fontSize: 26,
+ }
+}
+const supportStyles = {
+ fontSize: 24,
+ lineHeight: '36px',
+ color: 'white',
+ '@mdDown': {
+ fontSize: '3vw'
+ }
+}
+
+const imageStyles = {
+ maxWidth: 190,
+ width: '100%',
+}
+
+const socialIconContainerStyles = {
+ maxWidth: 32,
+ maxHeight: 32,
+}
+
+export function LandingFooter(): JSX.Element {
+ return (
+
+
+ Everything you read. Safe, organized, and easy to share.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ © 2022 Omnivore
+
+
+
+
+
+
+
+
+ {/*
+
+
+
+
+
+ */}
+
+
+ Contact
+
+
+ support@omnivore.app
+
+
+
+ )
+}
diff --git a/packages/web/components/templates/landing/LandingHeader.tsx b/packages/web/components/templates/landing/LandingHeader.tsx
new file mode 100644
index 000000000..54da7fa1a
--- /dev/null
+++ b/packages/web/components/templates/landing/LandingHeader.tsx
@@ -0,0 +1,50 @@
+import Link from 'next/link'
+import { Box, SpanBox } from '../../elements/LayoutPrimitives'
+import { OmnivoreNameLogo } from '../../elements/images/OmnivoreNameLogo'
+
+const containerStyles = {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ p: '0px 15px 0px 15px',
+ height: '68px',
+ minHeight: '68px',
+ display: 'flex',
+ alignItems: 'center',
+ '@md': { width: '50%' },
+ '@xsDown': { height: '48px' },
+ justifyContent: 'space-between',
+ width: '100%',
+}
+
+const linkStyles = {
+ marginLeft: 'auto',
+ verticalAlign: 'middle',
+ cursor: 'pointer',
+ lineHeight: '100%',
+}
+
+const textStyles = {
+ pt: '5px',
+ pr: '6px',
+ fontSize: 24,
+ lineHeight: '24px',
+ fontWeight: 'normal'
+}
+
+export function LandingHeader(): JSX.Element {
+ return (
+
+
+
+
+
+
+ Log in
+
+
+
+
+
+ )
+}
diff --git a/packages/web/components/templates/landing/LandingSection.tsx b/packages/web/components/templates/landing/LandingSection.tsx
new file mode 100644
index 000000000..02ed69d70
--- /dev/null
+++ b/packages/web/components/templates/landing/LandingSection.tsx
@@ -0,0 +1,90 @@
+import { HStack, VStack, Box } from '../../elements/LayoutPrimitives'
+import { CSS, styled } from '@stitches/react'
+
+type LandingSectionProps = {
+ titleText: string,
+ descriptionText: string,
+ icon: React.ReactElement,
+ image: React.ReactElement,
+ containerStyles?: CSS,
+}
+
+const MainContainer = styled(HStack, {
+ width: '100%',
+})
+
+const titleTextStyles = {
+ fontWeight: '700',
+ color: '#3D3D3D',
+ lineHeight: '53px',
+ '@mdDown': {
+ fontSize: 24,
+ },
+ '@md': {
+ fontSize: '$5',
+ },
+ '@xl': {
+ fontSize: 45,
+ }
+}
+
+const descriptionTextStyles = {
+ color: 'rgb(125, 125, 125)',
+}
+
+const iconContainerStyles = {
+ width: 56,
+ height: 56,
+ background: 'white',
+ border: '1px solid rgba(61, 61, 61, 0.08)',
+ boxSizing: 'border-box',
+ borderRadius: '50%',
+ '@mdDown': {
+ width: 32,
+ height: 32,
+ padding: 5,
+ },
+}
+
+const imageContainerStyles = {
+ width: '50%',
+ '@mdDown': {
+ width: 0,
+ display: 'none',
+ }
+}
+
+const layoutStyles = {
+ width: '50%',
+ padding: 10,
+ '@mdDown': {
+ width: '100%',
+ }
+}
+
+const innerLayoutStyles = {
+ maxWidth: 480,
+ alignSelf: 'center',
+ '@mdDown': {
+ alignItems: 'center',
+ },
+}
+
+export function LandingSection(props: LandingSectionProps): JSX.Element {
+ return (
+
+
+
+
+ {props.icon}
+
+ {props.titleText}
+ {props.descriptionText}
+
+
+
+ {props.image}
+
+
+ )
+}
diff --git a/packages/web/components/templates/landing/LandingSectionsContainer.tsx b/packages/web/components/templates/landing/LandingSectionsContainer.tsx
new file mode 100644
index 000000000..6fd01529b
--- /dev/null
+++ b/packages/web/components/templates/landing/LandingSectionsContainer.tsx
@@ -0,0 +1,210 @@
+import Link from 'next/link'
+import { VStack, Box, SpanBox } from '../../elements/LayoutPrimitives'
+import { CurvedUnderlineIcon } from '../../elements/images/CurvedUnderlineIcon'
+import { Button } from '../../elements/Button'
+import { MagnifyingGlass, Palette, MegaphoneSimple, Binoculars, ArrowRight } from 'phosphor-react'
+import { LandingSection } from './LandingSection'
+
+const buttonStyles = {
+ display: 'flex',
+ borderRadius: 4,
+ px: 30,
+ background: 'rgb(255, 210, 52)',
+ color: '#3D3D3D'
+}
+
+const arrowStyles = {
+ marginLeft: 10,
+ padding: 2,
+}
+
+export function GetStartedButton(): JSX.Element {
+ return (
+
+ )
+}
+
+const containerStyles = {
+ px: '2vw',
+ pt: 100,
+ pb: 100,
+ width: '100%',
+ background: 'linear-gradient(0deg, rgba(255, 255, 255, 0.2), rgba(255, 255, 255, 0.2)), linear-gradient(0deg, rgba(253, 250, 236, 0.7), rgba(253, 250, 236, 0.7))',
+ '@mdDown': {
+ pt: 50,
+ },
+ '@md': {
+ px: '6vw',
+ },
+ '@xl': {
+ px: '100px',
+ }
+}
+
+const titleStyles = {
+ fontWeight: '600',
+ fontSize: '24',
+ textAlign: 'center',
+ lineHeight: '36px',
+ color: '#FF9B3E',
+ '@mdDown': {
+ fontSize: 16,
+ letterSpacing: '0.02em'
+ }
+}
+
+const subTitleText = {
+ fontSize: 64,
+ maxWidth: 590,
+ fontWeight: '700',
+ textAlign: 'center',
+ lineHeight: '70px',
+ mb: 30,
+ '@mdDown': {
+ maxWidth: 295,
+ fontSize: 32,
+ lineHeight: '40px',
+ }
+}
+
+const reversedSectionStyles = {
+ flexDirection: 'row-reverse',
+ marginBottom: 20
+}
+
+const callToActionStyles = {
+ background: 'white',
+ borderRadius: '24px',
+ boxSizing: 'border-box',
+ border: '1px solid #D8D7D5',
+ boxShadow: '0px 7px 8px rgba(32, 31, 29, 0.03), 0px 18px 24px rgba(32, 31, 29, 0.03)',
+ padding: 40,
+ height: 330,
+ '@mdDown': {
+ display: 'none',
+ },
+ '@md': {
+ width: '100%',
+ },
+ '@xl': {
+ width: '95%',
+ },
+}
+
+const callToActionText = {
+ color: '#3D3D3D',
+ fontWeight: '700',
+ fontSize: 64,
+ lineHeight: '70px',
+ textAlign: 'center',
+ maxWidth: 500
+}
+
+const underlineIconStyles = {
+ height: '5px',
+ alignSelf: 'normal',
+ position: 'relative',
+ bottom: 20,
+}
+
+type LandingSectionsContainerProps = {
+ hideFirst?: boolean,
+ hideSecond?: boolean,
+ hideThird?: boolean,
+ hideFourth?: boolean,
+}
+
+export function LandingSectionsContainer({
+ hideFirst = false,
+ hideSecond = false,
+ hideThird = true,
+ hideFourth = true,
+}: LandingSectionsContainerProps): JSX.Element {
+ const iconColor = 'rgb(255, 210, 52)'
+ return (
+
+
+
+ This is Omnivore
+
+
+
+
+
+
+ Collect and share the best of the web
+
+ {!hideFirst && (
+
+ }
+ icon={}
+ />
+ )}
+ {!hideSecond && (
+
+ }
+ icon={}
+ containerStyles={reversedSectionStyles}
+ />
+ )}
+ {!hideThird && (
+
+ }
+ icon={}
+ />
+ )}
+ {!hideFourth && (
+
+ }
+ icon={}
+ containerStyles={reversedSectionStyles}
+ />
+ )}
+
+
+ Get started with Omnivore today
+
+
+
+
+ )
+}
diff --git a/packages/web/pages/landing.tsx b/packages/web/pages/landing.tsx
new file mode 100644
index 000000000..a7cb1bcbb
--- /dev/null
+++ b/packages/web/pages/landing.tsx
@@ -0,0 +1,51 @@
+import { VStack, Box } from './../components/elements/LayoutPrimitives'
+import { LandingSectionsContainer, GetStartedButton } from '../components/templates/landing/LandingSectionsContainer'
+import { LandingHeader } from '../components/templates/landing/LandingHeader'
+import { LandingFooter } from '../components/templates/landing/LandingFooter'
+
+const mobileContainerStyles = {
+ maxWidth: 430,
+ alignSelf: 'center',
+ px: 10,
+ display: 'none',
+ marginTop: 60,
+ '@mdDown': {
+ display: 'flex',
+ }
+}
+
+const headingStyles = {
+ fontWeight: '700',
+ fontSize: 42,
+ lineHeight: '46px',
+ mb: 16
+}
+
+const subHeadingStyles = {
+ fontWeight: '700',
+ fontSize: 24,
+ lineHeight: '36px',
+ color: '#5F5E58',
+ mb: 32,
+}
+
+export default function LandingPage(): JSX.Element {
+ return (
+ <>
+
+
+
+ Collect and share the best of the web
+
+ Everything you read. Safe, organized, and easy to share.
+
+
+
+
+
+
+
+
+ >
+ )
+}
diff --git a/packages/web/public/static/landing/landing-1.png b/packages/web/public/static/landing/landing-1.png
new file mode 100644
index 000000000..f199bf765
Binary files /dev/null and b/packages/web/public/static/landing/landing-1.png differ
diff --git a/packages/web/public/static/landing/landing-1@2x.png b/packages/web/public/static/landing/landing-1@2x.png
new file mode 100644
index 000000000..299ab5d2e
Binary files /dev/null and b/packages/web/public/static/landing/landing-1@2x.png differ
diff --git a/packages/web/public/static/landing/landing-2.png b/packages/web/public/static/landing/landing-2.png
new file mode 100644
index 000000000..b602ca09b
Binary files /dev/null and b/packages/web/public/static/landing/landing-2.png differ
diff --git a/packages/web/public/static/landing/landing-2@2x.png b/packages/web/public/static/landing/landing-2@2x.png
new file mode 100644
index 000000000..2247052f0
Binary files /dev/null and b/packages/web/public/static/landing/landing-2@2x.png differ
diff --git a/packages/web/public/static/landing/landing-3.png b/packages/web/public/static/landing/landing-3.png
new file mode 100644
index 000000000..62c71adbf
Binary files /dev/null and b/packages/web/public/static/landing/landing-3.png differ
diff --git a/packages/web/public/static/landing/landing-3@2x.png b/packages/web/public/static/landing/landing-3@2x.png
new file mode 100644
index 000000000..07cf79bea
Binary files /dev/null and b/packages/web/public/static/landing/landing-3@2x.png differ
diff --git a/packages/web/public/static/landing/landing-4.png b/packages/web/public/static/landing/landing-4.png
new file mode 100644
index 000000000..3032d15b4
Binary files /dev/null and b/packages/web/public/static/landing/landing-4.png differ
diff --git a/packages/web/public/static/landing/landing-4@2x.png b/packages/web/public/static/landing/landing-4@2x.png
new file mode 100644
index 000000000..f8d86f031
Binary files /dev/null and b/packages/web/public/static/landing/landing-4@2x.png differ
diff --git a/packages/web/public/static/media/googlePlayBadge.png b/packages/web/public/static/media/googlePlayBadge.png
new file mode 100644
index 000000000..a18b1b88b
Binary files /dev/null and b/packages/web/public/static/media/googlePlayBadge.png differ