first
126
CHANGELOG.md
Normal file
@ -0,0 +1,126 @@
|
||||
# Changelog
|
||||
|
||||
## 2025-05-22
|
||||
|
||||
- Fix bug with focus styles
|
||||
|
||||
## 2025-04-28
|
||||
|
||||
- Update template to Tailwind CSS v4.1.4
|
||||
|
||||
## 2025-04-10
|
||||
|
||||
- Update template to Tailwind CSS v4.1.3
|
||||
|
||||
## 2025-03-22
|
||||
|
||||
- Update template to Tailwind CSS v4.0.15
|
||||
|
||||
## 2025-02-10
|
||||
|
||||
- Update template to Tailwind CSS v4.0.6
|
||||
|
||||
## 2025-01-23
|
||||
|
||||
- Update template to Tailwind CSS v4.0
|
||||
|
||||
## 2024-06-21
|
||||
|
||||
- Bump Headless UI dependency to v2.1
|
||||
- Replaced `PopoverOverlay` with `PopoverBackdrop`
|
||||
|
||||
## 2024-06-18
|
||||
|
||||
- Update `prettier` and `prettier-plugin-tailwindcss` dependencies
|
||||
- Fix formatting in `PrimaryFeatures` component
|
||||
|
||||
## 2024-05-31
|
||||
|
||||
- Fix `npm audit` warnings
|
||||
|
||||
## 2024-05-07
|
||||
|
||||
- Bump Headless UI dependency to v2.0
|
||||
|
||||
## 2024-03-27
|
||||
|
||||
- Fix page scrolling issues ([#1563](https://github.com/tailwindlabs/tailwind-plus-issues/issues/1563))
|
||||
|
||||
## 2024-01-17
|
||||
|
||||
- Fix `sharp` dependency issues ([#1549](https://github.com/tailwindlabs/tailwind-plus-issues/issues/1549))
|
||||
|
||||
## 2024-01-10
|
||||
|
||||
- Update Tailwind CSS, Next.js, Prettier, TypeScript, ESLint, and other dependencies
|
||||
- Fix types in `Button` component
|
||||
|
||||
## 2023-09-07
|
||||
|
||||
- Added TypeScript version of template
|
||||
|
||||
## 2023-08-15
|
||||
|
||||
- Bump Next.js dependency
|
||||
|
||||
## 2023-08-12
|
||||
|
||||
- Use plain anchor for mailto link ([#1490](https://github.com/tailwindlabs/tailwind-plus-issues/issues/1490))
|
||||
|
||||
## 2023-07-31
|
||||
|
||||
- Port template to Next.js app router
|
||||
|
||||
## 2023-07-18
|
||||
|
||||
- Add 404 page
|
||||
- Sort imports
|
||||
|
||||
## 2023-05-16
|
||||
|
||||
- Bump Next.js dependency
|
||||
|
||||
## 2023-04-11
|
||||
|
||||
- Bump Next.js dependency
|
||||
|
||||
## 2023-03-29
|
||||
|
||||
- Bump Tailwind CSS and Prettier dependencies
|
||||
- Sort classes
|
||||
|
||||
## 2023-03-22
|
||||
|
||||
- Bump Headless UI dependency
|
||||
|
||||
## 2023-02-02
|
||||
|
||||
- Bump Headless UI dependency
|
||||
|
||||
## 2022-11-04
|
||||
|
||||
- Bump Tailwind CSS and Next.js dependencies
|
||||
|
||||
## 2022-09-27
|
||||
|
||||
- Update Headless UI, Next.js, and Autoprefixer dependencies
|
||||
|
||||
## 2022-09-09
|
||||
|
||||
- Update Next.js dependency
|
||||
|
||||
## 2022-09-07
|
||||
|
||||
- Update Headless UI dependency
|
||||
|
||||
## 2022-09-01
|
||||
|
||||
- Update Tailwind CSS, Next.js, Headless UI, ESLint, and other dependencies
|
||||
|
||||
## 2022-08-16
|
||||
|
||||
- Enable experimental Next.js `scrollRestoration` flag
|
||||
|
||||
## 2022-08-12
|
||||
|
||||
- Initial release
|
129
LICENSE.md
Normal file
@ -0,0 +1,129 @@
|
||||
# Tailwind Plus License
|
||||
|
||||
## Personal License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission to **one individual** (the Licensee) to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components and Templates to create End Products that are designed to allow an End User to build their own End Products using the Components and Templates or derivatives of the Components and Templates.
|
||||
- Re-distribute the Components and Templates or derivatives of the Components and Templates separately from an End Product, neither in code or as design assets.
|
||||
- Share your access to the Components and Templates with any other individuals.
|
||||
- Use the Components and Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a personal website by yourself.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components (like a conference organization app that uses the components for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of usage **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind Plus components or templates (or derivatives based on Tailwind Plus components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind Plus and making it available either for sale or for free.
|
||||
- Create a Figma or Sketch UI kit based on the Tailwind Plus component designs.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind Plus.
|
||||
- Creating a theme, template, or project starter kit using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
|
||||
In simple terms, use Tailwind Plus for anything you like as long as it doesn't compete with Tailwind Plus.
|
||||
|
||||
### Personal License Definitions
|
||||
|
||||
Licensee is the individual who has purchased a Personal License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind Plus license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Team License
|
||||
|
||||
Tailwind Labs Inc. grants you an on-going, non-exclusive license to use the Components and Templates.
|
||||
|
||||
The license grants permission for **up to 25 Employees and Contractors of the Licensee** to access and use the Components and Templates.
|
||||
|
||||
You **can**:
|
||||
|
||||
- Use the Components and Templates to create unlimited End Products.
|
||||
- Modify the Components and Templates to create derivative components and templates. Those components and templates are subject to this license.
|
||||
- Use the Components and Templates to create unlimited End Products for unlimited Clients.
|
||||
- Use the Components and Templates to create End Products where the End Product is sold to End Users.
|
||||
- Use the Components and Templates to create End Products that are open source and freely available to End Users.
|
||||
|
||||
You **cannot**:
|
||||
|
||||
- Use the Components or Templates to create End Products that are designed to allow an End User to build their own End Products using the Components or Templates or derivatives of the Components or Templates.
|
||||
- Re-distribute the Components or Templates or derivatives of the Components or Templates separately from an End Product.
|
||||
- Use the Components or Templates to create End Products that are the property of any individual or entity other than the Licensee or Clients of the Licensee.
|
||||
- Use the Components or Templates to produce anything that may be deemed by Tailwind Labs Inc, in their sole and absolute discretion, to be competitive or in conflict with the business of Tailwind Labs Inc.
|
||||
|
||||
### Example usage
|
||||
|
||||
Examples of usage **allowed** by the license:
|
||||
|
||||
- Creating a website for your company.
|
||||
- Creating a website or web application for a client that will be owned by that client.
|
||||
- Creating a commercial SaaS application (like an invoicing app for example) where end users have to pay a fee to use the application.
|
||||
- Creating a commercial self-hosted web application that is sold to end users for a one-time fee.
|
||||
- Creating a web application where the primary purpose is clearly not to simply re-distribute the components or templates (like a conference organization app that uses the components or a template for its UI for example) that is free and open source, where the source code is publicly available.
|
||||
|
||||
Examples of use **not allowed** by the license:
|
||||
|
||||
- Creating a repository of your favorite Tailwind Plus components or template (or derivatives based on Tailwind Plus components or templates) and publishing it publicly.
|
||||
- Creating a React or Vue version of Tailwind Plus and making it available either for sale or for free.
|
||||
- Creating a "website builder" project where end users can build their own websites using components or templates included with or derived from Tailwind Plus.
|
||||
- Creating a theme or template using the components or templates and making it available either for sale or for free.
|
||||
- Creating an admin panel tool (like [Laravel Nova](https://nova.laravel.com/) or [ActiveAdmin](https://activeadmin.info/)) that is made available either for sale or for free.
|
||||
- Creating any End Product that is not the sole property of either your company or a client of your company. For example your employees/contractors can't use your company Tailwind Plus license to build their own websites or side projects.
|
||||
|
||||
### Team License Definitions
|
||||
|
||||
Licensee is the business entity who has purchased a Team License.
|
||||
|
||||
Components and Templates are the source code and design assets made available to the Licensee after purchasing a Tailwind Plus license.
|
||||
|
||||
End Product is any artifact produced that incorporates the Components or Templates or derivatives of the Components or Templates.
|
||||
|
||||
End User is a user of an End Product.
|
||||
|
||||
Employee is a full-time or part-time employee of the Licensee.
|
||||
|
||||
Contractor is an individual or business entity contracted to perform services for the Licensee.
|
||||
|
||||
Client is an individual or entity receiving custom professional services directly from the Licensee, produced specifically for that individual or entity. Customers of software-as-a-service products are not considered clients for the purpose of this document.
|
||||
|
||||
## Enforcement
|
||||
|
||||
If you are found to be in violation of the license, access to your Tailwind Plus account will be terminated, and a refund may be issued at our discretion. When license violation is blatant and malicious (such as intentionally redistributing the Components or Templates through private warez channels), no refund will be issued.
|
||||
|
||||
The copyright of the Components and Templates is owned by Tailwind Labs Inc. You are granted only the permissions described in this license; all other rights are reserved. Tailwind Labs Inc. reserves the right to pursue legal remedies for any unauthorized use of the Components or Templates outside the scope of this license.
|
||||
|
||||
## Liability
|
||||
|
||||
Tailwind Labs Inc.’s liability to you for costs, damages, or other losses arising from your use of the Components or Templates — including third-party claims against you — is limited to a refund of your license fee. Tailwind Labs Inc. may not be held liable for any consequential damages related to your use of the Components or Templates.
|
||||
|
||||
This Agreement is governed by the laws of the Province of Ontario and the applicable laws of Canada. Legal proceedings related to this Agreement may only be brought in the courts of Ontario. You agree to service of process at the e-mail address on your original order.
|
||||
|
||||
## Questions?
|
||||
|
||||
Unsure which license you need, or unsure if your use case is covered by our licenses?
|
||||
|
||||
Email us at [support@tailwindcss.com](mailto:support@tailwindcss.com) with your questions.
|
35
README.md
@ -0,0 +1,35 @@
|
||||
# Pocket
|
||||
|
||||
Pocket is a [Tailwind Plus](https://tailwindcss.com/plus) site template built using [Tailwind CSS](https://tailwindcss.com) and [Next.js](https://nextjs.org).
|
||||
|
||||
## Getting started
|
||||
|
||||
To get started with this template, first install the npm dependencies:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
Next, run the development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Finally, open [http://localhost:3000](http://localhost:3000) in your browser to view the website.
|
||||
|
||||
## Customizing
|
||||
|
||||
You can start editing this template by modifying the files in the `/src` folder. The site will auto-update as you edit these files.
|
||||
|
||||
## License
|
||||
|
||||
This site template is a commercial product and is licensed under the [Tailwind Plus license](https://tailwindcss.com/plus/license).
|
||||
|
||||
## Learn more
|
||||
|
||||
To learn more about the technologies used in this site template, see the following resources:
|
||||
|
||||
- [Tailwind CSS](https://tailwindcss.com/docs) - the official Tailwind CSS documentation
|
||||
- [Next.js](https://nextjs.org/docs) - the official Next.js documentation
|
||||
- [Headless UI](https://headlessui.dev) - the official Headless UI documentation
|
5
next-env.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/basic-features/typescript for more information.
|
4
next.config.js
Normal file
@ -0,0 +1,4 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {}
|
||||
|
||||
module.exports = nextConfig
|
5289
package-lock.json
generated
Normal file
35
package.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "tailwind-plus-pocket",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"browserslist": "defaults, not ie <= 11",
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.1.0",
|
||||
"@tailwindcss/forms": "^0.5.3",
|
||||
"@tailwindcss/postcss": "^4.1.7",
|
||||
"@types/node": "^20.10.8",
|
||||
"@types/react": "^18.2.47",
|
||||
"@types/react-dom": "^18.2.18",
|
||||
"clsx": "^2.1.0",
|
||||
"framer-motion": "^10.15.0",
|
||||
"next": "^14.0.4",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^4.1.7",
|
||||
"typescript": "^5.3.3",
|
||||
"use-debounce": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-config-next": "^14.0.4",
|
||||
"prettier": "^3.3.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"sharp": "0.33.1"
|
||||
}
|
||||
}
|
5
postcss.config.js
Normal file
@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
},
|
||||
}
|
7
prettier.config.js
Normal file
@ -0,0 +1,7 @@
|
||||
/** @type {import('prettier').Options} */
|
||||
module.exports = {
|
||||
singleQuote: true,
|
||||
semi: false,
|
||||
plugins: ['prettier-plugin-tailwindcss'],
|
||||
tailwindStylesheet: './src/styles/tailwind.css',
|
||||
}
|
49
src/app/(auth)/login/page.tsx
Normal file
@ -0,0 +1,49 @@
|
||||
import { type Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { AuthLayout } from '@/components/AuthLayout'
|
||||
import { Button } from '@/components/Button'
|
||||
import { TextField } from '@/components/Fields'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sign In',
|
||||
}
|
||||
|
||||
export default function Login() {
|
||||
return (
|
||||
<AuthLayout
|
||||
title="Sign in to account"
|
||||
subtitle={
|
||||
<>
|
||||
Don’t have an account?{' '}
|
||||
<Link href="/register" className="text-cyan-600">
|
||||
Sign up
|
||||
</Link>{' '}
|
||||
for a free trial.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form>
|
||||
<div className="space-y-6">
|
||||
<TextField
|
||||
label="Email address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="current-password"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" color="cyan" className="mt-8 w-full">
|
||||
Sign in to account
|
||||
</Button>
|
||||
</form>
|
||||
</AuthLayout>
|
||||
)
|
||||
}
|
75
src/app/(auth)/register/page.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
import { type Metadata } from 'next'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { AuthLayout } from '@/components/AuthLayout'
|
||||
import { Button } from '@/components/Button'
|
||||
import { SelectField, TextField } from '@/components/Fields'
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Sign Up',
|
||||
}
|
||||
|
||||
export default function Register() {
|
||||
return (
|
||||
<AuthLayout
|
||||
title="Sign up for an account"
|
||||
subtitle={
|
||||
<>
|
||||
Already registered?{' '}
|
||||
<Link href="/login" className="text-cyan-600">
|
||||
Sign in
|
||||
</Link>{' '}
|
||||
to your account.
|
||||
</>
|
||||
}
|
||||
>
|
||||
<form>
|
||||
<div className="grid grid-cols-2 gap-6">
|
||||
<TextField
|
||||
label="First name"
|
||||
name="first_name"
|
||||
type="text"
|
||||
autoComplete="given-name"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
label="Last name"
|
||||
name="last_name"
|
||||
type="text"
|
||||
autoComplete="family-name"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
className="col-span-full"
|
||||
label="Email address"
|
||||
name="email"
|
||||
type="email"
|
||||
autoComplete="email"
|
||||
required
|
||||
/>
|
||||
<TextField
|
||||
className="col-span-full"
|
||||
label="Password"
|
||||
name="password"
|
||||
type="password"
|
||||
autoComplete="new-password"
|
||||
required
|
||||
/>
|
||||
<SelectField
|
||||
className="col-span-full"
|
||||
label="How did you hear about us?"
|
||||
name="referral_source"
|
||||
>
|
||||
<option>AltaVista search</option>
|
||||
<option>Super Bowl commercial</option>
|
||||
<option>Our route 34 city bus ad</option>
|
||||
<option>The “Never Use This” podcast</option>
|
||||
</SelectField>
|
||||
</div>
|
||||
<Button type="submit" color="cyan" className="mt-8 w-full">
|
||||
Get started today
|
||||
</Button>
|
||||
</form>
|
||||
</AuthLayout>
|
||||
)
|
||||
}
|
1
src/app/(main)/layout.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { Layout as default } from '@/components/Layout'
|
21
src/app/(main)/page.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { CallToAction } from '@/components/CallToAction'
|
||||
import { Faqs } from '@/components/Faqs'
|
||||
import { Hero } from '@/components/Hero'
|
||||
import { Pricing } from '@/components/Pricing'
|
||||
import { PrimaryFeatures } from '@/components/PrimaryFeatures'
|
||||
import { Reviews } from '@/components/Reviews'
|
||||
import { SecondaryFeatures } from '@/components/SecondaryFeatures'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
<PrimaryFeatures />
|
||||
<SecondaryFeatures />
|
||||
<CallToAction />
|
||||
<Reviews />
|
||||
<Pricing />
|
||||
<Faqs />
|
||||
</>
|
||||
)
|
||||
}
|
BIN
src/app/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
32
src/app/layout.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { type Metadata } from 'next'
|
||||
import { Inter } from 'next/font/google'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import '@/styles/tailwind.css'
|
||||
|
||||
const inter = Inter({
|
||||
subsets: ['latin'],
|
||||
display: 'swap',
|
||||
variable: '--font-inter',
|
||||
})
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: {
|
||||
template: '%s - Pocket',
|
||||
default: 'Pocket - Invest at the perfect time.',
|
||||
},
|
||||
description:
|
||||
'By leveraging insights from our network of industry insiders, you’ll know exactly when to buy to maximize profit, and exactly when to sell to avoid painful losses.',
|
||||
}
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className={clsx('bg-gray-50 antialiased', inter.variable)}>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
24
src/app/not-found.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import { Button } from '@/components/Button'
|
||||
import { CirclesBackground } from '@/components/CirclesBackground'
|
||||
import { Container } from '@/components/Container'
|
||||
import { Layout } from '@/components/Layout'
|
||||
|
||||
export default function NotFound() {
|
||||
return (
|
||||
<Layout>
|
||||
<Container className="relative isolate flex h-full flex-col items-center justify-center py-20 text-center sm:py-32">
|
||||
<CirclesBackground className="absolute top-1/2 left-1/2 -z-10 mt-44 w-272.5 -translate-x-1/2 -translate-y-1/2 mask-[linear-gradient(to_bottom,white_20%,transparent_75%)] stroke-gray-300/30" />
|
||||
<p className="text-sm font-semibold text-gray-900">404</p>
|
||||
<h1 className="mt-2 text-3xl font-medium tracking-tight text-gray-900">
|
||||
Page not found
|
||||
</h1>
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
Sorry, we couldn’t find the page you’re looking for.
|
||||
</p>
|
||||
<Button href="/" variant="outline" className="mt-8">
|
||||
Go back home
|
||||
</Button>
|
||||
</Container>
|
||||
</Layout>
|
||||
)
|
||||
}
|
250
src/components/AppDemo.tsx
Normal file
@ -0,0 +1,250 @@
|
||||
'use client'
|
||||
|
||||
import { useId, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { motion, useInView, useMotionValue } from 'framer-motion'
|
||||
|
||||
import { AppScreen } from '@/components/AppScreen'
|
||||
|
||||
const prices = [
|
||||
997.56, 944.34, 972.25, 832.4, 888.76, 834.8, 805.56, 767.38, 861.21, 669.6,
|
||||
694.39, 721.32, 694.03, 610.1, 502.2, 549.56, 611.03, 583.4, 610.14, 660.6,
|
||||
752.11, 721.19, 638.89, 661.7, 694.51, 580.3, 638.0, 613.3, 651.64, 560.51,
|
||||
611.45, 670.68, 752.56,
|
||||
]
|
||||
const maxPrice = Math.max(...prices)
|
||||
const minPrice = Math.min(...prices)
|
||||
|
||||
function Chart({
|
||||
className,
|
||||
activePointIndex,
|
||||
onChangeActivePointIndex,
|
||||
width: totalWidth,
|
||||
height: totalHeight,
|
||||
paddingX = 0,
|
||||
paddingY = 0,
|
||||
gridLines = 6,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'svg'> & {
|
||||
activePointIndex: number | null
|
||||
onChangeActivePointIndex: (index: number | null) => void
|
||||
width: number
|
||||
height: number
|
||||
paddingX?: number
|
||||
paddingY?: number
|
||||
gridLines?: number
|
||||
}) {
|
||||
let width = totalWidth - paddingX * 2
|
||||
let height = totalHeight - paddingY * 2
|
||||
|
||||
let id = useId()
|
||||
let svgRef = useRef<React.ElementRef<'svg'>>(null)
|
||||
let pathRef = useRef<React.ElementRef<'path'>>(null)
|
||||
let isInView = useInView(svgRef, { amount: 0.5, once: true })
|
||||
let pathWidth = useMotionValue(0)
|
||||
let [interactionEnabled, setInteractionEnabled] = useState(false)
|
||||
|
||||
let path = ''
|
||||
let points: Array<{ x: number; y: number }> = []
|
||||
|
||||
for (let index = 0; index < prices.length; index++) {
|
||||
let x = paddingX + (index / (prices.length - 1)) * width
|
||||
let y =
|
||||
paddingY +
|
||||
(1 - (prices[index] - minPrice) / (maxPrice - minPrice)) * height
|
||||
points.push({ x, y })
|
||||
path += `${index === 0 ? 'M' : 'L'} ${x.toFixed(4)} ${y.toFixed(4)}`
|
||||
}
|
||||
|
||||
return (
|
||||
<svg
|
||||
ref={svgRef}
|
||||
viewBox={`0 0 ${totalWidth} ${totalHeight}`}
|
||||
className={clsx(className, 'overflow-visible')}
|
||||
{...(interactionEnabled
|
||||
? {
|
||||
onPointerLeave: () => onChangeActivePointIndex(null),
|
||||
onPointerMove: (event) => {
|
||||
let x = event.nativeEvent.offsetX
|
||||
let closestPointIndex: number | null = null
|
||||
let closestDistance = Infinity
|
||||
for (
|
||||
let pointIndex = 0;
|
||||
pointIndex < points.length;
|
||||
pointIndex++
|
||||
) {
|
||||
let point = points[pointIndex]
|
||||
let distance = Math.abs(point.x - x)
|
||||
if (distance < closestDistance) {
|
||||
closestDistance = distance
|
||||
closestPointIndex = pointIndex
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
onChangeActivePointIndex(closestPointIndex)
|
||||
},
|
||||
}
|
||||
: {})}
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<clipPath id={`${id}-clip`}>
|
||||
<path d={`${path} V ${height + paddingY} H ${paddingX} Z`} />
|
||||
</clipPath>
|
||||
<linearGradient id={`${id}-gradient`} x1="0" x2="0" y1="0" y2="1">
|
||||
<stop offset="0%" stopColor="#13B5C8" />
|
||||
<stop offset="100%" stopColor="#13B5C8" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
{[...Array(gridLines - 1).keys()].map((index) => (
|
||||
<line
|
||||
key={index}
|
||||
stroke="#a3a3a3"
|
||||
opacity="0.1"
|
||||
x1="0"
|
||||
y1={(totalHeight / gridLines) * (index + 1)}
|
||||
x2={totalWidth}
|
||||
y2={(totalHeight / gridLines) * (index + 1)}
|
||||
/>
|
||||
))}
|
||||
<motion.rect
|
||||
y={paddingY}
|
||||
width={pathWidth}
|
||||
height={height}
|
||||
fill={`url(#${id}-gradient)`}
|
||||
clipPath={`url(#${id}-clip)`}
|
||||
opacity="0.5"
|
||||
/>
|
||||
<motion.path
|
||||
ref={pathRef}
|
||||
d={path}
|
||||
fill="none"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
initial={{ pathLength: 0 }}
|
||||
transition={{ duration: 1 }}
|
||||
{...(isInView ? { stroke: '#06b6d4', animate: { pathLength: 1 } } : {})}
|
||||
onUpdate={({ pathLength }) => {
|
||||
if (pathRef.current && typeof pathLength === 'number') {
|
||||
pathWidth.set(
|
||||
pathRef.current.getPointAtLength(
|
||||
pathLength * pathRef.current.getTotalLength(),
|
||||
).x,
|
||||
)
|
||||
}
|
||||
}}
|
||||
onAnimationComplete={() => setInteractionEnabled(true)}
|
||||
/>
|
||||
{activePointIndex !== null && (
|
||||
<>
|
||||
<line
|
||||
x1="0"
|
||||
y1={points[activePointIndex].y}
|
||||
x2={totalWidth}
|
||||
y2={points[activePointIndex].y}
|
||||
stroke="#06b6d4"
|
||||
strokeDasharray="1 3"
|
||||
/>
|
||||
<circle
|
||||
r="4"
|
||||
cx={points[activePointIndex].x}
|
||||
cy={points[activePointIndex].y}
|
||||
fill="#fff"
|
||||
strokeWidth="2"
|
||||
stroke="#06b6d4"
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppDemo() {
|
||||
let [activePointIndex, setActivePointIndex] = useState<number | null>(null)
|
||||
let activePriceIndex = activePointIndex ?? prices.length - 1
|
||||
let activeValue = prices[activePriceIndex]
|
||||
let previousValue = prices[activePriceIndex - 1]
|
||||
let percentageChange =
|
||||
activePriceIndex === 0
|
||||
? null
|
||||
: ((activeValue - previousValue) / previousValue) * 100
|
||||
|
||||
return (
|
||||
<AppScreen>
|
||||
<AppScreen.Body>
|
||||
<div className="p-4">
|
||||
<div className="flex gap-2">
|
||||
<div className="text-xs/6 text-gray-500">Tailwind Labs, Inc.</div>
|
||||
<div className="text-sm text-gray-900">$CSS</div>
|
||||
<svg viewBox="0 0 24 24" className="ml-auto h-6 w-6" fill="none">
|
||||
<path
|
||||
d="M5 12a7 7 0 1 1 14 0 7 7 0 0 1-14 0ZM12 9v6M15 12H9"
|
||||
stroke="#171717"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="mt-3 border-t border-gray-200 pt-5">
|
||||
<div className="flex items-baseline gap-2">
|
||||
<div className="text-2xl tracking-tight text-gray-900 tabular-nums">
|
||||
{activeValue.toFixed(2)}
|
||||
</div>
|
||||
<div className="text-sm text-gray-900">USD</div>
|
||||
{percentageChange && (
|
||||
<div
|
||||
className={clsx(
|
||||
'ml-auto text-sm tracking-tight tabular-nums',
|
||||
percentageChange >= 0 ? 'text-cyan-500' : 'text-gray-500',
|
||||
)}
|
||||
>
|
||||
{`${
|
||||
percentageChange >= 0 ? '+' : ''
|
||||
}${percentageChange.toFixed(2)}%`}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-6 flex gap-4 text-xs text-gray-500">
|
||||
<div>1D</div>
|
||||
<div>5D</div>
|
||||
<div className="font-semibold text-cyan-600">1M</div>
|
||||
<div>6M</div>
|
||||
<div>1Y</div>
|
||||
<div>5Y</div>
|
||||
</div>
|
||||
<div className="mt-3 rounded-lg bg-gray-50 ring-1 ring-black/5 ring-inset">
|
||||
<Chart
|
||||
width={286}
|
||||
height={208}
|
||||
paddingX={16}
|
||||
paddingY={32}
|
||||
activePointIndex={activePointIndex}
|
||||
onChangeActivePointIndex={setActivePointIndex}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-4 rounded-lg bg-cyan-500 px-4 py-2 text-center text-sm font-semibold text-white">
|
||||
Trade
|
||||
</div>
|
||||
<div className="mt-3 divide-y divide-gray-100 text-sm">
|
||||
<div className="flex justify-between py-1">
|
||||
<div className="text-gray-500">Open</div>
|
||||
<div className="font-medium text-gray-900">6,387.55</div>
|
||||
</div>
|
||||
<div className="flex justify-between py-1">
|
||||
<div className="text-gray-500">Closed</div>
|
||||
<div className="font-medium text-gray-900">6,487.09</div>
|
||||
</div>
|
||||
<div className="flex justify-between py-1">
|
||||
<div className="text-gray-500">Low</div>
|
||||
<div className="font-medium text-gray-900">6,322.01</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</AppScreen.Body>
|
||||
</AppScreen>
|
||||
)
|
||||
}
|
109
src/components/AppScreen.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import { forwardRef } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
function Logo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 79 24" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M12 24C5.373 24 0 18.627 0 12S5.373 0 12 0s12 5.373 12 12-5.373 12-12 12ZM2.4 12a9.004 9.004 0 0 0 6.055 8.507c1.565.542 2.945-.85 2.945-2.507V6c0-1.657-1.38-3.049-2.945-2.507A9.004 9.004 0 0 0 2.4 12Z"
|
||||
fill="#06B6D4"
|
||||
/>
|
||||
<path
|
||||
d="M33.004 17V6.818h3.818c.783 0 1.439.146 1.97.438.533.291.935.692 1.207 1.203.275.507.413 1.084.413 1.73 0 .653-.138 1.233-.413 1.74a2.948 2.948 0 0 1-1.218 1.198c-.537.288-1.198.433-1.983.433h-2.531v-1.517h2.282c.457 0 .832-.08 1.124-.238.291-.16.507-.378.646-.657.142-.278.214-.598.214-.96 0-.36-.072-.679-.214-.954a1.452 1.452 0 0 0-.651-.641c-.292-.156-.668-.234-1.129-.234h-1.69V17h-1.845Zm12.152.15c-.746 0-1.392-.165-1.939-.493a3.343 3.343 0 0 1-1.273-1.377c-.298-.59-.447-1.28-.447-2.068 0-.79.15-1.48.447-2.073a3.335 3.335 0 0 1 1.273-1.383c.547-.328 1.193-.492 1.94-.492.745 0 1.391.164 1.938.492.547.329.97.79 1.268 1.383.301.593.452 1.284.452 2.073 0 .789-.15 1.478-.452 2.068a3.309 3.309 0 0 1-1.268 1.377c-.547.328-1.193.492-1.939.492Zm.01-1.443c.404 0 .742-.11 1.014-.333.272-.225.474-.527.607-.905.136-.377.204-.798.204-1.262 0-.468-.068-.89-.204-1.268a2.007 2.007 0 0 0-.607-.91c-.272-.225-.61-.338-1.014-.338-.414 0-.759.113-1.034.338a2.041 2.041 0 0 0-.612.91 3.81 3.81 0 0 0-.198 1.268c0 .464.066.885.198 1.262.136.378.34.68.612.905.275.222.62.333 1.034.333Zm8.508 1.442c-.763 0-1.417-.167-1.964-.502a3.352 3.352 0 0 1-1.258-1.387c-.292-.593-.437-1.276-.437-2.048 0-.776.149-1.46.447-2.054a3.34 3.34 0 0 1 1.263-1.392c.547-.334 1.193-.502 1.939-.502.62 0 1.168.115 1.645.343.48.226.864.546 1.149.96.285.41.447.891.487 1.441h-1.72a1.644 1.644 0 0 0-.497-.92c-.259-.248-.605-.372-1.04-.372-.367 0-.69.1-.969.298-.278.196-.495.478-.651.845-.153.368-.229.81-.229 1.323 0 .52.076.968.229 1.342.152.371.366.658.641.86.279.2.605.298.98.298.265 0 .502-.05.71-.149.213-.102.39-.25.532-.442.143-.192.24-.426.294-.701h1.72a2.999 2.999 0 0 1-.477 1.437c-.275.414-.65.739-1.124.974-.474.232-1.03.348-1.67.348Zm6.39-2.545-.006-2.173h.289l2.744-3.067h2.103l-3.376 3.758h-.372l-1.383 1.482ZM58.422 17V6.818h1.8V17h-1.8Zm4.792 0-2.485-3.475 1.213-1.268L65.368 17h-2.153Zm6.245.15c-.766 0-1.427-.16-1.984-.478a3.233 3.233 0 0 1-1.278-1.362c-.298-.59-.447-1.285-.447-2.083 0-.786.149-1.475.447-2.069a3.384 3.384 0 0 1 1.263-1.392c.54-.334 1.175-.502 1.904-.502.47 0 .915.076 1.333.229.42.149.792.381 1.113.696.325.315.58.716.766 1.203.186.484.278 1.06.278 1.73v.552h-6.259v-1.213h4.534a1.935 1.935 0 0 0-.224-.92 1.625 1.625 0 0 0-.611-.641 1.719 1.719 0 0 0-.905-.234c-.368 0-.691.09-.97.269a1.848 1.848 0 0 0-.65.696c-.153.285-.231.598-.234.94v1.058c0 .444.08.825.243 1.144.163.315.39.556.681.726.292.165.634.248 1.025.248.261 0 .498-.036.71-.11.213-.075.397-.187.552-.332.156-.146.274-.327.353-.542l1.68.189a2.62 2.62 0 0 1-.606 1.163 2.958 2.958 0 0 1-1.133.766c-.461.179-.988.268-1.581.268Zm8.731-7.786v1.392h-4.39V9.364h4.39Zm-3.306-1.83h1.8v7.17c0 .241.036.427.109.556a.59.59 0 0 0 .298.258c.123.047.259.07.408.07.113 0 .215-.008.308-.025.096-.016.17-.031.219-.045l.303 1.407c-.096.034-.233.07-.412.11-.176.04-.392.063-.647.07a2.934 2.934 0 0 1-1.218-.204 1.895 1.895 0 0 1-.86-.706c-.209-.319-.311-.716-.308-1.194V7.534Z"
|
||||
fill="#fff"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MenuIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M5 6h14M5 18h14M5 12h14"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function UserIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M15 8a3 3 0 1 1-6 0 3 3 0 0 1 6 0ZM6.696 19h10.608c1.175 0 2.08-.935 1.532-1.897C18.028 15.69 16.187 14 12 14s-6.028 1.689-6.836 3.103C4.616 18.065 5.521 19 6.696 19Z"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppScreen({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div className={clsx('flex flex-col', className)} {...props}>
|
||||
<div className="flex justify-between px-4 pt-4">
|
||||
<MenuIcon className="h-6 w-6 flex-none" />
|
||||
<Logo className="h-6 flex-none" />
|
||||
<UserIcon className="h-6 w-6 flex-none" />
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
AppScreen.Header = forwardRef<
|
||||
React.ElementRef<'div'>,
|
||||
{ children: React.ReactNode }
|
||||
>(function AppScreenHeader({ children }, ref) {
|
||||
return (
|
||||
<div ref={ref} className="mt-6 px-4 text-white">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
AppScreen.Title = forwardRef<
|
||||
React.ElementRef<'div'>,
|
||||
{ children: React.ReactNode }
|
||||
>(function AppScreenTitle({ children }, ref) {
|
||||
return (
|
||||
<div ref={ref} className="text-2xl text-white">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
AppScreen.Subtitle = forwardRef<
|
||||
React.ElementRef<'div'>,
|
||||
{ children: React.ReactNode }
|
||||
>(function AppScreenSubtitle({ children }, ref) {
|
||||
return (
|
||||
<div ref={ref} className="text-sm text-gray-500">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
AppScreen.Body = forwardRef<
|
||||
React.ElementRef<'div'>,
|
||||
{ className?: string; children: React.ReactNode }
|
||||
>(function AppScreenBody({ children, className }, ref) {
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={clsx('mt-6 flex-auto rounded-t-2xl bg-white', className)}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
28
src/components/AppStoreLink.tsx
Normal file
40
src/components/AuthLayout.tsx
Normal file
@ -0,0 +1,40 @@
|
||||
import Link from 'next/link'
|
||||
|
||||
import { CirclesBackground } from '@/components/CirclesBackground'
|
||||
import { Logo } from '@/components/Logo'
|
||||
|
||||
export function AuthLayout({
|
||||
title,
|
||||
subtitle,
|
||||
children,
|
||||
}: {
|
||||
title: string
|
||||
subtitle: React.ReactNode
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<main className="flex min-h-full overflow-hidden pt-16 sm:py-28">
|
||||
<div className="mx-auto flex w-full max-w-2xl flex-col px-4 sm:px-6">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="mx-auto h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="relative mt-12 sm:mt-16">
|
||||
<CirclesBackground
|
||||
width="1090"
|
||||
height="1090"
|
||||
className="absolute -top-7 left-1/2 -z-10 h-[788px] -translate-x-1/2 mask-[linear-gradient(to_bottom,white_20%,transparent_75%)] stroke-gray-300/30 sm:-top-9 sm:h-auto"
|
||||
/>
|
||||
<h1 className="text-center text-2xl font-medium tracking-tight text-gray-900">
|
||||
{title}
|
||||
</h1>
|
||||
{subtitle && (
|
||||
<p className="mt-3 text-center text-lg text-gray-600">{subtitle}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="-mx-4 mt-10 flex-auto bg-white px-4 py-10 shadow-2xl shadow-gray-900/10 sm:mx-0 sm:flex-none sm:rounded-5xl sm:p-24">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
)
|
||||
}
|
59
src/components/Button.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import Link from 'next/link'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const baseStyles = {
|
||||
solid:
|
||||
'inline-flex justify-center rounded-lg py-2 px-3 text-sm font-semibold transition-colors',
|
||||
outline:
|
||||
'inline-flex justify-center rounded-lg border py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-sm transition-colors',
|
||||
}
|
||||
|
||||
const variantStyles = {
|
||||
solid: {
|
||||
cyan: 'relative overflow-hidden bg-cyan-500 text-white before:absolute before:inset-0 active:before:bg-transparent hover:before:bg-white/10 active:bg-cyan-600 active:text-white/80 before:transition-colors',
|
||||
white:
|
||||
'bg-white text-cyan-900 hover:bg-white/90 active:bg-white/90 active:text-cyan-900/70',
|
||||
gray: 'bg-gray-800 text-white hover:bg-gray-900 active:bg-gray-800 active:text-white/80',
|
||||
},
|
||||
outline: {
|
||||
gray: 'border-gray-300 text-gray-700 hover:border-gray-400 active:bg-gray-100 active:text-gray-700/80',
|
||||
},
|
||||
}
|
||||
|
||||
type ButtonProps = (
|
||||
| {
|
||||
variant?: 'solid'
|
||||
color?: keyof typeof variantStyles.solid
|
||||
}
|
||||
| {
|
||||
variant: 'outline'
|
||||
color?: keyof typeof variantStyles.outline
|
||||
}
|
||||
) &
|
||||
(
|
||||
| Omit<React.ComponentPropsWithoutRef<typeof Link>, 'color'>
|
||||
| (Omit<React.ComponentPropsWithoutRef<'button'>, 'color'> & {
|
||||
href?: undefined
|
||||
})
|
||||
)
|
||||
|
||||
export function Button({ className, ...props }: ButtonProps) {
|
||||
props.variant ??= 'solid'
|
||||
props.color ??= 'gray'
|
||||
|
||||
className = clsx(
|
||||
baseStyles[props.variant],
|
||||
props.variant === 'outline'
|
||||
? variantStyles.outline[props.color]
|
||||
: props.variant === 'solid'
|
||||
? variantStyles.solid[props.color]
|
||||
: undefined,
|
||||
className,
|
||||
)
|
||||
|
||||
return typeof props.href === 'undefined' ? (
|
||||
<button className={className} {...props} />
|
||||
) : (
|
||||
<Link className={className} {...props} />
|
||||
)
|
||||
}
|
31
src/components/CallToAction.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { AppStoreLink } from '@/components/AppStoreLink'
|
||||
import { CircleBackground } from '@/components/CircleBackground'
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
export function CallToAction() {
|
||||
return (
|
||||
<section
|
||||
id="get-free-shares-today"
|
||||
className="relative overflow-hidden bg-gray-900 py-20 sm:py-28"
|
||||
>
|
||||
<div className="absolute top-1/2 left-20 -translate-y-1/2 sm:left-1/2 sm:-translate-x-1/2">
|
||||
<CircleBackground color="#fff" className="animate-spin-slower" />
|
||||
</div>
|
||||
<Container className="relative">
|
||||
<div className="mx-auto max-w-md sm:text-center">
|
||||
<h2 className="text-3xl font-medium tracking-tight text-white sm:text-4xl">
|
||||
Get your first tips today
|
||||
</h2>
|
||||
<p className="mt-4 text-lg text-gray-300">
|
||||
It takes 30 seconds to sign up. Download the app and create an
|
||||
account today and we’ll send you a tip guaranteed to double your
|
||||
first investment.
|
||||
</p>
|
||||
<div className="mt-8 flex justify-center">
|
||||
<AppStoreLink color="white" />
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
45
src/components/CircleBackground.tsx
Normal file
@ -0,0 +1,45 @@
|
||||
import { useId } from 'react'
|
||||
|
||||
export function CircleBackground({
|
||||
color,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'svg'> & {
|
||||
color: string
|
||||
}) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 558 558"
|
||||
width="558"
|
||||
height="558"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={id}
|
||||
x1="79"
|
||||
y1="16"
|
||||
x2="105"
|
||||
y2="237"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor={color} />
|
||||
<stop offset="1" stopColor={color} stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<path
|
||||
opacity=".2"
|
||||
d="M1 279C1 125.465 125.465 1 279 1s278 124.465 278 278-124.465 278-278 278S1 432.535 1 279Z"
|
||||
stroke={color}
|
||||
/>
|
||||
<path
|
||||
d="M1 279C1 125.465 125.465 1 279 1"
|
||||
stroke={`url(#${id})`}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
18
src/components/CirclesBackground.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
export function CirclesBackground(
|
||||
props: React.ComponentPropsWithoutRef<'svg'>,
|
||||
) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 1090 1090"
|
||||
aria-hidden="true"
|
||||
fill="none"
|
||||
preserveAspectRatio="none"
|
||||
{...props}
|
||||
>
|
||||
<circle cx={545} cy={545} r="544.5" />
|
||||
<circle cx={545} cy={545} r="480.5" />
|
||||
<circle cx={545} cy={545} r="416.5" />
|
||||
<circle cx={545} cy={545} r="352.5" />
|
||||
</svg>
|
||||
)
|
||||
}
|
13
src/components/Container.tsx
Normal file
@ -0,0 +1,13 @@
|
||||
import clsx from 'clsx'
|
||||
|
||||
export function Container({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'>) {
|
||||
return (
|
||||
<div
|
||||
className={clsx('mx-auto max-w-7xl px-4 sm:px-6 lg:px-8', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
105
src/components/Faqs.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
const faqs = [
|
||||
[
|
||||
{
|
||||
question: 'How do I know the tips are good?',
|
||||
answer:
|
||||
'Our whole business depends on our tips being good, so it’s in our best interest that they are. The results of our customers speak for themselves, just trust us.',
|
||||
},
|
||||
{
|
||||
question: 'Isn’t this insider trading?',
|
||||
answer:
|
||||
'Yes exactly. But at scale! Historically you could only make insider trades with knowledge from your direct network. Pocket brings you insider trading tips from people you don’t even know.',
|
||||
},
|
||||
{
|
||||
question: 'But isn’t insider trading illegal?',
|
||||
answer:
|
||||
'Here’s the thing: you’re the one doing the insider trading, not us. We’re just giving you the tips and some tools to make trades. We’re not doing anything wrong here.',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: 'Do the people giving you tips realize what they are doing?',
|
||||
answer:
|
||||
'Again I would argue this isn’t really our responsibility. People make their own choices. If they don’t research the consequences that’s on them, not on us.',
|
||||
},
|
||||
{
|
||||
question: 'Where is Pocket based?',
|
||||
answer:
|
||||
'Let’s just say it’s not somewhere where the SEC is going to find us.',
|
||||
},
|
||||
{
|
||||
question: 'Is there any age limit to trading on Pocket?',
|
||||
answer:
|
||||
'For our free plan, the age limit is based on the minimum age to trade in your country of residence. Our VIP plan uses advanced transaction anonymization though, so you can use that plan even if you’re 9 years old. Or a dog.',
|
||||
},
|
||||
],
|
||||
[
|
||||
{
|
||||
question: 'How did you get this on the App Store?',
|
||||
answer:
|
||||
'Honestly we were surprised too, but eventually we found out that the app reviewer found the app so compelling they approved it just so they could use it themselves.',
|
||||
},
|
||||
{
|
||||
question: 'How do I explain the money I withdraw from Pocket to the IRS?',
|
||||
answer:
|
||||
'This feels like one-hundred percent a you problem. Pocket is not responsible in any way for your tax returns.',
|
||||
},
|
||||
{
|
||||
question: 'How do I become an insider?',
|
||||
answer:
|
||||
'Contact us with some details about your industry and the type of access you have to apply for an insider account. Once approved, we’ll send you a guide on collecting insider information without being detected at work.',
|
||||
},
|
||||
],
|
||||
]
|
||||
|
||||
export function Faqs() {
|
||||
return (
|
||||
<section
|
||||
id="faqs"
|
||||
aria-labelledby="faqs-title"
|
||||
className="border-t border-gray-200 py-20 sm:py-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||
<h2
|
||||
id="faqs-title"
|
||||
className="text-3xl font-medium tracking-tight text-gray-900"
|
||||
>
|
||||
Frequently asked questions
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
If you have anything else you want to ask,{' '}
|
||||
<a
|
||||
href="mailto:info@example.com"
|
||||
className="text-gray-900 underline"
|
||||
>
|
||||
reach out to us
|
||||
</a>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
<ul
|
||||
role="list"
|
||||
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 sm:mt-20 lg:max-w-none lg:grid-cols-3"
|
||||
>
|
||||
{faqs.map((column, columnIndex) => (
|
||||
<li key={columnIndex}>
|
||||
<ul role="list" className="space-y-10">
|
||||
{column.map((faq, faqIndex) => (
|
||||
<li key={faqIndex}>
|
||||
<h3 className="text-lg/6 font-semibold text-gray-900">
|
||||
{faq.question}
|
||||
</h3>
|
||||
<p className="mt-4 text-sm text-gray-700">{faq.answer}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
47
src/components/Fields.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { useId } from 'react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
const formClasses =
|
||||
'block w-full appearance-none rounded-lg border border-gray-200 bg-white py-[calc(--spacing(2)-1px)] px-[calc(--spacing(3)-1px)] text-gray-900 placeholder:text-gray-400 focus:border-cyan-500 focus:outline-hidden focus:ring-cyan-500 sm:text-sm'
|
||||
|
||||
function Label({ id, children }: { id: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className="mb-2 block text-sm font-semibold text-gray-900"
|
||||
>
|
||||
{children}
|
||||
</label>
|
||||
)
|
||||
}
|
||||
|
||||
export function TextField({
|
||||
label,
|
||||
type = 'text',
|
||||
className,
|
||||
...props
|
||||
}: Omit<React.ComponentPropsWithoutRef<'input'>, 'id'> & { label?: string }) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && <Label id={id}>{label}</Label>}
|
||||
<input id={id} type={type} {...props} className={formClasses} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function SelectField({
|
||||
label,
|
||||
className,
|
||||
...props
|
||||
}: Omit<React.ComponentPropsWithoutRef<'select'>, 'id'> & { label?: string }) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{label && <Label id={id}>{label}</Label>}
|
||||
<select id={id} {...props} className={clsx(formClasses, 'pr-8')} />
|
||||
</div>
|
||||
)
|
||||
}
|
80
src/components/Footer.tsx
Normal file
@ -0,0 +1,80 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import { TextField } from '@/components/Fields'
|
||||
import { Logomark } from '@/components/Logo'
|
||||
import { NavLinks } from '@/components/NavLinks'
|
||||
import qrCode from '@/images/qr-code.svg'
|
||||
|
||||
function QrCodeBorder(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 96 96" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M1 17V9a8 8 0 0 1 8-8h8M95 17V9a8 8 0 0 0-8-8h-8M1 79v8a8 8 0 0 0 8 8h8M95 79v8a8 8 0 0 1-8 8h-8"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-gray-200">
|
||||
<Container>
|
||||
<div className="flex flex-col items-start justify-between gap-y-12 pt-16 pb-6 lg:flex-row lg:items-center lg:py-16">
|
||||
<div>
|
||||
<div className="flex items-center text-gray-900">
|
||||
<Logomark className="h-10 w-10 flex-none fill-cyan-500" />
|
||||
<div className="ml-4">
|
||||
<p className="text-base font-semibold">Pocket</p>
|
||||
<p className="mt-1 text-sm">Invest at the perfect time.</p>
|
||||
</div>
|
||||
</div>
|
||||
<nav className="mt-11 flex gap-8">
|
||||
<NavLinks />
|
||||
</nav>
|
||||
</div>
|
||||
<div className="group relative -mx-4 flex items-center self-stretch p-4 transition-colors hover:bg-gray-100 sm:self-auto sm:rounded-2xl lg:mx-0 lg:self-auto lg:p-6">
|
||||
<div className="relative flex h-24 w-24 flex-none items-center justify-center">
|
||||
<QrCodeBorder className="absolute inset-0 h-full w-full stroke-gray-300 transition-colors group-hover:stroke-cyan-500" />
|
||||
<Image src={qrCode} alt="" unoptimized />
|
||||
</div>
|
||||
<div className="ml-8 lg:w-64">
|
||||
<p className="text-base font-semibold text-gray-900">
|
||||
<Link href="#">
|
||||
<span className="absolute inset-0 sm:rounded-2xl" />
|
||||
Download the app
|
||||
</Link>
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-gray-700">
|
||||
Scan the QR code to download the app from the App Store.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-center border-t border-gray-200 pt-8 pb-12 md:flex-row-reverse md:justify-between md:pt-6">
|
||||
<form className="flex w-full justify-center md:w-auto">
|
||||
<TextField
|
||||
type="email"
|
||||
aria-label="Email address"
|
||||
placeholder="Email address"
|
||||
autoComplete="email"
|
||||
required
|
||||
className="w-60 min-w-0 shrink"
|
||||
/>
|
||||
<Button type="submit" color="cyan" className="ml-4 flex-none">
|
||||
<span className="hidden lg:inline">Join our newsletter</span>
|
||||
<span className="lg:hidden">Join newsletter</span>
|
||||
</Button>
|
||||
</form>
|
||||
<p className="mt-6 text-sm text-gray-500 md:mt-0">
|
||||
© Copyright {new Date().getFullYear()}. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
</footer>
|
||||
)
|
||||
}
|
146
src/components/Header.tsx
Normal file
@ -0,0 +1,146 @@
|
||||
'use client'
|
||||
|
||||
import Link from 'next/link'
|
||||
import {
|
||||
Popover,
|
||||
PopoverButton,
|
||||
PopoverBackdrop,
|
||||
PopoverPanel,
|
||||
} from '@headlessui/react'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import { Logo } from '@/components/Logo'
|
||||
import { NavLinks } from '@/components/NavLinks'
|
||||
|
||||
function MenuIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M5 6h14M5 18h14M5 12h14"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function ChevronUpIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M17 14l-5-5-5 5"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function MobileNavLink(
|
||||
props: Omit<
|
||||
React.ComponentPropsWithoutRef<typeof PopoverButton<typeof Link>>,
|
||||
'as' | 'className'
|
||||
>,
|
||||
) {
|
||||
return (
|
||||
<PopoverButton
|
||||
as={Link}
|
||||
className="block text-base/7 tracking-tight text-gray-700"
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function Header() {
|
||||
return (
|
||||
<header>
|
||||
<nav>
|
||||
<Container className="relative z-50 flex justify-between py-8">
|
||||
<div className="relative z-10 flex items-center gap-16">
|
||||
<Link href="/" aria-label="Home">
|
||||
<Logo className="h-10 w-auto" />
|
||||
</Link>
|
||||
<div className="hidden lg:flex lg:gap-10">
|
||||
<NavLinks />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<Popover className="lg:hidden">
|
||||
{({ open }) => (
|
||||
<>
|
||||
<PopoverButton
|
||||
className="relative z-10 -m-2 inline-flex items-center rounded-lg stroke-gray-900 p-2 hover:bg-gray-200/50 hover:stroke-gray-600 focus:not-data-focus:outline-hidden active:stroke-gray-900"
|
||||
aria-label="Toggle site navigation"
|
||||
>
|
||||
{({ open }) =>
|
||||
open ? (
|
||||
<ChevronUpIcon className="h-6 w-6" />
|
||||
) : (
|
||||
<MenuIcon className="h-6 w-6" />
|
||||
)
|
||||
}
|
||||
</PopoverButton>
|
||||
<AnimatePresence initial={false}>
|
||||
{open && (
|
||||
<>
|
||||
<PopoverBackdrop
|
||||
static
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="fixed inset-0 z-0 bg-gray-300/60 backdrop-blur-sm"
|
||||
/>
|
||||
<PopoverPanel
|
||||
static
|
||||
as={motion.div}
|
||||
initial={{ opacity: 0, y: -32 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
y: -32,
|
||||
transition: { duration: 0.2 },
|
||||
}}
|
||||
className="absolute inset-x-0 top-0 z-0 origin-top rounded-b-2xl bg-gray-50 px-6 pt-32 pb-6 shadow-2xl shadow-gray-900/20"
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<MobileNavLink href="/#features">
|
||||
Features
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#reviews">
|
||||
Reviews
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#pricing">
|
||||
Pricing
|
||||
</MobileNavLink>
|
||||
<MobileNavLink href="/#faqs">FAQs</MobileNavLink>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<Button href="/login" variant="outline">
|
||||
Log in
|
||||
</Button>
|
||||
<Button href="#">Download the app</Button>
|
||||
</div>
|
||||
</PopoverPanel>
|
||||
</>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)}
|
||||
</Popover>
|
||||
<div className="flex items-center gap-6 max-lg:hidden">
|
||||
<Button href="/login" variant="outline">
|
||||
Log in
|
||||
</Button>
|
||||
<Button href="#">Download</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</nav>
|
||||
</header>
|
||||
)
|
||||
}
|
162
src/components/Hero.tsx
Normal file
@ -0,0 +1,162 @@
|
||||
import { useId } from 'react'
|
||||
import Image from 'next/image'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { AppDemo } from '@/components/AppDemo'
|
||||
import { AppStoreLink } from '@/components/AppStoreLink'
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import { PhoneFrame } from '@/components/PhoneFrame'
|
||||
import logoBbc from '@/images/logos/bbc.svg'
|
||||
import logoCbs from '@/images/logos/cbs.svg'
|
||||
import logoCnn from '@/images/logos/cnn.svg'
|
||||
import logoFastCompany from '@/images/logos/fast-company.svg'
|
||||
import logoForbes from '@/images/logos/forbes.svg'
|
||||
import logoHuffpost from '@/images/logos/huffpost.svg'
|
||||
import logoTechcrunch from '@/images/logos/techcrunch.svg'
|
||||
import logoWired from '@/images/logos/wired.svg'
|
||||
|
||||
function BackgroundIllustration(props: React.ComponentPropsWithoutRef<'div'>) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<div {...props}>
|
||||
<svg
|
||||
viewBox="0 0 1026 1026"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 h-full w-full animate-spin-slow"
|
||||
>
|
||||
<path
|
||||
d="M1025 513c0 282.77-229.23 512-512 512S1 795.77 1 513 230.23 1 513 1s512 229.23 512 512Z"
|
||||
stroke="#D4D4D4"
|
||||
strokeOpacity="0.7"
|
||||
/>
|
||||
<path
|
||||
d="M513 1025C230.23 1025 1 795.77 1 513"
|
||||
stroke={`url(#${id}-gradient-1)`}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`${id}-gradient-1`}
|
||||
x1="1"
|
||||
y1="513"
|
||||
x2="1"
|
||||
y2="1025"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#06b6d4" />
|
||||
<stop offset="1" stopColor="#06b6d4" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<svg
|
||||
viewBox="0 0 1026 1026"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
className="absolute inset-0 h-full w-full animate-spin-reverse-slower"
|
||||
>
|
||||
<path
|
||||
d="M913 513c0 220.914-179.086 400-400 400S113 733.914 113 513s179.086-400 400-400 400 179.086 400 400Z"
|
||||
stroke="#D4D4D4"
|
||||
strokeOpacity="0.7"
|
||||
/>
|
||||
<path
|
||||
d="M913 513c0 220.914-179.086 400-400 400"
|
||||
stroke={`url(#${id}-gradient-2)`}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`${id}-gradient-2`}
|
||||
x1="913"
|
||||
y1="513"
|
||||
x2="913"
|
||||
y2="913"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#06b6d4" />
|
||||
<stop offset="1" stopColor="#06b6d4" stopOpacity="0" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function PlayIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
|
||||
<circle cx="12" cy="12" r="11.5" stroke="#D4D4D4" />
|
||||
<path
|
||||
d="M9.5 14.382V9.618a.5.5 0 0 1 .724-.447l4.764 2.382a.5.5 0 0 1 0 .894l-4.764 2.382a.5.5 0 0 1-.724-.447Z"
|
||||
fill="#A3A3A3"
|
||||
stroke="#A3A3A3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<div className="overflow-hidden py-20 sm:py-32 lg:pb-32 xl:pb-36">
|
||||
<Container>
|
||||
<div className="lg:grid lg:grid-cols-12 lg:gap-x-8 lg:gap-y-20">
|
||||
<div className="relative z-10 mx-auto max-w-2xl lg:col-span-7 lg:max-w-none lg:pt-6 xl:col-span-6">
|
||||
<h1 className="text-4xl font-medium tracking-tight text-gray-900">
|
||||
Invest at the perfect time.
|
||||
</h1>
|
||||
<p className="mt-6 text-lg text-gray-600">
|
||||
By leveraging insights from our network of industry insiders,
|
||||
you’ll know exactly when to buy to maximize profit, and exactly
|
||||
when to sell to avoid painful losses.
|
||||
</p>
|
||||
<div className="mt-8 flex flex-wrap gap-x-6 gap-y-4">
|
||||
<AppStoreLink />
|
||||
<Button
|
||||
href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
||||
variant="outline"
|
||||
>
|
||||
<PlayIcon className="h-6 w-6 flex-none" />
|
||||
<span className="ml-2.5">Watch the video</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-10 sm:mt-20 lg:col-span-5 lg:row-span-2 lg:mt-0 xl:col-span-6">
|
||||
<BackgroundIllustration className="absolute top-4 left-1/2 h-[1026px] w-[1026px] -translate-x-1/3 mask-[linear-gradient(to_bottom,white_20%,transparent_75%)] stroke-gray-300/70 sm:top-16 sm:-translate-x-1/2 lg:-top-16 lg:ml-12 xl:-top-14 xl:ml-0" />
|
||||
<div className="-mx-4 h-[448px] mask-[linear-gradient(to_bottom,white_60%,transparent)] px-9 sm:mx-0 lg:absolute lg:-inset-x-10 lg:-top-10 lg:-bottom-20 lg:h-auto lg:px-0 lg:pt-10 xl:-bottom-32">
|
||||
<PhoneFrame className="mx-auto max-w-[366px]" priority>
|
||||
<AppDemo />
|
||||
</PhoneFrame>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative -mt-4 lg:col-span-7 lg:mt-0 xl:col-span-6">
|
||||
<p className="text-center text-sm font-semibold text-gray-900 lg:text-left">
|
||||
As featured in
|
||||
</p>
|
||||
<ul
|
||||
role="list"
|
||||
className="mx-auto mt-8 flex max-w-xl flex-wrap justify-center gap-x-10 gap-y-8 lg:mx-0 lg:justify-start"
|
||||
>
|
||||
{[
|
||||
['Forbes', logoForbes],
|
||||
['TechCrunch', logoTechcrunch],
|
||||
['Wired', logoWired],
|
||||
['CNN', logoCnn, 'hidden xl:block'],
|
||||
['BBC', logoBbc],
|
||||
['CBS', logoCbs],
|
||||
['Fast Company', logoFastCompany],
|
||||
['HuffPost', logoHuffpost, 'hidden xl:block'],
|
||||
].map(([name, logo, className]) => (
|
||||
<li key={name} className={clsx('flex', className)}>
|
||||
<Image src={logo} alt={name} className="h-8" unoptimized />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
)
|
||||
}
|
12
src/components/Layout.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { Header } from '@/components/Header'
|
||||
|
||||
export function Layout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main className="flex-auto">{children}</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
23
src/components/Logo.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
export function Logomark(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M20 40C8.954 40 0 31.046 0 20S8.954 0 20 0s20 8.954 20 20-8.954 20-20 20ZM4 20c0 7.264 5.163 13.321 12.02 14.704C17.642 35.03 19 33.657 19 32V8c0-1.657-1.357-3.031-2.98-2.704C9.162 6.68 4 12.736 4 20Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function Logo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 106 40" aria-hidden="true" {...props}>
|
||||
<Logomark width="40" height="40" className="fill-cyan-500" />
|
||||
<path
|
||||
className="fill-gray-900"
|
||||
d="M53.1477 26V14.3636H57.5114C58.4053 14.3636 59.1553 14.5303 59.7614 14.8636C60.3712 15.197 60.8314 15.6553 61.142 16.2386C61.4564 16.8182 61.6136 17.4773 61.6136 18.2159C61.6136 18.9621 61.4564 19.625 61.142 20.2045C60.8277 20.7841 60.3636 21.2405 59.75 21.5739C59.1364 21.9034 58.3807 22.0682 57.483 22.0682H54.5909V20.3352H57.1989C57.7216 20.3352 58.1496 20.2443 58.483 20.0625C58.8163 19.8807 59.0625 19.6307 59.2216 19.3125C59.3845 18.9943 59.4659 18.6288 59.4659 18.2159C59.4659 17.803 59.3845 17.4394 59.2216 17.125C59.0625 16.8106 58.8144 16.5663 58.4773 16.392C58.1439 16.214 57.714 16.125 57.1875 16.125H55.2557V26H53.1477ZM67.0355 26.1705C66.1832 26.1705 65.4446 25.983 64.8196 25.608C64.1946 25.233 63.7098 24.7083 63.3651 24.0341C63.0241 23.3598 62.8537 22.572 62.8537 21.6705C62.8537 20.7689 63.0241 19.9792 63.3651 19.3011C63.7098 18.6231 64.1946 18.0966 64.8196 17.7216C65.4446 17.3466 66.1832 17.1591 67.0355 17.1591C67.8878 17.1591 68.6264 17.3466 69.2514 17.7216C69.8764 18.0966 70.3594 18.6231 70.7003 19.3011C71.045 19.9792 71.2173 20.7689 71.2173 21.6705C71.2173 22.572 71.045 23.3598 70.7003 24.0341C70.3594 24.7083 69.8764 25.233 69.2514 25.608C68.6264 25.983 67.8878 26.1705 67.0355 26.1705ZM67.0469 24.5227C67.509 24.5227 67.8954 24.3958 68.206 24.142C68.5166 23.8845 68.7476 23.5398 68.8991 23.108C69.0545 22.6761 69.1321 22.1951 69.1321 21.6648C69.1321 21.1307 69.0545 20.6477 68.8991 20.2159C68.7476 19.7803 68.5166 19.4337 68.206 19.1761C67.8954 18.9186 67.509 18.7898 67.0469 18.7898C66.5734 18.7898 66.1795 18.9186 65.8651 19.1761C65.5545 19.4337 65.3215 19.7803 65.1662 20.2159C65.0147 20.6477 64.9389 21.1307 64.9389 21.6648C64.9389 22.1951 65.0147 22.6761 65.1662 23.108C65.3215 23.5398 65.5545 23.8845 65.8651 24.142C66.1795 24.3958 66.5734 24.5227 67.0469 24.5227ZM76.7699 26.1705C75.8987 26.1705 75.1506 25.9792 74.5256 25.5966C73.9044 25.214 73.4252 24.6856 73.0881 24.0114C72.7547 23.3333 72.5881 22.553 72.5881 21.6705C72.5881 20.7841 72.7585 20.0019 73.0994 19.3239C73.4403 18.642 73.9214 18.1117 74.5426 17.733C75.1676 17.3504 75.9063 17.1591 76.7585 17.1591C77.4669 17.1591 78.0938 17.2898 78.6392 17.5511C79.1884 17.8087 79.6259 18.1742 79.9517 18.6477C80.2775 19.1174 80.4631 19.6667 80.5085 20.2955H78.5426C78.4631 19.875 78.2737 19.5246 77.9744 19.2443C77.679 18.9602 77.2831 18.8182 76.7869 18.8182C76.3665 18.8182 75.9972 18.9318 75.679 19.1591C75.3608 19.3826 75.1127 19.7045 74.9347 20.125C74.7604 20.5455 74.6733 21.0492 74.6733 21.6364C74.6733 22.2311 74.7604 22.7424 74.9347 23.1705C75.1089 23.5947 75.3532 23.9223 75.6676 24.1534C75.9858 24.3807 76.3589 24.4943 76.7869 24.4943C77.09 24.4943 77.3608 24.4375 77.5994 24.3239C77.8419 24.2064 78.0445 24.0379 78.2074 23.8182C78.3703 23.5985 78.482 23.3314 78.5426 23.017H80.5085C80.4593 23.6345 80.2775 24.1818 79.9631 24.6591C79.6487 25.1326 79.2206 25.5038 78.679 25.7727C78.1373 26.0379 77.5009 26.1705 76.7699 26.1705ZM84.0724 23.2614L84.0668 20.7784H84.3963L87.5327 17.2727H89.9361L86.0781 21.5682H85.652L84.0724 23.2614ZM82.1974 26V14.3636H84.2543V26H82.1974ZM87.6747 26L84.8338 22.0284L86.2202 20.5795L90.1349 26H87.6747ZM94.8111 26.1705C93.9361 26.1705 93.1804 25.9886 92.544 25.625C91.9115 25.2576 91.4247 24.7386 91.0838 24.0682C90.7429 23.3939 90.5724 22.6004 90.5724 21.6875C90.5724 20.7898 90.7429 20.0019 91.0838 19.3239C91.4285 18.642 91.9096 18.1117 92.527 17.733C93.1444 17.3504 93.8698 17.1591 94.7031 17.1591C95.241 17.1591 95.7486 17.2462 96.2259 17.4205C96.7069 17.5909 97.1312 17.8561 97.4986 18.2159C97.8698 18.5758 98.1615 19.0341 98.3736 19.5909C98.5857 20.1439 98.6918 20.803 98.6918 21.5682V22.1989H91.5384V20.8125H96.7202C96.7164 20.4186 96.6312 20.0682 96.4645 19.7614C96.2978 19.4508 96.0649 19.2064 95.7656 19.0284C95.4702 18.8504 95.1255 18.7614 94.7315 18.7614C94.3111 18.7614 93.9418 18.8636 93.6236 19.0682C93.3054 19.2689 93.0573 19.5341 92.8793 19.8636C92.705 20.1894 92.616 20.5473 92.6122 20.9375V22.1477C92.6122 22.6553 92.705 23.0909 92.8906 23.4545C93.0762 23.8144 93.3357 24.0909 93.669 24.2841C94.0024 24.4735 94.3925 24.5682 94.8395 24.5682C95.1387 24.5682 95.4096 24.5265 95.652 24.4432C95.8944 24.3561 96.1046 24.2292 96.2827 24.0625C96.4607 23.8958 96.5952 23.6894 96.6861 23.4432L98.6065 23.6591C98.4853 24.1667 98.2543 24.6098 97.9134 24.9886C97.5762 25.3636 97.1444 25.6553 96.6179 25.8636C96.0914 26.0682 95.4891 26.1705 94.8111 26.1705ZM104.79 17.2727V18.8636H99.7727V17.2727H104.79ZM101.011 15.1818H103.068V23.375C103.068 23.6515 103.11 23.8636 103.193 24.0114C103.28 24.1553 103.394 24.2538 103.534 24.3068C103.674 24.3598 103.83 24.3864 104 24.3864C104.129 24.3864 104.246 24.3769 104.352 24.358C104.462 24.339 104.545 24.322 104.602 24.3068L104.949 25.9148C104.839 25.9527 104.682 25.9943 104.477 26.0398C104.277 26.0852 104.03 26.1117 103.739 26.1193C103.223 26.1345 102.759 26.0568 102.347 25.8864C101.934 25.7121 101.606 25.4432 101.364 25.0795C101.125 24.7159 101.008 24.2614 101.011 23.7159V15.1818Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
50
src/components/NavLinks.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { AnimatePresence, motion } from 'framer-motion'
|
||||
|
||||
export function NavLinks() {
|
||||
let [hoveredIndex, setHoveredIndex] = useState<number | null>(null)
|
||||
let timeoutRef = useRef<number | null>(null)
|
||||
|
||||
return [
|
||||
['Features', '/#features'],
|
||||
['Reviews', '/#reviews'],
|
||||
['Pricing', '/#pricing'],
|
||||
['FAQs', '/#faqs'],
|
||||
].map(([label, href], index) => (
|
||||
<Link
|
||||
key={label}
|
||||
href={href}
|
||||
className="relative -mx-3 -my-2 rounded-lg px-3 py-2 text-sm text-gray-700 transition-colors delay-150 hover:text-gray-900 hover:delay-0"
|
||||
onMouseEnter={() => {
|
||||
if (timeoutRef.current) {
|
||||
window.clearTimeout(timeoutRef.current)
|
||||
}
|
||||
setHoveredIndex(index)
|
||||
}}
|
||||
onMouseLeave={() => {
|
||||
timeoutRef.current = window.setTimeout(() => {
|
||||
setHoveredIndex(null)
|
||||
}, 200)
|
||||
}}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{hoveredIndex === index && (
|
||||
<motion.span
|
||||
className="absolute inset-0 rounded-lg bg-gray-100"
|
||||
layoutId="hoverBackground"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1, transition: { duration: 0.15 } }}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.15 },
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
<span className="relative z-10">{label}</span>
|
||||
</Link>
|
||||
))
|
||||
}
|
42
src/components/PhoneFrame.tsx
Normal file
@ -0,0 +1,42 @@
|
||||
import Image from 'next/image'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import frame from '@/images/phone-frame.svg'
|
||||
|
||||
function PlaceholderFrame(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 366 729" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fill="#F2F2F2"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M300.092 1c41.22 0 63.223 21.99 63.223 63.213V184.94c-.173.184-.329.476-.458.851.188-.282.404-.547.647-.791.844-.073 2.496.257 2.496 2.157V268.719c-.406 2.023-2.605 2.023-2.605 2.023a7.119 7.119 0 0 1-.08-.102v394.462c0 41.213-22.001 63.212-63.223 63.212h-95.074c-.881-.468-2.474-.795-4.323-.838l-33.704-.005-.049.001h-.231l-.141-.001c-2.028 0-3.798.339-4.745.843H66.751c-41.223 0-63.223-21.995-63.223-63.208V287.739c-.402-.024-2.165-.23-2.524-2.02v-.973A2.039 2.039 0 0 1 1 284.62v-47.611c0-.042.001-.084.004-.126v-.726c0-1.9 1.652-2.23 2.496-2.157l.028.028v-16.289c-.402-.024-2.165-.23-2.524-2.02v-.973A2.039 2.039 0 0 1 1 214.62v-47.611c0-.042.001-.084.004-.126v-.726c0-1.9 1.652-2.23 2.496-2.157l.028.028v-26.041a2.26 2.26 0 0 0 .093-.236l-.064-.01a3.337 3.337 0 0 1-.72-.12l-.166-.028A2 2 0 0 1 1 135.62v-24.611a2 2 0 0 1 1.671-1.973l.857-.143v-44.68C3.528 22.99 25.53 1 66.75 1h233.341ZM3.952 234.516a5.481 5.481 0 0 0-.229-.278c.082.071.159.163.228.278Zm89.99-206.304A4.213 4.213 0 0 0 89.727 24H56.864C38.714 24 24 38.708 24 56.852v618.296C24 693.292 38.714 708 56.864 708h250.272c18.15 0 32.864-14.708 32.864-32.852V56.852C340 38.708 325.286 24 307.136 24h-32.864a4.212 4.212 0 0 0-4.213 4.212v2.527c0 10.235-8.3 18.532-18.539 18.532H112.48c-10.239 0-18.539-8.297-18.539-18.532v-2.527Z"
|
||||
/>
|
||||
<rect x="154" y="29" width="56" height="5" rx="2.5" fill="#D4D4D4" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function PhoneFrame({
|
||||
className,
|
||||
children,
|
||||
priority = false,
|
||||
...props
|
||||
}: React.ComponentPropsWithoutRef<'div'> & { priority?: boolean }) {
|
||||
return (
|
||||
<div className={clsx('relative aspect-366/729', className)} {...props}>
|
||||
<div className="absolute inset-y-[calc(1/729*100%)] right-[calc(5/729*100%)] left-[calc(7/729*100%)] rounded-[calc(58/366*100%)/calc(58/729*100%)] shadow-2xl" />
|
||||
<div className="absolute top-[calc(23/729*100%)] left-[calc(23/366*100%)] grid h-[calc(686/729*100%)] w-[calc(318/366*100%)] transform grid-cols-1 overflow-hidden bg-gray-900 pt-[calc(23/318*100%)]">
|
||||
{children}
|
||||
</div>
|
||||
<PlaceholderFrame className="pointer-events-none absolute inset-0 h-full w-full fill-gray-100" />
|
||||
<Image
|
||||
src={frame}
|
||||
alt=""
|
||||
className="pointer-events-none absolute inset-0 h-full w-full"
|
||||
unoptimized
|
||||
priority={priority}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
288
src/components/Pricing.tsx
Normal file
@ -0,0 +1,288 @@
|
||||
'use client'
|
||||
|
||||
import { useState } from 'react'
|
||||
import { Radio, RadioGroup } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
|
||||
import { Button } from '@/components/Button'
|
||||
import { Container } from '@/components/Container'
|
||||
import { Logomark } from '@/components/Logo'
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: 'Starter',
|
||||
featured: false,
|
||||
price: { Monthly: '$0', Annually: '$0' },
|
||||
description:
|
||||
'You’re new to investing but want to do it right. Get started for free.',
|
||||
button: {
|
||||
label: 'Get started for free',
|
||||
href: '/register',
|
||||
},
|
||||
features: [
|
||||
'Commission-free trading',
|
||||
'Multi-layered encryption',
|
||||
'One tip every day',
|
||||
'Invest up to $1,500 each month',
|
||||
],
|
||||
logomarkClassName: 'fill-gray-300',
|
||||
},
|
||||
{
|
||||
name: 'Investor',
|
||||
featured: false,
|
||||
price: { Monthly: '$7', Annually: '$70' },
|
||||
description:
|
||||
'You’ve been investing for a while. Invest more and grow your wealth faster.',
|
||||
button: {
|
||||
label: 'Subscribe',
|
||||
href: '/register',
|
||||
},
|
||||
features: [
|
||||
'Commission-free trading',
|
||||
'Multi-layered encryption',
|
||||
'One tip every hour',
|
||||
'Invest up to $15,000 each month',
|
||||
'Basic transaction anonymization',
|
||||
],
|
||||
logomarkClassName: 'fill-gray-500',
|
||||
},
|
||||
{
|
||||
name: 'VIP',
|
||||
featured: true,
|
||||
price: { Monthly: '$199', Annually: '$1,990' },
|
||||
description:
|
||||
'You’ve got a huge amount of assets but it’s not enough. To the moon.',
|
||||
button: {
|
||||
label: 'Subscribe',
|
||||
href: '/register',
|
||||
},
|
||||
features: [
|
||||
'Commission-free trading',
|
||||
'Multi-layered encryption',
|
||||
'Real-time tip notifications',
|
||||
'No investment limits',
|
||||
'Advanced transaction anonymization',
|
||||
'Automated tax-loss harvesting',
|
||||
],
|
||||
logomarkClassName: 'fill-cyan-500',
|
||||
},
|
||||
]
|
||||
|
||||
function CheckIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" {...props}>
|
||||
<path
|
||||
d="M9.307 12.248a.75.75 0 1 0-1.114 1.004l1.114-1.004ZM11 15.25l-.557.502a.75.75 0 0 0 1.15-.043L11 15.25Zm4.844-5.041a.75.75 0 0 0-1.188-.918l1.188.918Zm-7.651 3.043 2.25 2.5 1.114-1.004-2.25-2.5-1.114 1.004Zm3.4 2.457 4.25-5.5-1.187-.918-4.25 5.5 1.188.918Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
<circle
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="8.25"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function Plan({
|
||||
name,
|
||||
price,
|
||||
description,
|
||||
button,
|
||||
features,
|
||||
activePeriod,
|
||||
logomarkClassName,
|
||||
featured = false,
|
||||
}: {
|
||||
name: string
|
||||
price: {
|
||||
Monthly: string
|
||||
Annually: string
|
||||
}
|
||||
description: string
|
||||
button: {
|
||||
label: string
|
||||
href: string
|
||||
}
|
||||
features: Array<string>
|
||||
activePeriod: 'Monthly' | 'Annually'
|
||||
logomarkClassName?: string
|
||||
featured?: boolean
|
||||
}) {
|
||||
return (
|
||||
<section
|
||||
className={clsx(
|
||||
'flex flex-col overflow-hidden rounded-3xl p-6 shadow-lg shadow-gray-900/5',
|
||||
featured ? 'order-first bg-gray-900 lg:order-none' : 'bg-white',
|
||||
)}
|
||||
>
|
||||
<h3
|
||||
className={clsx(
|
||||
'flex items-center text-sm font-semibold',
|
||||
featured ? 'text-white' : 'text-gray-900',
|
||||
)}
|
||||
>
|
||||
<Logomark className={clsx('h-6 w-6 flex-none', logomarkClassName)} />
|
||||
<span className="ml-4">{name}</span>
|
||||
</h3>
|
||||
<p
|
||||
className={clsx(
|
||||
'relative mt-5 flex text-3xl tracking-tight',
|
||||
featured ? 'text-white' : 'text-gray-900',
|
||||
)}
|
||||
>
|
||||
{price.Monthly === price.Annually ? (
|
||||
price.Monthly
|
||||
) : (
|
||||
<>
|
||||
<span
|
||||
aria-hidden={activePeriod === 'Annually'}
|
||||
className={clsx(
|
||||
'transition duration-300',
|
||||
activePeriod === 'Annually' &&
|
||||
'pointer-events-none translate-x-6 opacity-0 select-none',
|
||||
)}
|
||||
>
|
||||
{price.Monthly}
|
||||
</span>
|
||||
<span
|
||||
aria-hidden={activePeriod === 'Monthly'}
|
||||
className={clsx(
|
||||
'absolute top-0 left-0 transition duration-300',
|
||||
activePeriod === 'Monthly' &&
|
||||
'pointer-events-none -translate-x-6 opacity-0 select-none',
|
||||
)}
|
||||
>
|
||||
{price.Annually}
|
||||
</span>
|
||||
</>
|
||||
)}
|
||||
</p>
|
||||
<p
|
||||
className={clsx(
|
||||
'mt-3 text-sm',
|
||||
featured ? 'text-gray-300' : 'text-gray-700',
|
||||
)}
|
||||
>
|
||||
{description}
|
||||
</p>
|
||||
<div className="order-last mt-6">
|
||||
<ul
|
||||
role="list"
|
||||
className={clsx(
|
||||
'-my-2 divide-y text-sm',
|
||||
featured
|
||||
? 'divide-gray-800 text-gray-300'
|
||||
: 'divide-gray-200 text-gray-700',
|
||||
)}
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<li key={feature} className="flex py-2">
|
||||
<CheckIcon
|
||||
className={clsx(
|
||||
'h-6 w-6 flex-none',
|
||||
featured ? 'text-white' : 'text-cyan-500',
|
||||
)}
|
||||
/>
|
||||
<span className="ml-4">{feature}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
<Button
|
||||
href={button.href}
|
||||
color={featured ? 'cyan' : 'gray'}
|
||||
className="mt-6"
|
||||
aria-label={`Get started with the ${name} plan for ${price}`}
|
||||
>
|
||||
{button.label}
|
||||
</Button>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
export function Pricing() {
|
||||
let [activePeriod, setActivePeriod] = useState<'Monthly' | 'Annually'>(
|
||||
'Monthly',
|
||||
)
|
||||
|
||||
return (
|
||||
<section
|
||||
id="pricing"
|
||||
aria-labelledby="pricing-title"
|
||||
className="border-t border-gray-200 bg-gray-100 py-20 sm:py-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl text-center">
|
||||
<h2
|
||||
id="pricing-title"
|
||||
className="text-3xl font-medium tracking-tight text-gray-900"
|
||||
>
|
||||
Flat pricing, no management fees.
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
Whether you’re one person trying to get ahead or a big firm trying
|
||||
to take over the world, we’ve got a plan for you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex justify-center">
|
||||
<div className="relative">
|
||||
<RadioGroup
|
||||
value={activePeriod}
|
||||
onChange={setActivePeriod}
|
||||
className="grid grid-cols-2"
|
||||
>
|
||||
{['Monthly', 'Annually'].map((period) => (
|
||||
<Radio
|
||||
key={period}
|
||||
value={period}
|
||||
className={clsx(
|
||||
'cursor-pointer border border-gray-300 px-[calc(--spacing(3)-1px)] py-[calc(--spacing(2)-1px)] text-sm text-gray-700 transition-colors hover:border-gray-400 data-focus:outline-2 data-focus:outline-offset-2',
|
||||
period === 'Monthly'
|
||||
? 'rounded-l-lg'
|
||||
: '-ml-px rounded-r-lg',
|
||||
)}
|
||||
>
|
||||
{period}
|
||||
</Radio>
|
||||
))}
|
||||
</RadioGroup>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className={clsx(
|
||||
'pointer-events-none absolute inset-0 z-10 grid grid-cols-2 overflow-hidden rounded-lg bg-cyan-500 transition-all duration-300',
|
||||
activePeriod === 'Monthly'
|
||||
? '[clip-path:inset(0_50%_0_0)]'
|
||||
: '[clip-path:inset(0_0_0_calc(50%-1px))]',
|
||||
)}
|
||||
>
|
||||
{['Monthly', 'Annually'].map((period) => (
|
||||
<div
|
||||
key={period}
|
||||
className={clsx(
|
||||
'py-2 text-center text-sm font-semibold text-white',
|
||||
period === 'Annually' && '-ml-px',
|
||||
)}
|
||||
>
|
||||
{period}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-auto mt-16 grid max-w-2xl grid-cols-1 items-start gap-x-8 gap-y-10 sm:mt-20 lg:max-w-none lg:grid-cols-3">
|
||||
{plans.map((plan) => (
|
||||
<Plan key={plan.name} {...plan} activePeriod={activePeriod} />
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
598
src/components/PrimaryFeatures.tsx
Normal file
@ -0,0 +1,598 @@
|
||||
'use client'
|
||||
|
||||
import { Fragment, useEffect, useId, useRef, useState } from 'react'
|
||||
import { Tab, TabGroup, TabList, TabPanel, TabPanels } from '@headlessui/react'
|
||||
import clsx from 'clsx'
|
||||
import {
|
||||
type MotionProps,
|
||||
type Variant,
|
||||
type Variants,
|
||||
AnimatePresence,
|
||||
motion,
|
||||
} from 'framer-motion'
|
||||
import { useDebouncedCallback } from 'use-debounce'
|
||||
|
||||
import { AppScreen } from '@/components/AppScreen'
|
||||
import { CircleBackground } from '@/components/CircleBackground'
|
||||
import { Container } from '@/components/Container'
|
||||
import { PhoneFrame } from '@/components/PhoneFrame'
|
||||
import {
|
||||
DiageoLogo,
|
||||
LaravelLogo,
|
||||
MirageLogo,
|
||||
ReversableLogo,
|
||||
StatamicLogo,
|
||||
StaticKitLogo,
|
||||
TransistorLogo,
|
||||
TupleLogo,
|
||||
} from '@/components/StockLogos'
|
||||
|
||||
const MotionAppScreenHeader = motion(AppScreen.Header)
|
||||
const MotionAppScreenBody = motion(AppScreen.Body)
|
||||
|
||||
interface CustomAnimationProps {
|
||||
isForwards: boolean
|
||||
changeCount: number
|
||||
}
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'Invite friends for better returns',
|
||||
description:
|
||||
'For every friend you invite to Pocket, you get insider notifications 5 seconds sooner. And it’s 10 seconds if you invite an insider.',
|
||||
icon: DeviceUserIcon,
|
||||
screen: InviteScreen,
|
||||
},
|
||||
{
|
||||
name: 'Notifications on stock dips',
|
||||
description:
|
||||
'Get a push notification every time we find out something that’s going to lower the share price on your holdings so you can sell before the information hits the public markets.',
|
||||
icon: DeviceNotificationIcon,
|
||||
screen: StocksScreen,
|
||||
},
|
||||
{
|
||||
name: 'Invest what you want',
|
||||
description:
|
||||
'We hide your stock purchases behind thousands of anonymous trading accounts, so suspicious activity can never be traced back to you.',
|
||||
icon: DeviceTouchIcon,
|
||||
screen: InvestScreen,
|
||||
},
|
||||
]
|
||||
|
||||
function DeviceUserIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 23a3 3 0 100-6 3 3 0 000 6zm-1 2a4 4 0 00-4 4v1a2 2 0 002 2h6a2 2 0 002-2v-1a4 4 0 00-4-4h-2z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v24a4.002 4.002 0 01-3.01 3.877c-.535.136-.99-.325-.99-.877s.474-.98.959-1.244A2 2 0 0025 28V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 001.041 1.756C8.525 30.02 9 30.448 9 31s-.455 1.013-.99.877A4.002 4.002 0 015 28V4z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceNotificationIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
<path
|
||||
d="M9 8a2 2 0 012-2h10a2 2 0 012 2v2a2 2 0 01-2 2H11a2 2 0 01-2-2V8z"
|
||||
fill="#737373"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceTouchIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true" {...props}>
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`${id}-gradient`}
|
||||
x1={14}
|
||||
y1={14.5}
|
||||
x2={7}
|
||||
y2={17}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#737373" />
|
||||
<stop offset={1} stopColor="#D4D4D4" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v13h-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 002 2h4v2H9a4 4 0 01-4-4V4z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
<path
|
||||
d="M7 22c0-4.694 3.5-8 8-8"
|
||||
stroke={`url(#${id}-gradient)`}
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M21 20l.217-5.513a1.431 1.431 0 00-2.85-.226L17.5 21.5l-1.51-1.51a2.107 2.107 0 00-2.98 0 .024.024 0 00-.005.024l3.083 9.25A4 4 0 0019.883 32H25a4 4 0 004-4v-5a3 3 0 00-3-3h-5z"
|
||||
fill="#A3A3A3"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
const headerAnimation: Variants = {
|
||||
initial: { opacity: 0, transition: { duration: 0.3 } },
|
||||
animate: { opacity: 1, transition: { duration: 0.3, delay: 0.3 } },
|
||||
exit: { opacity: 0, transition: { duration: 0.3 } },
|
||||
}
|
||||
|
||||
const maxZIndex = 2147483647
|
||||
|
||||
const bodyVariantBackwards: Variant = {
|
||||
opacity: 0.4,
|
||||
scale: 0.8,
|
||||
zIndex: 0,
|
||||
filter: 'blur(4px)',
|
||||
transition: { duration: 0.4 },
|
||||
}
|
||||
|
||||
const bodyVariantForwards: Variant = (custom: CustomAnimationProps) => ({
|
||||
y: '100%',
|
||||
zIndex: maxZIndex - custom.changeCount,
|
||||
transition: { duration: 0.4 },
|
||||
})
|
||||
|
||||
const bodyAnimation: MotionProps = {
|
||||
initial: 'initial',
|
||||
animate: 'animate',
|
||||
exit: 'exit',
|
||||
variants: {
|
||||
initial: (custom: CustomAnimationProps, ...props) =>
|
||||
custom.isForwards
|
||||
? bodyVariantForwards(custom, ...props)
|
||||
: bodyVariantBackwards,
|
||||
animate: (custom: CustomAnimationProps) => ({
|
||||
y: '0%',
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
zIndex: maxZIndex / 2 - custom.changeCount,
|
||||
filter: 'blur(0px)',
|
||||
transition: { duration: 0.4 },
|
||||
}),
|
||||
exit: (custom: CustomAnimationProps, ...props) =>
|
||||
custom.isForwards
|
||||
? bodyVariantBackwards
|
||||
: bodyVariantForwards(custom, ...props),
|
||||
},
|
||||
}
|
||||
|
||||
type ScreenProps =
|
||||
| {
|
||||
animated: true
|
||||
custom: CustomAnimationProps
|
||||
}
|
||||
| { animated?: false }
|
||||
|
||||
function InviteScreen(props: ScreenProps) {
|
||||
return (
|
||||
<AppScreen className="w-full">
|
||||
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
|
||||
<AppScreen.Title>Invite people</AppScreen.Title>
|
||||
<AppScreen.Subtitle>
|
||||
Get tips <span className="text-white">5s sooner</span> for every
|
||||
invite.
|
||||
</AppScreen.Subtitle>
|
||||
</MotionAppScreenHeader>
|
||||
<MotionAppScreenBody
|
||||
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
|
||||
>
|
||||
<div className="px-4 py-6">
|
||||
<div className="space-y-6">
|
||||
{[
|
||||
{ label: 'Full name', value: 'Albert H. Wiggin' },
|
||||
{ label: 'Email address', value: 'awiggin@chase.com' },
|
||||
].map((field) => (
|
||||
<div key={field.label}>
|
||||
<div className="text-sm text-gray-500">{field.label}</div>
|
||||
<div className="mt-2 border-b border-gray-200 pb-2 text-sm text-gray-900">
|
||||
{field.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 rounded-lg bg-cyan-500 px-3 py-2 text-center text-sm font-semibold text-white">
|
||||
Invite person
|
||||
</div>
|
||||
</div>
|
||||
</MotionAppScreenBody>
|
||||
</AppScreen>
|
||||
)
|
||||
}
|
||||
|
||||
function StocksScreen(props: ScreenProps) {
|
||||
return (
|
||||
<AppScreen className="w-full">
|
||||
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
|
||||
<AppScreen.Title>Stocks</AppScreen.Title>
|
||||
<AppScreen.Subtitle>March 9, 2022</AppScreen.Subtitle>
|
||||
</MotionAppScreenHeader>
|
||||
<MotionAppScreenBody
|
||||
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
|
||||
>
|
||||
<div className="divide-y divide-gray-100">
|
||||
{[
|
||||
{
|
||||
name: 'Laravel',
|
||||
price: '4,098.01',
|
||||
change: '+4.98%',
|
||||
color: '#F9322C',
|
||||
logo: LaravelLogo,
|
||||
},
|
||||
{
|
||||
name: 'Tuple',
|
||||
price: '5,451.10',
|
||||
change: '-3.38%',
|
||||
color: '#5A67D8',
|
||||
logo: TupleLogo,
|
||||
},
|
||||
{
|
||||
name: 'Transistor',
|
||||
price: '4,098.41',
|
||||
change: '+6.25%',
|
||||
color: '#2A5B94',
|
||||
logo: TransistorLogo,
|
||||
},
|
||||
{
|
||||
name: 'Diageo',
|
||||
price: '250.65',
|
||||
change: '+1.25%',
|
||||
color: '#3320A7',
|
||||
logo: DiageoLogo,
|
||||
},
|
||||
{
|
||||
name: 'StaticKit',
|
||||
price: '250.65',
|
||||
change: '-3.38%',
|
||||
color: '#2A3034',
|
||||
logo: StaticKitLogo,
|
||||
},
|
||||
{
|
||||
name: 'Statamic',
|
||||
price: '5,040.85',
|
||||
change: '-3.11%',
|
||||
color: '#0EA5E9',
|
||||
logo: StatamicLogo,
|
||||
},
|
||||
{
|
||||
name: 'Mirage',
|
||||
price: '140.44',
|
||||
change: '+9.09%',
|
||||
color: '#16A34A',
|
||||
logo: MirageLogo,
|
||||
},
|
||||
{
|
||||
name: 'Reversable',
|
||||
price: '550.60',
|
||||
change: '-1.25%',
|
||||
color: '#8D8D8D',
|
||||
logo: ReversableLogo,
|
||||
},
|
||||
].map((stock) => (
|
||||
<div key={stock.name} className="flex items-center gap-4 px-4 py-3">
|
||||
<div
|
||||
className="flex-none rounded-full"
|
||||
style={{ backgroundColor: stock.color }}
|
||||
>
|
||||
<stock.logo className="h-10 w-10" />
|
||||
</div>
|
||||
<div className="flex-auto text-sm text-gray-900">
|
||||
{stock.name}
|
||||
</div>
|
||||
<div className="flex-none text-right">
|
||||
<div className="text-sm font-medium text-gray-900">
|
||||
{stock.price}
|
||||
</div>
|
||||
<div
|
||||
className={clsx(
|
||||
'text-xs/5',
|
||||
stock.change.startsWith('+')
|
||||
? 'text-cyan-500'
|
||||
: 'text-gray-500',
|
||||
)}
|
||||
>
|
||||
{stock.change}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</MotionAppScreenBody>
|
||||
</AppScreen>
|
||||
)
|
||||
}
|
||||
|
||||
function InvestScreen(props: ScreenProps) {
|
||||
return (
|
||||
<AppScreen className="w-full">
|
||||
<MotionAppScreenHeader {...(props.animated ? headerAnimation : {})}>
|
||||
<AppScreen.Title>Buy $LA</AppScreen.Title>
|
||||
<AppScreen.Subtitle>
|
||||
<span className="text-white">$34.28</span> per share
|
||||
</AppScreen.Subtitle>
|
||||
</MotionAppScreenHeader>
|
||||
<MotionAppScreenBody
|
||||
{...(props.animated ? { ...bodyAnimation, custom: props.custom } : {})}
|
||||
>
|
||||
<div className="px-4 py-6">
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ label: 'Number of shares', value: '100' },
|
||||
{
|
||||
label: 'Current market price',
|
||||
value: (
|
||||
<div className="flex">
|
||||
$34.28
|
||||
<svg viewBox="0 0 24 24" fill="none" className="h-6 w-6">
|
||||
<path
|
||||
d="M17 15V7H9M17 7 7 17"
|
||||
stroke="#06B6D4"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{ label: 'Estimated cost', value: '$3,428.00' },
|
||||
].map((item) => (
|
||||
<div
|
||||
key={item.label}
|
||||
className="flex justify-between border-b border-gray-100 pb-4"
|
||||
>
|
||||
<div className="text-sm text-gray-500">{item.label}</div>
|
||||
<div className="text-sm font-semibold text-gray-900">
|
||||
{item.value}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
<div className="rounded-lg bg-cyan-500 px-3 py-2 text-center text-sm font-semibold text-white">
|
||||
Buy shares
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</MotionAppScreenBody>
|
||||
</AppScreen>
|
||||
)
|
||||
}
|
||||
|
||||
function usePrevious<T>(value: T) {
|
||||
let ref = useRef<T>()
|
||||
|
||||
useEffect(() => {
|
||||
ref.current = value
|
||||
}, [value])
|
||||
|
||||
return ref.current
|
||||
}
|
||||
|
||||
function FeaturesDesktop() {
|
||||
let [changeCount, setChangeCount] = useState(0)
|
||||
let [selectedIndex, setSelectedIndex] = useState(0)
|
||||
let prevIndex = usePrevious(selectedIndex)
|
||||
let isForwards = prevIndex === undefined ? true : selectedIndex > prevIndex
|
||||
|
||||
let onChange = useDebouncedCallback(
|
||||
(selectedIndex) => {
|
||||
setSelectedIndex(selectedIndex)
|
||||
setChangeCount((changeCount) => changeCount + 1)
|
||||
},
|
||||
100,
|
||||
{ leading: true },
|
||||
)
|
||||
|
||||
return (
|
||||
<TabGroup
|
||||
className="grid grid-cols-12 items-center gap-8 lg:gap-16 xl:gap-24"
|
||||
selectedIndex={selectedIndex}
|
||||
onChange={onChange}
|
||||
vertical
|
||||
>
|
||||
<TabList className="relative z-10 order-last col-span-6 space-y-6">
|
||||
{features.map((feature, featureIndex) => (
|
||||
<div
|
||||
key={feature.name}
|
||||
className="relative rounded-2xl transition-colors hover:bg-gray-800/30"
|
||||
>
|
||||
{featureIndex === selectedIndex && (
|
||||
<motion.div
|
||||
layoutId="activeBackground"
|
||||
className="absolute inset-0 bg-gray-800"
|
||||
initial={{ borderRadius: 16 }}
|
||||
/>
|
||||
)}
|
||||
<div className="relative z-10 p-8">
|
||||
<feature.icon className="h-8 w-8" />
|
||||
<h3 className="mt-6 text-lg font-semibold text-white">
|
||||
<Tab className="text-left data-selected:not-data-focus:outline-hidden">
|
||||
<span className="absolute inset-0 rounded-2xl" />
|
||||
{feature.name}
|
||||
</Tab>
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-gray-400">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</TabList>
|
||||
<div className="relative col-span-6">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<CircleBackground color="#13B5C8" className="animate-spin-slower" />
|
||||
</div>
|
||||
<PhoneFrame className="z-10 mx-auto w-full max-w-[366px]">
|
||||
<TabPanels as={Fragment}>
|
||||
<AnimatePresence
|
||||
initial={false}
|
||||
custom={{ isForwards, changeCount }}
|
||||
>
|
||||
{features.map((feature, featureIndex) =>
|
||||
selectedIndex === featureIndex ? (
|
||||
<TabPanel
|
||||
static
|
||||
key={feature.name + changeCount}
|
||||
className="col-start-1 row-start-1 flex focus:outline-offset-32 data-selected:not-data-focus:outline-hidden"
|
||||
>
|
||||
<feature.screen
|
||||
animated
|
||||
custom={{ isForwards, changeCount }}
|
||||
/>
|
||||
</TabPanel>
|
||||
) : null,
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</TabPanels>
|
||||
</PhoneFrame>
|
||||
</div>
|
||||
</TabGroup>
|
||||
)
|
||||
}
|
||||
|
||||
function FeaturesMobile() {
|
||||
let [activeIndex, setActiveIndex] = useState(0)
|
||||
let slideContainerRef = useRef<React.ElementRef<'div'>>(null)
|
||||
let slideRefs = useRef<Array<React.ElementRef<'div'>>>([])
|
||||
|
||||
useEffect(() => {
|
||||
let observer = new window.IntersectionObserver(
|
||||
(entries) => {
|
||||
for (let entry of entries) {
|
||||
if (entry.isIntersecting && entry.target instanceof HTMLDivElement) {
|
||||
setActiveIndex(slideRefs.current.indexOf(entry.target))
|
||||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
root: slideContainerRef.current,
|
||||
threshold: 0.6,
|
||||
},
|
||||
)
|
||||
|
||||
for (let slide of slideRefs.current) {
|
||||
if (slide) {
|
||||
observer.observe(slide)
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
}, [slideContainerRef, slideRefs])
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
ref={slideContainerRef}
|
||||
className="-mb-4 flex snap-x snap-mandatory -space-x-4 overflow-x-auto overscroll-x-contain scroll-smooth pb-4 [scrollbar-width:none] sm:-space-x-6 [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{features.map((feature, featureIndex) => (
|
||||
<div
|
||||
key={featureIndex}
|
||||
ref={(ref) => ref && (slideRefs.current[featureIndex] = ref)}
|
||||
className="w-full flex-none snap-center px-4 sm:px-6"
|
||||
>
|
||||
<div className="relative transform overflow-hidden rounded-2xl bg-gray-800 px-5 py-6">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2">
|
||||
<CircleBackground
|
||||
color="#13B5C8"
|
||||
className={featureIndex % 2 === 1 ? 'rotate-180' : undefined}
|
||||
/>
|
||||
</div>
|
||||
<PhoneFrame className="relative mx-auto w-full max-w-[366px]">
|
||||
<feature.screen />
|
||||
</PhoneFrame>
|
||||
<div className="absolute inset-x-0 bottom-0 bg-gray-800/95 p-6 backdrop-blur-sm sm:p-10">
|
||||
<feature.icon className="h-8 w-8" />
|
||||
<h3 className="mt-6 text-sm font-semibold text-white sm:text-lg">
|
||||
{feature.name}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm text-gray-400">
|
||||
{feature.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-6 flex justify-center gap-3">
|
||||
{features.map((_, featureIndex) => (
|
||||
<button
|
||||
type="button"
|
||||
key={featureIndex}
|
||||
className={clsx(
|
||||
'relative h-0.5 w-4 rounded-full',
|
||||
featureIndex === activeIndex ? 'bg-gray-300' : 'bg-gray-500',
|
||||
)}
|
||||
aria-label={`Go to slide ${featureIndex + 1}`}
|
||||
onClick={() => {
|
||||
slideRefs.current[featureIndex].scrollIntoView({
|
||||
block: 'nearest',
|
||||
inline: 'nearest',
|
||||
})
|
||||
}}
|
||||
>
|
||||
<span className="absolute -inset-x-1.5 -inset-y-3" />
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export function PrimaryFeatures() {
|
||||
return (
|
||||
<section
|
||||
id="features"
|
||||
aria-label="Features for investing all your money"
|
||||
className="bg-gray-900 py-20 sm:py-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl lg:mx-0 lg:max-w-3xl">
|
||||
<h2 className="text-3xl font-medium tracking-tight text-white">
|
||||
Every feature you need to win. Try it for yourself.
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-400">
|
||||
Pocket was built for investors like you who play by their own rules
|
||||
and aren’t going to let SEC regulations get in the way of their
|
||||
dreams. If other investing tools are afraid to build it, Pocket has
|
||||
it.
|
||||
</p>
|
||||
</div>
|
||||
</Container>
|
||||
<div className="mt-16 md:hidden">
|
||||
<FeaturesMobile />
|
||||
</div>
|
||||
<Container className="hidden md:mt-20 md:block">
|
||||
<FeaturesDesktop />
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
294
src/components/Reviews.tsx
Normal file
@ -0,0 +1,294 @@
|
||||
'use client'
|
||||
|
||||
import { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import clsx from 'clsx'
|
||||
import { useInView } from 'framer-motion'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
interface Review {
|
||||
title: string
|
||||
body: string
|
||||
author: string
|
||||
rating: 1 | 2 | 3 | 4 | 5
|
||||
}
|
||||
|
||||
const reviews: Array<Review> = [
|
||||
{
|
||||
title: 'It really works.',
|
||||
body: 'I downloaded Pocket today and turned $5000 into $25,000 in half an hour.',
|
||||
author: 'CrazyInvestor',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'You need this app.',
|
||||
body: 'I didn’t understand the stock market at all before Pocket. I still don’t, but at least I’m rich now.',
|
||||
author: 'CluelessButRich',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'This shouldn’t be legal.',
|
||||
body: 'Pocket makes it so easy to win big in the stock market that I can’t believe it’s actually legal.',
|
||||
author: 'LivingDaDream',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Screw financial advisors.',
|
||||
body: 'I barely made any money investing in mutual funds. With Pocket, I’m doubling my net-worth every single month.',
|
||||
author: 'JordanBelfort1962',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'I love it!',
|
||||
body: 'I started providing insider information myself and now I get new insider tips every 5 minutes. I don’t even have time to act on all of them. New Lamborghini is being delivered next week!',
|
||||
author: 'MrBurns',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Too good to be true.',
|
||||
body: 'I was making money so fast with Pocket that it felt like a scam. But I sold my shares and withdrew the money and it’s really there, right in my bank account. This app is crazy!',
|
||||
author: 'LazyRich99',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Wish I could give 6 stars',
|
||||
body: 'This is literally the most important app you will ever download in your life. Get on this before it’s so popular that everyone else is getting these tips too.',
|
||||
author: 'SarahLuvzCash',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Bought an island.',
|
||||
body: 'Yeah, you read that right. Want your own island too? Get Pocket.',
|
||||
author: 'ScroogeMcduck',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'No more debt!',
|
||||
body: 'After 2 weeks of trading on Pocket I was debt-free. Why did I even go to school at all when Pocket exists?',
|
||||
author: 'BruceWayne',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'I’m 13 and I’m rich.',
|
||||
body: 'I love that with Pocket’s transaction anonymization I could sign up and start trading when I was 12 years old. I had a million dollars before I had armpit hair!',
|
||||
author: 'RichieRich',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Started an investment firm.',
|
||||
body: 'I charge clients a 3% management fee and just throw all their investments into Pocket. Easy money!',
|
||||
author: 'TheCountOfMonteChristo',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'It’s like a superpower.',
|
||||
body: 'Every tip Pocket has sent me has paid off. It’s like playing Blackjack but knowing exactly what card is coming next!',
|
||||
author: 'ClarkKent',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Quit my job.',
|
||||
body: 'I downloaded Pocket three days ago and quit my job today. I can’t believe no one else thought to build a stock trading app that works this way!',
|
||||
author: 'GeorgeCostanza',
|
||||
rating: 5,
|
||||
},
|
||||
{
|
||||
title: 'Don’t download this app',
|
||||
body: 'Unless you want to have the best life ever! I am literally writing this from a yacht.',
|
||||
author: 'JeffBezos',
|
||||
rating: 5,
|
||||
},
|
||||
]
|
||||
|
||||
function StarIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 20 20" aria-hidden="true" {...props}>
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function StarRating({ rating }: { rating: Review['rating'] }) {
|
||||
return (
|
||||
<div className="flex">
|
||||
{[...Array(5).keys()].map((index) => (
|
||||
<StarIcon
|
||||
key={index}
|
||||
className={clsx(
|
||||
'h-5 w-5',
|
||||
rating > index ? 'fill-cyan-500' : 'fill-gray-300',
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function Review({
|
||||
title,
|
||||
body,
|
||||
author,
|
||||
rating,
|
||||
className,
|
||||
...props
|
||||
}: Omit<React.ComponentPropsWithoutRef<'figure'>, keyof Review> & Review) {
|
||||
let animationDelay = useMemo(() => {
|
||||
let possibleAnimationDelays = ['0s', '0.1s', '0.2s', '0.3s', '0.4s', '0.5s']
|
||||
return possibleAnimationDelays[
|
||||
Math.floor(Math.random() * possibleAnimationDelays.length)
|
||||
]
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<figure
|
||||
className={clsx(
|
||||
'animate-fade-in rounded-3xl bg-white p-6 opacity-0 shadow-md shadow-gray-900/5',
|
||||
className,
|
||||
)}
|
||||
style={{ animationDelay }}
|
||||
{...props}
|
||||
>
|
||||
<blockquote className="text-gray-900">
|
||||
<StarRating rating={rating} />
|
||||
<p className="mt-4 text-lg/6 font-semibold before:content-['“'] after:content-['”']">
|
||||
{title}
|
||||
</p>
|
||||
<p className="mt-3 text-base/7">{body}</p>
|
||||
</blockquote>
|
||||
<figcaption className="mt-3 text-sm text-gray-600 before:content-['–_']">
|
||||
{author}
|
||||
</figcaption>
|
||||
</figure>
|
||||
)
|
||||
}
|
||||
|
||||
function splitArray<T>(array: Array<T>, numParts: number) {
|
||||
let result: Array<Array<T>> = []
|
||||
for (let i = 0; i < array.length; i++) {
|
||||
let index = i % numParts
|
||||
if (!result[index]) {
|
||||
result[index] = []
|
||||
}
|
||||
result[index].push(array[i])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function ReviewColumn({
|
||||
reviews,
|
||||
className,
|
||||
reviewClassName,
|
||||
msPerPixel = 0,
|
||||
}: {
|
||||
reviews: Array<Review>
|
||||
className?: string
|
||||
reviewClassName?: (reviewIndex: number) => string
|
||||
msPerPixel?: number
|
||||
}) {
|
||||
let columnRef = useRef<React.ElementRef<'div'>>(null)
|
||||
let [columnHeight, setColumnHeight] = useState(0)
|
||||
let duration = `${columnHeight * msPerPixel}ms`
|
||||
|
||||
useEffect(() => {
|
||||
if (!columnRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
let resizeObserver = new window.ResizeObserver(() => {
|
||||
setColumnHeight(columnRef.current?.offsetHeight ?? 0)
|
||||
})
|
||||
|
||||
resizeObserver.observe(columnRef.current)
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={columnRef}
|
||||
className={clsx('animate-marquee space-y-8 py-4', className)}
|
||||
style={{ '--marquee-duration': duration } as React.CSSProperties}
|
||||
>
|
||||
{reviews.concat(reviews).map((review, reviewIndex) => (
|
||||
<Review
|
||||
key={reviewIndex}
|
||||
aria-hidden={reviewIndex >= reviews.length}
|
||||
className={reviewClassName?.(reviewIndex % reviews.length)}
|
||||
{...review}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function ReviewGrid() {
|
||||
let containerRef = useRef<React.ElementRef<'div'>>(null)
|
||||
let isInView = useInView(containerRef, { once: true, amount: 0.4 })
|
||||
let columns = splitArray(reviews, 3)
|
||||
let column1 = columns[0]
|
||||
let column2 = columns[1]
|
||||
let column3 = splitArray(columns[2], 2)
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="relative -mx-4 mt-16 grid h-196 max-h-[150vh] grid-cols-1 items-start gap-8 overflow-hidden px-4 sm:mt-20 md:grid-cols-2 lg:grid-cols-3"
|
||||
>
|
||||
{isInView && (
|
||||
<>
|
||||
<ReviewColumn
|
||||
reviews={[...column1, ...column3.flat(), ...column2]}
|
||||
reviewClassName={(reviewIndex) =>
|
||||
clsx(
|
||||
reviewIndex >= column1.length + column3[0].length &&
|
||||
'md:hidden',
|
||||
reviewIndex >= column1.length && 'lg:hidden',
|
||||
)
|
||||
}
|
||||
msPerPixel={10}
|
||||
/>
|
||||
<ReviewColumn
|
||||
reviews={[...column2, ...column3[1]]}
|
||||
className="hidden md:block"
|
||||
reviewClassName={(reviewIndex) =>
|
||||
reviewIndex >= column2.length ? 'lg:hidden' : ''
|
||||
}
|
||||
msPerPixel={15}
|
||||
/>
|
||||
<ReviewColumn
|
||||
reviews={column3.flat()}
|
||||
className="hidden lg:block"
|
||||
msPerPixel={10}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<div className="pointer-events-none absolute inset-x-0 top-0 h-32 bg-linear-to-b from-gray-50" />
|
||||
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-32 bg-linear-to-t from-gray-50" />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function Reviews() {
|
||||
return (
|
||||
<section
|
||||
id="reviews"
|
||||
aria-labelledby="reviews-title"
|
||||
className="pt-20 pb-16 sm:pt-32 sm:pb-24"
|
||||
>
|
||||
<Container>
|
||||
<h2
|
||||
id="reviews-title"
|
||||
className="text-3xl font-medium tracking-tight text-gray-900 sm:text-center"
|
||||
>
|
||||
Everyone is changing their life with Pocket.
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-600 sm:text-center">
|
||||
Thousands of people have doubled their net-worth in the last 30 days.
|
||||
</p>
|
||||
<ReviewGrid />
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
226
src/components/SecondaryFeatures.tsx
Normal file
@ -0,0 +1,226 @@
|
||||
import { useId } from 'react'
|
||||
|
||||
import { Container } from '@/components/Container'
|
||||
|
||||
const features = [
|
||||
{
|
||||
name: 'Invest any amount',
|
||||
description:
|
||||
'Whether it’s $1 or $1,000,000, we can put your money to work for you.',
|
||||
icon: DeviceArrowIcon,
|
||||
},
|
||||
{
|
||||
name: 'Build a balanced portfolio',
|
||||
description:
|
||||
'Invest in different industries to find the most opportunities to win huge.',
|
||||
icon: DeviceCardsIcon,
|
||||
},
|
||||
{
|
||||
name: 'Trade in real-time',
|
||||
description:
|
||||
'Get insider tips on big stock moves and act on them within seconds.',
|
||||
icon: DeviceClockIcon,
|
||||
},
|
||||
{
|
||||
name: 'Profit from your network',
|
||||
description:
|
||||
'Invite new insiders to get tips faster and beat even other Pocket users.',
|
||||
icon: DeviceListIcon,
|
||||
},
|
||||
{
|
||||
name: 'Encrypted and anonymized',
|
||||
description:
|
||||
'Cutting-edge security technology that even the NSA doesn’t know about keeps you hidden.',
|
||||
icon: DeviceLockIcon,
|
||||
},
|
||||
{
|
||||
name: 'Portfolio tracking',
|
||||
description:
|
||||
'Watch your investments grow exponentially, leaving other investors in the dust.',
|
||||
icon: DeviceChartIcon,
|
||||
},
|
||||
]
|
||||
|
||||
function DeviceArrowIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
d="M12 25l8-8m0 0h-6m6 0v6"
|
||||
stroke="#171717"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="round"
|
||||
/>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceCardsIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
let id = useId()
|
||||
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 13a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H10a1 1 0 01-1-1v-2zm0 6a1 1 0 011-1h12a1 1 0 011 1v2a1 1 0 01-1 1H10a1 1 0 01-1-1v-2zm1 5a1 1 0 00-1 1v2a1 1 0 001 1h12a1 1 0 001-1v-2a1 1 0 00-1-1H10z"
|
||||
fill={`url(#${id}-gradient)`}
|
||||
/>
|
||||
<rect x={9} y={6} width={14} height={4} rx={1} fill="#171717" />
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<defs>
|
||||
<linearGradient
|
||||
id={`${id}-gradient`}
|
||||
x1={16}
|
||||
y1={12}
|
||||
x2={16}
|
||||
y2={28}
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stopColor="#737373" />
|
||||
<stop offset={1} stopColor="#737373" stopOpacity={0} />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceClockIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v10h-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 002 2h5v2H9a4 4 0 01-4-4V4z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M24 32a8 8 0 100-16 8 8 0 000 16zm1-8.414V19h-2v5.414l4 4L28.414 27 25 23.586z"
|
||||
fill="#171717"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceListIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<circle cx={11} cy={14} r={2} fill="#171717" />
|
||||
<circle cx={11} cy={20} r={2} fill="#171717" />
|
||||
<circle cx={11} cy={26} r={2} fill="#171717" />
|
||||
<path
|
||||
d="M16 14h6M16 20h6M16 26h6"
|
||||
stroke="#737373"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="square"
|
||||
/>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceLockIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" aria-hidden="true" {...props}>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M5 4a4 4 0 014-4h14a4 4 0 014 4v10h-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9a2 2 0 00-2 2v24a2 2 0 002 2h5v2H9a4 4 0 01-4-4V4z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M18 19.5a3.5 3.5 0 117 0V22a2 2 0 012 2v6a2 2 0 01-2 2h-7a2 2 0 01-2-2v-6a2 2 0 012-2v-2.5zm2 2.5h3v-2.5a1.5 1.5 0 00-3 0V22z"
|
||||
fill="#171717"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
function DeviceChartIcon(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9 0a4 4 0 00-4 4v24a4 4 0 004 4h14a4 4 0 004-4V4a4 4 0 00-4-4H9zm0 2a2 2 0 00-2 2v24a2 2 0 002 2h14a2 2 0 002-2V4a2 2 0 00-2-2h-1.382a1 1 0 00-.894.553l-.448.894a1 1 0 01-.894.553h-6.764a1 1 0 01-.894-.553l-.448-.894A1 1 0 0010.382 2H9z"
|
||||
fill="#737373"
|
||||
/>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M23 13.838V26a2 2 0 01-2 2H11a2 2 0 01-2-2V15.65l2.57 3.212a1 1 0 001.38.175L15.4 17.2a1 1 0 011.494.353l1.841 3.681c.399.797 1.562.714 1.843-.13L23 13.837z"
|
||||
fill="#171717"
|
||||
/>
|
||||
<path
|
||||
d="M10 12h12"
|
||||
stroke="#737373"
|
||||
strokeWidth={2}
|
||||
strokeLinecap="square"
|
||||
/>
|
||||
<circle cx={16} cy={16} r={16} fill="#A3A3A3" fillOpacity={0.2} />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function SecondaryFeatures() {
|
||||
return (
|
||||
<section
|
||||
id="secondary-features"
|
||||
aria-label="Features for building a portfolio"
|
||||
className="py-20 sm:py-32"
|
||||
>
|
||||
<Container>
|
||||
<div className="mx-auto max-w-2xl sm:text-center">
|
||||
<h2 className="text-3xl font-medium tracking-tight text-gray-900">
|
||||
Now is the time to build your portfolio.
|
||||
</h2>
|
||||
<p className="mt-2 text-lg text-gray-600">
|
||||
With typical market returns, you have to start young to secure your
|
||||
future. With Pocket, it’s never too late to build your nest egg.
|
||||
</p>
|
||||
</div>
|
||||
<ul
|
||||
role="list"
|
||||
className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-6 text-sm sm:mt-20 sm:grid-cols-2 md:gap-y-10 lg:max-w-none lg:grid-cols-3"
|
||||
>
|
||||
{features.map((feature) => (
|
||||
<li
|
||||
key={feature.name}
|
||||
className="rounded-2xl border border-gray-200 p-8"
|
||||
>
|
||||
<feature.icon className="h-8 w-8" />
|
||||
<h3 className="mt-6 font-semibold text-gray-900">
|
||||
{feature.name}
|
||||
</h3>
|
||||
<p className="mt-2 text-gray-700">{feature.description}</p>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Container>
|
||||
</section>
|
||||
)
|
||||
}
|
100
src/components/StockLogos.tsx
Normal file
@ -0,0 +1,100 @@
|
||||
export function LaravelLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M29.982 14.509c.002.005.006.01.007.015a.316.316 0 0 1 .011.082v4.293a.304.304 0 0 1-.043.156.32.32 0 0 1-.119.115l-3.709 2.075v4.112a.305.305 0 0 1-.043.157.32.32 0 0 1-.119.114l-7.742 4.33a.286.286 0 0 1-.056.023l-.022.008a.33.33 0 0 1-.18-.005l-.01-.005c-.018-.006-.036-.011-.053-.021l-7.742-4.33a.32.32 0 0 1-.119-.115.304.304 0 0 1-.043-.156v-12.88a.33.33 0 0 1 .01-.08c.004-.01.01-.018.012-.027l.01-.027a.158.158 0 0 1 .011-.022c.006-.01.015-.018.023-.028.009-.012.017-.025.028-.036.01-.01.02-.016.031-.024.012-.009.023-.019.036-.026l3.871-2.165a.33.33 0 0 1 .322 0l3.872 2.165c.013.007.024.017.036.026l.01.008c.008.005.015.01.021.016a.175.175 0 0 1 .021.025l.008.011.022.028c.008.015.014.032.02.049l.006.01.006.016a.307.307 0 0 1 .01.082v8.044l3.227-1.804v-4.112c0-.028.004-.055.011-.082.003-.01.008-.017.011-.026l.004-.01a.228.228 0 0 1 .017-.039.132.132 0 0 1 .013-.018.203.203 0 0 0 .01-.01c.009-.012.017-.025.028-.036l.015-.013.016-.01.019-.016a.126.126 0 0 1 .017-.011l3.871-2.165a.33.33 0 0 1 .322 0l3.871 2.165c.014.007.024.018.036.026l.012.008.02.016a.162.162 0 0 1 .02.026l.009.01.008.01c.005.006.01.012.013.018a.254.254 0 0 1 .018.04l.003.009.005.01Zm-15.138 8.717 3.22 1.77 7.094-3.933-3.223-1.803-7.091 3.966Zm10.64-2.704v-3.57l-3.226-1.804v3.57l3.225 1.804Zm3.547-5.916-3.225-1.803-3.224 1.803 3.224 1.803 3.225-1.803Zm-14.515.218v7.863l3.226-1.805V13.02l-3.226 1.804Zm2.902-2.346-3.225-1.803-3.224 1.803 3.224 1.803 3.225-1.803Zm-3.547 2.347-3.226-1.805v12.155l7.098 3.97V25.54l-3.708-2.038h-.001l-.002-.002c-.013-.008-.024-.018-.035-.027a.28.28 0 0 0-.011-.007.133.133 0 0 1-.02-.015v-.001l-.019-.022a.452.452 0 0 0-.008-.011l-.016-.02a.086.086 0 0 1-.008-.01v-.002a.123.123 0 0 1-.013-.027l-.005-.012-.008-.016a.115.115 0 0 1-.007-.02.18.18 0 0 1-.005-.033l-.002-.013a.293.293 0 0 0-.002-.013l-.002-.022v-8.405Zm4.516 10.715v3.605l7.096-3.969v-3.572l-7.096 3.935Zm7.742-5.019 3.226-1.804v-3.57l-3.226 1.805v3.57Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function TupleLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M22.5 8 12 11.692v12l3.5 1.231v3.385L26 32V12.615l-3.5 1.231V8Zm-5.833 17.334 5.833 2.05v-12.24l2.333-.82v15.968l-8.166-2.87v-2.088Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function TransistorLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path d="M20 32c-6.617 0-12-5.383-12-12S13.383 8 20 8s12 5.383 12 12-5.383 12-12 12Zm0-22.4C14.267 9.6 9.6 14.266 9.6 20S14.267 30.4 20 30.4c5.734 0 10.4-4.666 10.4-10.4S25.734 9.6 20 9.6Z" />
|
||||
<path d="M19.434 27.749c.15.15.354.234.566.235.433 0 .8-.368.8-.8V12.815a.8.8 0 0 0-1.6 0v14.368c0 .212.084.415.234.565ZM12.833 20.8h3.833a.802.802 0 0 0 .802-.8.8.8 0 0 0-.801-.8h-3.834c-.45 0-.8.35-.8.8a.8.8 0 0 0 .8.8ZM23.333 20.8h3.85c.433 0 .783-.35.783-.8a.799.799 0 0 0-.8-.8h-3.833c-.45 0-.8.35-.8.8a.8.8 0 0 0 .8.8Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function DiageoLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 40 40"
|
||||
fill="#fff"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M22.16 19 26 13H14l3.84 6" fill="none" />
|
||||
<path d="M25 24a5 5 0 1 1-10 0 5 5 0 0 1 10 0Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function StaticKitLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path d="m26.068 10.555-11.49 13.089L12 21.089 23.489 8l2.58 2.555ZM28 18.91 16.512 32l-2.579-2.555 11.489-13.089L28 18.911Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function StatamicLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M30.177 27.293c0 1.921-.644 2.707-2.398 2.707H12.22c-1.754 0-2.397-.786-2.397-2.707v-3.741c0-1.805-.837-2.824-1.642-3.291a.385.385 0 0 1-.133-.143.403.403 0 0 1 .133-.526c.837-.551 1.642-1.704 1.642-3.241v-3.677c0-2.072.547-2.674 2.3-2.674h15.754c1.754 0 2.3.602 2.3 2.674v3.675c0 1.537.805 2.69 1.641 3.24.243.168.243.52 0 .67-.804.484-1.64 1.503-1.64 3.29v3.743h-.001Zm-14.739-2.455c1.271 1.152 2.64 1.737 4.522 1.737 2.96 0 4.891-1.537 4.891-4.026 0-2.637-2.3-3.31-4.17-3.856-1.282-.375-2.363-.691-2.363-1.54 0-.551.564-1.086 1.513-1.086.917 0 1.674.2 2.397.584.242.117.467.2.676.2.306 0 .547-.15.756-.45l.29-.451a.955.955 0 0 0 .161-.55c0-.336-.161-.67-.402-.837-.966-.635-2.27-1.17-4.039-1.17-2.51 0-4.44 1.37-4.44 3.826 0 2.746 2.349 3.443 4.23 4h.001c1.255.372 2.3.681 2.3 1.497 0 .785-.707 1.17-1.592 1.17a5.19 5.19 0 0 1-2.992-.92c-.274-.183-.532-.3-.805-.3-.242 0-.451.117-.644.368l-.387.517a.888.888 0 0 0-.192.585c0 .25.08.501.29.702Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function MirageLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg viewBox="0 0 40 40" fill="#fff" aria-hidden="true" {...props}>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M24.05 9c2.307 0 4.177 1.885 4.177 4.21a4.21 4.21 0 0 1-2.762 3.964l3.366 6.057h2.304c.355 0 .642.29.642.647a.645.645 0 0 1-.642.647H7.142a.645.645 0 0 1-.642-.647c0-.358.288-.647.643-.647h2.304l5.994-10.747a.641.641 0 0 1 1.097-.036l3.444 5.32 1.071-1.627a4.214 4.214 0 0 1-1.178-2.93c0-2.326 1.87-4.211 4.176-4.211Zm-3.304 9.948 2.772 4.283h3.84l-4.317-7.769-2.295 3.486Zm1.239 4.283-5.944-9.183-5.121 9.183h11.065Zm5.038-10.02a2.995 2.995 0 0 1-2.159 2.883l-1.216-2.19a.64.64 0 0 0-1.096-.04l-.811 1.232a3 3 0 0 1-.663-1.885c0-1.655 1.332-2.997 2.973-2.997 1.641 0 2.972 1.341 2.972 2.997Z"
|
||||
/>
|
||||
<path d="M12.069 26.469c-.354 0-.641.289-.641.646 0 .358.287.646.64.646h14.139c.354 0 .641-.29.641-.646a.644.644 0 0 0-.64-.646h-14.14Zm4.928 3.236a.645.645 0 0 0-.642.648c0 .357.288.647.642.647h4.282c.355 0 .643-.29.643-.647a.645.645 0 0 0-.643-.648h-4.282Z" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
|
||||
export function ReversableLogo(props: React.ComponentPropsWithoutRef<'svg'>) {
|
||||
return (
|
||||
<svg
|
||||
viewBox="0 0 40 40"
|
||||
fill="none"
|
||||
stroke="#fff"
|
||||
strokeWidth="2"
|
||||
strokeLinecap="square"
|
||||
strokeLinejoin="round"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path d="M15 26v-5.25m0 0V16a2 2 0 0 1 2-2h4.21c.968 0 1.37 1.24.587 1.809L15 20.75Zm0 0L25 26" />
|
||||
</svg>
|
||||
)
|
||||
}
|
5
src/images/logos/bbc.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="83" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M0 15.531v11.531h24.771V4H0v11.531Zm29.042 0v11.531h24.771V4H29.041v11.531Zm29.041 0v11.531h24.771V4H58.083v11.531ZM14.807 7.817c.979.444 1.988 1.197 2.242 1.67.254.475.461 1.43.461 2.125 0 .694-.364 1.713-.81 2.263l-.81 1 1.548 1.34c1.307 1.13 1.548 1.608 1.548 3.079 0 1.25-.29 2.049-1.026 2.834-.565.6-1.714 1.283-2.553 1.516-.84.233-3.208.425-5.264.426l-3.737.003V6.99l3.31.009c2.32.006 3.843.251 5.09.818Zm28.763-.134c1.045.418 1.982 1.173 2.358 1.9.343.665.624 1.677.624 2.25 0 .572-.365 1.492-.81 2.042-.81 1-.81 1 .68 2.29 1.18 1.02 1.5 1.621 1.535 2.888.028 1-.281 2.041-.827 2.776-.48.647-1.526 1.406-2.326 1.686s-3.233.52-5.406.534l-3.95.024V6.99h3.194c1.952 0 3.87.27 4.928.693Zm32.237-.116c1.566.47 1.72.65 1.844 2.148.1 1.222-.007 1.577-.427 1.402-.31-.128-1.59-.551-2.846-.94-1.556-.484-2.896-.618-4.206-.421-1.325.198-2.407.732-3.482 1.717-1.464 1.342-1.56 1.588-1.56 3.997 0 2.26.151 2.725 1.257 3.88.692.721 1.94 1.501 2.776 1.734.836.232 2.096.418 2.8.414.705-.004 2.387-.463 3.738-1.018l2.455-1.01v1.592c0 1.318-.202 1.688-1.174 2.148-.646.306-2.667.65-4.492.765-2.484.156-3.79.027-5.204-.512-1.038-.397-2.562-1.468-3.387-2.38-.824-.913-1.715-2.46-1.978-3.44-.264-.98-.36-2.496-.211-3.37.148-.874.624-2.175 1.06-2.892.436-.717 1.434-1.781 2.217-2.366.783-.584 2.001-1.264 2.706-1.51.704-.246 2.434-.448 3.844-.449 1.409 0 3.33.23 4.27.511ZM9.48 12.007l.13 2.03 1.685-.057c1.182-.039 1.915-.34 2.456-1.008.424-.523.77-1.11.77-1.301 0-.192-.301-.651-.67-1.02-.411-.411-1.415-.672-2.586-.672H9.349l.13 2.029Zm29.042 0 .13 2.03 1.594-.048c.876-.027 1.886-.29 2.242-.587.357-.296.648-.956.648-1.468 0-.511-.23-1.16-.512-1.442-.282-.282-1.35-.513-2.372-.513h-1.86l.13 2.029ZM9.48 19.056l.128 2.242 2.45-.05c1.347-.026 2.74-.29 3.096-.585.356-.296.647-.983.647-1.529 0-.545-.427-1.29-.95-1.655-.57-.4-1.855-.666-3.225-.666H9.351l.13 2.243Zm29.041 0c.123 2.136.189 2.249 1.403 2.387.7.08 1.995-.072 2.878-.337 1.184-.354 1.68-.776 1.887-1.604.217-.864.06-1.305-.683-1.907-.672-.543-1.674-.782-3.29-.782h-2.324l.13 2.243Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.1 KiB |
5
src/images/logos/cbs.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="101" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M15.072 1C6.675 1 0 7.855 0 16.205c0 8.44 6.676 15.297 15.072 15.297 8.44 0 15.026-6.855 15.026-15.295C30.099 7.857 23.51 1 15.072 1Zm0 6.454c6.855 0 11.876 4.353 13.998 8.753-2.122 4.467-7.143 8.841-13.998 8.841-6.9 0-11.881-4.375-14.002-8.84 2.12-4.4 7.104-8.751 14.002-8.751v-.003Zm0 .692c-4.466 0-8.107 3.614-8.107 8.059a8.092 8.092 0 0 0 8.107 8.106c4.465 0 8.058-3.595 8.058-8.104a8.055 8.055 0 0 0-8.058-8.061ZM44.506 2.81c-7.883 0-13.376 5.983-13.376 13.261v.088c0 7.368 5.606 13.198 13.177 13.198 4.934 0 7.882-1.765 10.517-4.601L51.23 21.14c-2.01 1.83-3.822 3.016-6.747 3.016-4.398 0-7.457-3.689-7.457-8.086v-.066c0-4.399 3.126-7.995 7.457-7.995 2.568 0 4.578 1.094 6.566 2.904l3.595-4.155c-2.389-2.344-5.291-3.95-10.137-3.95ZM56.951 3.254v25.653h12.213c5.782 0 9.6-2.341 9.6-7.029v-.09c0-3.438-1.829-5.16-4.801-6.297 1.832-1.026 3.372-2.633 3.372-5.536V9.89c0-1.767-.581-3.195-1.765-4.378-1.472-1.45-3.772-2.257-6.698-2.257h-11.92Zm5.49 4.934h5.584c2.39 0 3.705.96 3.705 2.635v.09c0 1.898-1.585 2.702-4.085 2.702l-5.203.002V8.188Zm0 10.119h6.545c2.878 0 4.173 1.073 4.173 2.792v.087c0 1.898-1.517 2.768-3.995 2.768l-6.722.003v-5.65ZM89.833 2.876c-5.224 0-8.974 3.08-8.974 7.745v.069c0 5.093 3.347 6.523 8.506 7.84 4.287 1.115 5.179 1.829 5.179 3.258v.09c0 1.495-1.408 2.412-3.707 2.412-2.948 0-5.36-1.209-7.66-3.106L79.83 25.18c3.081 2.747 7.01 4.088 10.896 4.088v-.003c5.537 0 9.421-2.86 9.421-7.946v-.067c0-4.487-2.946-6.344-8.149-7.705-4.42-1.14-5.538-1.697-5.538-3.372v-.066c0-1.25 1.139-2.255 3.305-2.255 2.166 0 4.4.959 6.678 2.542l2.924-4.26c-2.59-2.077-5.781-3.261-9.533-3.261Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.7 KiB |
11
src/images/logos/cnn.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="68" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M5.155 15.807c0 5.795 4.715 10.51 10.51 10.51h9.817c.617 0 1.095-.586 1.095-1.09V5.966a2.21 2.21 0 0 1 2.206-2.207c.78 0 1.508.414 1.897 1.08a91573.874 91573.874 0 0 1 12.332 21.263.918.918 0 0 0 1.701-.476V5.968c0-1.217.99-2.207 2.206-2.207.78 0 1.508.414 1.897 1.08.05.084 3.02 5.208 6.164 10.634l6.16 10.63a.918.918 0 0 0 1.7-.476V.138h-3.87V15.31S51.96 3.243 51.734 2.855C50.709 1.105 48.768 0 46.703 0a5.861 5.861 0 0 0-5.86 5.86v9.45s-7.01-12.067-7.238-12.455C32.582 1.105 30.641 0 28.575 0a5.861 5.861 0 0 0-5.86 5.86V21.39c.003.565-.422 1.057-1.04 1.059h-5.968a6.64 6.64 0 0 1 0-13.282h4.998V5.297h-5.04c-5.795 0-10.51 4.715-10.51 10.51Z"
|
||||
fill="#737373" />
|
||||
<path
|
||||
d="M64.13.137v25.49c0 1.217-.989 2.207-2.206 2.207-.78 0-1.508-.414-1.897-1.08-.05-.084-3.02-5.209-6.163-10.634l-6.16-10.63a.918.918 0 0 0-1.7.475v19.662c-.001 1.217-.99 2.207-2.208 2.207-.78 0-1.507-.414-1.897-1.08L35.732 16.12 29.567 5.49a.917.917 0 0 0-1.7.475v19.26c0 1.269-1.115 2.381-2.385 2.381h-9.816c-6.507 0-11.8-5.293-11.8-11.8 0-6.506 5.293-11.8 11.8-11.8h5.04V.136H15.67C7.016.137 0 7.153 0 15.807c0 8.655 7.016 15.67 15.67 15.67h9.911c3.754.002 6.169-2.198 6.164-6.255v-8.938s7.06 12.153 7.238 12.456a5.86 5.86 0 0 0 10.89-3.006v-9.45s7.01 12.068 7.238 12.456c1.023 1.75 2.964 2.855 5.03 2.855A5.86 5.86 0 0 0 68 25.734V.136h-3.87Z"
|
||||
fill="#737373" />
|
||||
<path
|
||||
d="M3.865 15.807c0 6.507 5.294 11.8 11.8 11.8h9.817c1.27 0 2.384-1.112 2.384-2.38V5.966a.917.917 0 0 1 1.7-.475c.05.084 3.159 5.445 6.165 10.629L41.9 26.755a2.202 2.202 0 0 0 1.897 1.08 2.21 2.21 0 0 0 2.207-2.207V5.967a.917.917 0 0 1 1.7-.475l6.16 10.629 6.163 10.634a2.201 2.201 0 0 0 1.897 1.08c1.218 0 2.207-.99 2.207-2.207V.138h-1.29v25.49a.918.918 0 0 1-1.7.475l-6.16-10.629c-3.145-5.426-6.114-10.55-6.164-10.634a2.201 2.201 0 0 0-1.897-1.08 2.21 2.21 0 0 0-2.206 2.207v19.66a.918.918 0 0 1-1.701.476A344293.41 344293.41 0 0 1 30.68 4.84a2.201 2.201 0 0 0-1.897-1.08 2.21 2.21 0 0 0-2.207 2.207v19.26c0 .504-.478 1.09-1.094 1.09h-9.817c-5.795 0-10.51-4.715-10.51-10.51s4.715-10.51 10.51-10.51h5.04v-1.29h-5.04c-6.506 0-11.8 5.294-11.8 11.8Z"
|
||||
fill="#fff" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
5
src/images/logos/fast-company.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="124" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M58.787 11.975c0 2.488-.249 10.2-.249 10.2 0 1.492.498 2.154 1.907 2.154v.663H54.89v-.663c1.409 0 2.155-.662 2.155-2.155l.498-12.602c-.415-.996-.83-1.327-1.907-1.41v-.58h4.228l4.478 11.774 4.561-11.774h3.814v.58c-1.078 0-1.658.498-1.658 1.658l.498 12.189c0 1.824.331 2.322 1.824 2.322v.662h-6.385v-.662c1.244 0 1.825-.415 1.741-2.322l-.331-10.033-3.067 8.457a31.599 31.599 0 0 0-1.162 3.649l-.994.083c-.249-.912-.912-2.57-.912-2.57l-3.484-9.62Zm-5.224 13.1c-.332-.083-.663-.083-.995-.083-.663 0-1.825.332-3.234.332-5.39 0-9.37-3.233-9.37-9.203 0-5.058 3.732-8.955 9.452-8.955 1.327 0 2.82.25 4.064.747.248-.25.33-.415.58-.663h.497c-.083.912-.414 3.897-.414 3.897h-.498c-.332-1.741-1.244-2.902-4.146-2.902-3.482 0-6.633 2.736-6.633 7.794 0 5.721 3.234 8.208 6.799 8.208 2.322 0 3.73-1.326 4.643-2.985l.58.25-1.326 3.564ZM37.89 10.816h-.58c-.083-1.576-.58-2.074-1.824-2.074h-2.405V21.76c0 2.155.415 2.57 1.825 2.57v.663h-6.219v-.663c1.41 0 1.825-.415 1.825-2.654V8.741h-2.405c-1.575 0-1.825.415-2.24 2.073h-.579l.415-3.731.414.083c.166.414.498.414 1.493.414h8.043c1.493 0 1.824 0 2.156-.497h.498l-.416 3.73v.001ZM20.314 25.406c-1.161 0-2.073-.166-2.736-.415-.663-.165-1.161-.414-1.41-.58-.331.331-.58.746-.58.746l-.498-.166.415-3.897.58.084c.332 1.824 1.575 3.15 4.146 3.15 1.99 0 3.316-1.658 3.316-3.482 0-1.492-.58-2.24-2.57-3.732l-1.742-1.16c-1.824-1.16-2.985-2.488-2.985-4.643 0-2.488 2.073-4.147 4.81-4.147 1.574 0 2.57.498 2.902.663l.58-.663.414.084-.415 3.565-.58-.083c0-1.658-.995-2.404-2.653-2.404-1.575 0-2.736.83-2.736 2.404 0 1.41 1.078 2.322 2.321 3.15l1.658 1.079c2.902 2.072 3.566 3.399 3.566 5.389 0 2.901-2.238 5.057-5.803 5.057v.001Zm-9.452-14.593H10.2c0-1.493-.663-2.073-1.99-2.073H4.48v6.55h2.404c1.243 0 1.493-.662 1.658-1.74h.58v4.643h-.58c-.165-1.244-.415-1.741-1.742-1.741H4.48v5.556c0 2.072.497 2.322 1.824 2.322v.662H0v-.662c1.575 0 1.824-.415 1.824-2.322V9.819c0-1.16-.498-1.658-1.824-1.658v-.58h8.623c1.492 0 1.824-.084 2.073-.581h.58l-.415 3.814h.001Zm89.465.995v9.95c0 2.155.496 2.57 1.823 2.57v.663h-9.783v-.663c.747 0 1.409-.25 1.409-.747 0-.332-.083-.912-.249-1.493l-.747-2.404H87.31c-.084.332-1.078 3.067-1.078 3.814 0 .663.747.83 1.243.83v.663h-4.56v-.663c.829 0 1.492-.084 2.072-1.824l4.23-13.516c-.25-.58-.581-.745-1.16-.83v-.58h3.73l4.476 14.18c.416 1.491.83 2.238 1.493 2.569.996-.331 1.245-.663 1.245-2.57V9.82c-.747-.994-.83-1.658-2.24-1.658v-.58h4.229l6.964 11.855V10.4c0-1.824-.413-2.239-1.824-2.239v-.58h9.286v.58c-.58 0-1.077.249-1.077.83 0 .414.166.828.331 1.326l2.654 5.722 2.571-5.555c.413-.746.498-1.16.498-1.576 0-.497-.334-.746-1.078-.746v-.58h4.394v.58c-1.078 0-1.743.995-2.239 2.154l-3.4 6.966v4.56c0 1.99.498 2.487 1.825 2.487v.663h-6.302v-.663c1.326 0 1.824-.496 1.824-2.487v-3.897l-3.649-7.711c-.662-1.492-.911-1.823-1.327-2.073-.828.331-1.077.912-1.077 2.239v12.769l.166 2.155-1.078.083-8.125-13.598h.001Zm-10.364-.83-2.323 7.547h4.81l-2.488-7.546ZM79.68 18.526h-1.243v3.565c0 1.824.496 2.239 1.907 2.239v.663h-6.385v-.663c1.327 0 1.825-.497 1.825-2.321V10.317c0-1.823-.498-2.155-1.825-2.155v-.58h4.809c4.643 0 7.379 1.409 7.379 5.223 0 3.98-3.316 5.721-6.467 5.721Zm-.83-9.784h-.413v8.54h.745c2.737 0 4.147-1.245 4.147-4.395 0-3.15-1.244-4.145-4.478-4.145ZM50.744 21.676c-3.15 0-5.306-2.24-5.306-5.472 0-3.068 2.24-5.472 5.306-5.472 2.986 0 5.224 2.322 5.224 5.472 0 3.067-2.322 5.472-5.224 5.472Zm0-9.95c-2.155 0-2.902 2.073-2.902 4.478 0 2.24.664 4.477 2.902 4.477 2.156 0 2.82-2.24 2.82-4.56 0-2.238-.83-4.395-2.82-4.395Zm-36.481 8.955v-.58c.83 0 .995-.248.995-.497 0-.333-.083-.581-.166-.83 0 0-.167-.664-.332-1.16h-3.234l-.331 1.078c-.083.248-.166.497-.166.828 0 .331.332.58.83.58v.581H8.624v-.58c.83 0 1.161-.248 1.492-1.16l2.488-7.463c-.083-.332-.332-.58-.83-.58v-.58h2.653l2.736 7.96c.415 1.408.83 1.823 1.492 1.823v.58h-4.393ZM13.1 12.638l-1.243 4.063h2.57l-1.326-4.063Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 3.9 KiB |
5
src/images/logos/forbes.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="82" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M49.928 11.88c-1.318 0-2.354.286-3.39.854L46.632 6 40.32 7.138v.57l.66.094c.846.19 1.13.664 1.317 1.803.19 2.277.094 14.42 0 16.412 1.696.38 3.485.665 5.276.665 4.993 0 8.008-3.132 8.008-8.064 0-3.89-2.45-6.736-5.653-6.736v-.001Zm-2.166 13.757c-.377 0-.848 0-1.13-.095-.095-1.328-.189-6.925-.095-11.858.566-.19.941-.285 1.413-.285 2.075 0 3.205 2.467 3.205 5.502 0 3.796-1.414 6.736-3.392 6.736ZM16.58 6.378H0v.854l.942.095c1.224.19 1.696.948 1.884 2.75.283 3.416.189 9.583 0 12.523-.189 1.803-.659 2.657-1.884 2.75L0 25.54v.76h10.645v-.76l-1.13-.19c-1.226-.094-1.697-.948-1.884-2.75a77.793 77.793 0 0 1-.19-5.692l2.262.095c1.413 0 2.072 1.139 2.354 2.75h.849V13.02h-.849c-.283 1.613-.941 2.75-2.354 2.75l-2.26.096c0-3.226.094-6.262.189-8.064h3.296c2.545 0 3.864 1.612 4.805 4.459l.942-.285-.094-5.598h-.001Zm5.37 5.313c4.71 0 7.065 3.226 7.065 7.495 0 4.079-2.638 7.495-7.348 7.495s-7.065-3.227-7.065-7.495c0-4.08 2.638-7.495 7.349-7.495h-.001Zm-.283.949c-2.073 0-2.638 2.846-2.638 6.546 0 3.604.942 6.545 2.826 6.545 2.166 0 2.731-2.846 2.731-6.545 0-3.605-.94-6.546-2.92-6.546h.001Zm35.138 6.64c0-3.889 2.449-7.589 7.253-7.589 3.955 0 5.84 2.942 5.84 6.83h-8.76c-.095 3.51 1.6 6.073 4.992 6.073 1.508 0 2.26-.379 3.204-1.044l.376.474c-.943 1.328-3.014 2.657-5.652 2.657-4.24-.001-7.253-3.036-7.253-7.4Zm4.332-1.802 4.428-.095c0-1.897-.283-4.743-1.883-4.743-1.601 0-2.45 2.656-2.544 4.838Zm19.974-5.028c-1.13-.475-2.638-.759-4.334-.759-3.485 0-5.653 2.087-5.653 4.554s1.6 3.51 3.864 4.27c2.355.852 3.014 1.517 3.014 2.655 0 1.139-.848 2.182-2.355 2.182-1.79 0-3.11-1.043-4.145-3.89l-.66.19.095 4.175c1.13.474 3.202.855 4.993.855 3.674 0 6.03-1.899 6.03-4.839 0-1.993-1.038-3.13-3.487-4.08-2.638-1.043-3.579-1.707-3.579-2.94 0-1.234.85-2.088 1.98-2.088 1.695 0 2.826 1.043 3.675 3.606l.659-.19-.096-3.7h-.001Zm-39.85-.379c-1.6-.948-4.427-.474-5.934 2.942l.093-3.321-6.31 1.233v.57l.66.095c.848.095 1.223.57 1.319 1.803.188 2.277.093 6.261 0 8.253-.095 1.138-.47 1.707-1.32 1.802l-.66.095v.759h8.761v-.759l-1.13-.095c-.942-.095-1.224-.665-1.32-1.802-.188-1.803-.188-5.408-.093-7.684.47-.665 2.543-1.233 4.427 0l1.508-3.89Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
5
src/images/logos/huffpost.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="142" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M18.359 24.268h5.186l2.78-15.914H21.14l-1.026 5.7h-4.496l1.025-5.7h-5.186l-2.78 15.914h5.186l1.045-5.955h4.496l-1.045 5.955ZM29.264 8.335l-1.578 8.854c-.138.69-.197 1.36-.197 1.992 0 4.693 4.102 5.403 6.567 5.403 5.166 0 7.395-1.716 8.203-6.35l1.736-9.919h-5.187l-1.479 8.322c-.473 2.603-.808 3.964-2.74 3.964-1.263 0-1.874-.67-1.874-2.051 0-.533.079-1.183.237-1.992l1.498-8.243h-5.186v.02ZM71.404 24.268h5.187l.73-4.101h2.444c4.516 0 7.02-2.446 7.02-6.902 0-3.136-2.169-4.93-5.974-4.93h-6.626l-2.78 15.933Zm8.085-8.026h-1.498l.69-3.746h1.4c1.144 0 1.755.572 1.755 1.617 0 1.321-.888 2.13-2.347 2.13ZM96.862 8c-5.64 0-9.011 3.648-9.011 9.78 0 4.26 2.681 6.824 7.177 6.824 5.64 0 9.012-3.648 9.012-9.781.02-4.28-2.662-6.823-7.178-6.823Zm-1.518 12.482c-1.34 0-2.13-.966-2.13-2.583 0-.611.06-1.144.178-1.755.394-2.09 1.065-4.003 3.175-4.003 1.34 0 2.13.966 2.13 2.583 0 .612-.06 1.144-.178 1.755-.395 2.07-1.065 4.003-3.175 4.003ZM122.083 24.268h5.186l2.051-11.654h3.904l.75-4.26h-13.272l-.749 4.26h4.141l-2.011 11.654ZM113.485 14.231c-1.636-.512-2.307-.73-2.307-1.498 0-.513.335-1.124 1.321-1.124.73 0 1.341.414 1.578 1.025l4.575-1.242C118.119 9.144 116.187 8 112.854 8c-6.27 0-6.763 4.2-6.763 5.482 0 2.603 1.38 4.2 4.377 5.029.789.216 1.696.453 1.696 1.32 0 .69-.513 1.125-1.4 1.125-.808 0-1.676-.474-1.972-1.302l-4.516 1.223c.493 2.366 2.722 3.707 6.212 3.707 2.662 0 7.119-.71 7.119-5.521.019-2.406-1.341-3.984-4.122-4.832ZM5.778 24.268l2.8-15.914H0v15.914h5.778ZM136.202 8.354l-2.78 15.914H142V8.354h-5.798ZM50.186 19.575h4.358l.71-4.003h-4.358l.493-2.958h6.093l.75-4.279h-11.28l-2.78 15.933h5.186l.828-4.693ZM63.773 19.575h4.378l.71-4.003h-4.358l.493-2.958h6.093l.75-4.279h-11.28l-2.78 15.933h5.185l.809-4.693Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.8 KiB |
5
src/images/logos/techcrunch.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="181" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M66.825 12.77v11.34h-4.157V12.77H58.48V9.14h12.54v3.63h-4.195Zm13.89 6.85h-6.632a1.77 1.77 0 0 0 1.904 1.803 5.853 5.853 0 0 0 2.911-.793l1.507 2.506a8.69 8.69 0 0 1-4.528 1.293c-3.398 0-5.241-2.405-5.241-5.852 0-3.878 2.186-5.875 5.132-5.875 2.945 0 5.002 2.041 5.002 6.215-.044.272-.044.51-.056.704h.001Zm-5.099-4.117c-.884 0-1.37.601-1.495 1.724h3.206c-.193-1.145-.578-1.723-1.711-1.723Zm12.257 8.88c-3.296 0-5.256-2.098-5.256-5.897 0-3.402 1.745-5.83 5.347-5.83A4.676 4.676 0 0 1 92.28 15.2l-2.651 2.038c-.532-.975-.895-1.452-1.733-1.452-.837 0-1.47.986-1.47 2.766s.533 2.704 1.587 2.704c.725 0 1.23-.409 1.948-1.463l2.436 1.906c-1.31 1.94-2.526 2.688-4.52 2.688l-.004-.003Zm13.142-.238V17.76c0-1.463-.488-1.849-1.28-1.849-.793 0-1.311.386-1.311 1.804v6.43h-3.851V10.071l3.85-1.543v5.115a4.348 4.348 0 0 1 2.833-.986c2.462 0 3.637 1.656 3.637 4.604v6.884h-3.878Zm13.095.239c-4.453 0-6.797-3.3-6.797-7.735 0-4.82 2.832-7.734 6.797-7.734 3.681 0 5.131 1.577 6.162 4.41l-3.761 1.471c-.544-1.39-1.053-2.268-2.424-2.268-1.734 0-2.482 1.736-2.482 4.117 0 2.382.726 4.106 2.527 4.106 1.302 0 1.858-.704 2.673-2.121l3.513 1.871a6.446 6.446 0 0 1-6.209 3.878l.001.005Zm15.701-7.247a2.411 2.411 0 0 0-1.79-.942c-.94 0-1.517.453-1.517 1.826v6.124h-3.852V12.93h3.852v.816a3.394 3.394 0 0 1 4.213-.578l-.906 3.97Zm9.165 7.009v-.749a4.259 4.259 0 0 1-2.81.987c-2.462 0-3.636-1.656-3.636-4.617v-6.839h3.863v6.35c0 1.453.498 1.838 1.291 1.838.793 0 1.28-.385 1.28-1.792v-6.396h3.874v11.25l-3.862-.033Zm13.095 0V17.76c0-1.463-.499-1.849-1.28-1.849-.782 0-1.311.386-1.311 1.804v6.43h-3.851V12.93h3.851v.749a4.349 4.349 0 0 1 2.833-.987c2.461 0 3.636 1.656 3.636 4.604v6.884l-3.878-.033Zm11.385.238c-3.296 0-5.268-2.098-5.268-5.897 0-3.403 1.756-5.83 5.347-5.83a4.714 4.714 0 0 1 4.327 2.54l-2.65 2.007c-.545-.975-.907-1.452-1.734-1.452s-1.484.987-1.484 2.767c0 1.78.533 2.703 1.586 2.703.725 0 1.246-.408 1.949-1.463l2.435 1.905c-1.302 1.973-2.526 2.722-4.509 2.722l.001-.003v.001Zm13.13-.238V17.76c0-1.463-.488-1.849-1.281-1.849-.792 0-1.302.386-1.302 1.804v6.43h-3.851V10.071l3.851-1.543v5.115a4.296 4.296 0 0 1 2.821-.986c2.47 0 3.637 1.656 3.637 4.604v6.884h-3.875ZM0 3v8.54h8.519v17.066h8.53V11.539h8.518V3H0Zm34.096 17.067V11.54h-8.53v17.067h25.578v-8.539H34.096Zm0-17.066h17.048v8.538H34.096V3.001Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
5
src/images/logos/wired.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg width="121" height="32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M101.512 7.74v16.645h8.198c2.291 0 3.617-.362 4.582-1.207 1.206-1.085 1.808-3.015 1.808-7.116 0-4.102-.602-6.031-1.808-7.117-.965-.844-2.291-1.206-4.582-1.206h-8.198Zm11.213 8.322c0 3.618-.241 4.463-.965 4.945-.482.362-1.085.482-2.29.482h-4.582V10.513h4.582c1.205 0 1.808 0 2.29.483.724.603.965 1.447.965 5.066Zm7.595 12.002H96.268V4h24.052v24.064Zm-43.703-17.55V7.739h14.226v5.308h-3.015v-2.534h-5.787v3.981h4.582v2.654H82.04v4.463h5.909v-2.895h3.013v5.669H76.618V21.61h2.29V10.513h-2.29Zm-12.9 9.528c0 1.81.12 3.136.36 4.222h3.257c-.122-.844-.241-2.412-.241-4.463-.122-2.412-.845-2.774-2.533-3.136 1.929-.362 2.774-1.206 2.774-4.222 0-2.412-.363-3.377-1.086-3.98-.482-.482-1.325-.724-2.653-.724H53.468v16.646h3.376V17.87h4.703c.965 0 1.325.121 1.688.362s.482.604.482 1.81Zm-6.873-4.825v-4.583h5.426c.724 0 .965.12 1.084.241.241.241.483.603.483 2.05 0 1.448-.242 1.93-.483 2.172-.119.12-.36.24-1.084.24l-5.426-.12Zm15.312 12.847H48.044V4h24.052v24.064h.06Zm-30.38-6.453v2.774H30.32V21.61h4.099V10.513h-4.1V7.74h11.454v2.775h-4.1V21.61h4.101ZM22.423 7.739H19.29L17 20.887 14.346 8.704c-.12-.844-.482-.965-1.206-.965h-1.688c-.723 0-1.085.241-1.205.965L7.595 20.887 5.305 7.739H1.929l3.255 15.802c.12.723.362.844 1.206.844h2.29c.724 0 .965-.12 1.207-.845l2.532-11.458L14.95 23.54c.12.723.361.844 1.205.844h2.17c.724 0 1.085-.12 1.206-.845l2.894-15.8h-.002Zm1.688 20.325H0V4h24.052v24.064h.06Z"
|
||||
fill="#737373" />
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
231
src/images/phone-frame.svg
Normal file
@ -0,0 +1,231 @@
|
||||
<svg width="366" height="729" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g mask="url(#mask)">
|
||||
<g filter="url(#a)">
|
||||
<path
|
||||
d="M363.315 64.213C363.315 22.99 341.312 1 300.092 1H66.751C25.53 1 3.528 22.99 3.528 64.213v44.68l-.857.143A2 2 0 0 0 1 111.009v24.611a2 2 0 0 0 1.671 1.973l.95.158a2.26 2.26 0 0 1-.093.236v26.173c.212.1.398.296.541.643l-1.398.233A2 2 0 0 0 1 167.009v47.611a2 2 0 0 0 1.671 1.973l1.368.228c-.139.319-.314.533-.511.653v16.637c.221.104.414.313.56.689l-1.417.236A2 2 0 0 0 1 237.009v47.611a2 2 0 0 0 1.671 1.973l1.347.225c-.135.294-.302.493-.49.607v377.681c0 41.213 22 63.208 63.223 63.208h95.074c.947-.504 2.717-.843 4.745-.843l.141.001h.194l.086-.001 33.704.005c1.849.043 3.442.37 4.323.838h95.074c41.222 0 63.223-21.999 63.223-63.212v-394.63c-.259-.275-.48-.796-.63-1.47l-.011-.133 1.655-.276A2 2 0 0 0 366 266.62v-77.611a2 2 0 0 0-1.671-1.973l-1.712-.285c.148-.839.396-1.491.698-1.811V64.213Z"
|
||||
fill="url(#b)" />
|
||||
<path
|
||||
d="M363.315 64.213C363.315 22.99 341.312 1 300.092 1H66.751C25.53 1 3.528 22.99 3.528 64.213v44.68l-.857.143A2 2 0 0 0 1 111.009v24.611a2 2 0 0 0 1.671 1.973l.95.158a2.26 2.26 0 0 1-.093.236v26.173c.212.1.398.296.541.643l-1.398.233A2 2 0 0 0 1 167.009v47.611a2 2 0 0 0 1.671 1.973l1.368.228c-.139.319-.314.533-.511.653v16.637c.221.104.414.313.56.689l-1.417.236A2 2 0 0 0 1 237.009v47.611a2 2 0 0 0 1.671 1.973l1.347.225c-.135.294-.302.493-.49.607v377.681c0 41.213 22 63.208 63.223 63.208h95.074c.947-.504 2.717-.843 4.745-.843l.141.001h.194l.086-.001 33.704.005c1.849.043 3.442.37 4.323.838h95.074c41.222 0 63.223-21.999 63.223-63.212v-394.63c-.259-.275-.48-.796-.63-1.47l-.011-.133 1.655-.276A2 2 0 0 0 366 266.62v-77.611a2 2 0 0 0-1.671-1.973l-1.712-.285c.148-.839.396-1.491.698-1.811V64.213Z"
|
||||
fill="url(#c)" />
|
||||
</g>
|
||||
<g filter="url(#d)">
|
||||
<path
|
||||
d="M5 133.772v-21.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v24.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 133.772Z"
|
||||
fill="url(#e)" />
|
||||
<path
|
||||
d="M5 133.772v-21.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v24.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 133.772Z"
|
||||
fill="url(#f)" fill-opacity=".1" />
|
||||
</g>
|
||||
<g filter="url(#g)">
|
||||
<path
|
||||
d="M5 213.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 213.772Z"
|
||||
fill="url(#h)" />
|
||||
<path
|
||||
d="M5 213.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 213.772Z"
|
||||
fill="url(#i)" fill-opacity=".1" />
|
||||
</g>
|
||||
<g filter="url(#j)">
|
||||
<path
|
||||
d="M5 283.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 283.772Z"
|
||||
fill="url(#k)" />
|
||||
<path
|
||||
d="M5 283.772v-46.15c0-1.359-.54-2.661-1.5-3.622-.844-.073-2.496.257-2.496 2.157v49.562c.406 2.023 2.605 2.023 2.605 2.023A6.363 6.363 0 0 0 5 283.772Z"
|
||||
fill="url(#l)" fill-opacity=".1" />
|
||||
</g>
|
||||
<g filter="url(#m)">
|
||||
<path
|
||||
d="M362.004 266.772v-78.15a5.12 5.12 0 0 1 1.5-3.622c.844-.073 2.496.257 2.496 2.157v81.562c-.406 2.023-2.605 2.023-2.605 2.023a6.359 6.359 0 0 1-1.391-3.97Z"
|
||||
fill="url(#n)" />
|
||||
<path
|
||||
d="M362.004 266.772v-78.15a5.12 5.12 0 0 1 1.5-3.622c.844-.073 2.496.257 2.496 2.157v81.562c-.406 2.023-2.605 2.023-2.605 2.023a6.359 6.359 0 0 1-1.391-3.97Z"
|
||||
fill="url(#o)" fill-opacity=".1" />
|
||||
</g>
|
||||
<path
|
||||
d="M305 14.5H59c-24.577 0-44.5 19.923-44.5 44.5v615c0 23.472 19.028 42.5 42.5 42.5h250c23.472 0 42.5-19.028 42.5-42.5V59c0-24.577-19.923-44.5-44.5-44.5Z"
|
||||
stroke="url(#p)" stroke-opacity=".5" />
|
||||
<g filter="url(#q)" shape-rendering="crispEdges">
|
||||
<path
|
||||
d="M16 59c0-23.748 19.252-43 43-43h246c23.748 0 43 19.252 43 43v615c0 23.196-18.804 42-42 42H58c-23.196 0-42-18.804-42-42V59Z"
|
||||
fill="url(#r)" fill-opacity=".3" />
|
||||
<path
|
||||
d="M305 15.5H59c-24.024 0-43.5 19.476-43.5 43.5v615c0 23.472 19.028 42.5 42.5 42.5h248c23.472 0 42.5-19.028 42.5-42.5V59c0-24.024-19.476-43.5-43.5-43.5Z"
|
||||
stroke="#000" stroke-opacity=".07" />
|
||||
</g>
|
||||
<g filter="url(#s)">
|
||||
<rect x="154" y="29" width="56" height="5" rx="2.5" fill="#D4D4D4" />
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<mask id="mask">
|
||||
<rect width="366" height="729" fill="#fff" />
|
||||
<path fill-rule="evenodd" clip-rule="evenodd"
|
||||
d="M89.728 24a4.213 4.213 0 0 1 4.213 4.212v2.527c0 10.235 8.3 18.532 18.539 18.532h139.04c10.239 0 18.539-8.297 18.539-18.532v-2.527A4.212 4.212 0 0 1 274.272 24h32.864C325.286 24 340 38.71 340 56.853v618.295c0 18.144-14.714 32.853-32.864 32.853H56.864c-18.15 0-32.864-14.709-32.864-32.853V56.853C24 38.709 38.714 24 56.864 24h32.864Z"
|
||||
fill="#000" />
|
||||
</mask>
|
||||
<linearGradient id="e" x1="1.004" y1="123.367" x2="5" y2="123.367" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D4D4D4" />
|
||||
<stop offset="1" stop-color="#E6E6E6" />
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="3.002" y1="108.991" x2="3.002" y2="116.75" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171717" />
|
||||
<stop offset=".783" stop-color="#171717" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="h" x1="1.004" y1="190.867" x2="5" y2="190.867" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D4D4D4" />
|
||||
<stop offset="1" stop-color="#E6E6E6" />
|
||||
</linearGradient>
|
||||
<linearGradient id="i" x1="3.002" y1="163.991" x2="3.002" y2="178.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171717" />
|
||||
<stop offset=".783" stop-color="#171717" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="k" x1="1.004" y1="260.867" x2="5" y2="260.867" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#D4D4D4" />
|
||||
<stop offset="1" stop-color="#E6E6E6" />
|
||||
</linearGradient>
|
||||
<linearGradient id="l" x1="3.002" y1="233.991" x2="3.002" y2="248.497" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171717" />
|
||||
<stop offset=".783" stop-color="#171717" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="n" x1="362.004" y1="226.25" x2="366" y2="226.25" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".124" stop-color="#E6E6E6" />
|
||||
<stop offset="1" stop-color="#D4D4D4" />
|
||||
</linearGradient>
|
||||
<linearGradient id="o" x1="364.002" y1="184.991" x2="364.002" y2="208.134" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#171717" />
|
||||
<stop offset=".783" stop-color="#171717" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<linearGradient id="p" x1="182" y1="15" x2="182" y2="716" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset=".381" stop-color="#fff" stop-opacity="0" />
|
||||
</linearGradient>
|
||||
<filter id="a" x="-1" y="-1" width="367" height="730.314" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="-2" />
|
||||
<feGaussianBlur stdDeviation="1.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend in2="shape" result="effect1_innerShadow_104_2007" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="-2" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0.0901961 0 0 0 0 0.0901961 0 0 0 0 0.0901961 0 0 0 0.17 0" />
|
||||
<feBlend in2="effect1_innerShadow_104_2007" result="effect2_innerShadow_104_2007" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation=".5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.6 0" />
|
||||
<feBlend in2="effect2_innerShadow_104_2007" result="effect3_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="d" x="1.004" y="108.991" width="4.996" height="28.751" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="-1" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="g" x="1.004" y="163.991" width="4.996" height="53.751" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="-1" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="j" x="1.004" y="233.991" width="4.996" height="53.751" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="-1" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="m" x="361.004" y="184.991" width="4.996" height="85.751" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="-1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.5 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dx="1" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="q" x="15" y="15" width="334" height="703" filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feGaussianBlur stdDeviation="2.5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.03 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<filter id="s" x="154" y="29" width="56" height="6" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feComposite in2="hardAlpha" operator="out" />
|
||||
<feColorMatrix values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.3 0" />
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow_104_2007" />
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow_104_2007" result="shape" />
|
||||
<feColorMatrix in="SourceAlpha" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha" />
|
||||
<feOffset dy="1" />
|
||||
<feGaussianBlur stdDeviation=".5" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.12 0" />
|
||||
<feBlend in2="shape" result="effect2_innerShadow_104_2007" />
|
||||
</filter>
|
||||
<radialGradient id="b" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0 727 -642 0 184 1)">
|
||||
<stop stop-color="#FAFAFA" />
|
||||
<stop offset="1" stop-color="#E6E6E6" />
|
||||
</radialGradient>
|
||||
<radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0 319 -295.5 0 183.5 1)">
|
||||
<stop stop-color="#fff" />
|
||||
<stop offset=".533" stop-color="#fff" stop-opacity="0" />
|
||||
</radialGradient>
|
||||
<radialGradient id="r" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0 689 -326.783 0 182 27)">
|
||||
<stop offset=".319" stop-color="#D4D4D4" />
|
||||
<stop offset="1" stop-color="#E6E6E6" />
|
||||
</radialGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 15 KiB |
118
src/images/qr-code.svg
Normal file
@ -0,0 +1,118 @@
|
||||
<svg width="80" height="80" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M3.2 0H0v3.2h3.2V0ZM3.2 3.2H0v3.2h3.2V3.2ZM3.2 6.4H0v3.2h3.2V6.4Z" fill="#171717" />
|
||||
<path
|
||||
d="M3.2 9.6H0v3.2h3.2V9.6ZM3.2 12.8H0V16h3.2v-3.2ZM3.2 16H0v3.2h3.2V16ZM3.2 19.2H0v3.2h3.2v-3.2ZM3.2 25.6H0v3.2h3.2v-3.2ZM3.2 35.2H0v3.2h3.2v-3.2ZM3.2 57.6H0v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M3.2 60.8H0V64h3.2v-3.2ZM3.2 64H0v3.2h3.2V64ZM3.2 67.2H0v3.2h3.2v-3.2ZM3.2 70.4H0v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M3.2 73.6H0v3.2h3.2v-3.2ZM3.2 76.8H0V80h3.2v-3.2ZM6.4 0H3.2v3.2h3.2V0ZM6.4 19.2H3.2v3.2h3.2v-3.2ZM6.4 25.6H3.2v3.2h3.2v-3.2ZM6.4 28.8H3.2V32h3.2v-3.2ZM6.4 32H3.2v3.2h3.2V32ZM6.4 35.2H3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M6.4 38.4H3.2v3.2h3.2v-3.2ZM6.4 48H3.2v3.2h3.2V48ZM6.4 51.2H3.2v3.2h3.2v-3.2ZM6.4 57.6H3.2v3.2h3.2v-3.2ZM6.4 76.8H3.2V80h3.2v-3.2ZM9.6 0H6.4v3.2h3.2V0ZM9.6 6.4H6.4v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M9.6 9.6H6.4v3.2h3.2V9.6ZM9.6 12.8H6.4V16h3.2v-3.2ZM9.6 19.2H6.4v3.2h3.2v-3.2ZM9.6 28.8H6.4V32h3.2v-3.2ZM9.6 32H6.4v3.2h3.2V32ZM9.6 41.6H6.4v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M9.6 44.8H6.4V48h3.2v-3.2ZM9.6 57.6H6.4v3.2h3.2v-3.2ZM9.6 64H6.4v3.2h3.2V64ZM9.6 67.2H6.4v3.2h3.2v-3.2ZM9.6 70.4H6.4v3.2h3.2v-3.2ZM9.6 76.8H6.4V80h3.2v-3.2ZM12.8 0H9.6v3.2h3.2V0ZM12.8 6.4H9.6v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M12.8 9.6H9.6v3.2h3.2V9.6ZM12.8 12.8H9.6V16h3.2v-3.2ZM12.8 19.2H9.6v3.2h3.2v-3.2ZM12.8 25.6H9.6v3.2h3.2v-3.2ZM12.8 38.4H9.6v3.2h3.2v-3.2ZM12.8 48H9.6v3.2h3.2V48ZM12.8 51.2H9.6v3.2h3.2v-3.2ZM12.8 57.6H9.6v3.2h3.2v-3.2ZM12.8 64H9.6v3.2h3.2V64ZM12.8 67.2H9.6v3.2h3.2v-3.2ZM12.8 70.4H9.6v3.2h3.2v-3.2ZM12.8 76.8H9.6V80h3.2v-3.2ZM16 0h-3.2v3.2H16V0ZM16 6.4h-3.2v3.2H16V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M16 9.6h-3.2v3.2H16V9.6ZM16 12.8h-3.2V16H16v-3.2ZM16 19.2h-3.2v3.2H16v-3.2ZM16 25.6h-3.2v3.2H16v-3.2ZM16 41.6h-3.2v3.2H16v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M16 44.8h-3.2V48H16v-3.2ZM16 57.6h-3.2v3.2H16v-3.2ZM16 64h-3.2v3.2H16V64ZM16 67.2h-3.2v3.2H16v-3.2ZM16 70.4h-3.2v3.2H16v-3.2ZM16 76.8h-3.2V80H16v-3.2ZM19.2 0H16v3.2h3.2V0ZM19.2 19.2H16v3.2h3.2v-3.2ZM19.2 28.8H16V32h3.2v-3.2ZM19.2 44.8H16V48h3.2v-3.2ZM19.2 57.6H16v3.2h3.2v-3.2ZM19.2 76.8H16V80h3.2v-3.2ZM22.4 0h-3.2v3.2h3.2V0ZM22.4 3.2h-3.2v3.2h3.2V3.2ZM22.4 6.4h-3.2v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M22.4 9.6h-3.2v3.2h3.2V9.6ZM22.4 12.8h-3.2V16h3.2v-3.2ZM22.4 16h-3.2v3.2h3.2V16ZM22.4 19.2h-3.2v3.2h3.2v-3.2ZM22.4 25.6h-3.2v3.2h3.2v-3.2ZM22.4 32h-3.2v3.2h3.2V32ZM22.4 38.4h-3.2v3.2h3.2v-3.2ZM22.4 44.8h-3.2V48h3.2v-3.2ZM22.4 51.2h-3.2v3.2h3.2v-3.2ZM22.4 57.6h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M22.4 60.8h-3.2V64h3.2v-3.2ZM22.4 64h-3.2v3.2h3.2V64ZM22.4 67.2h-3.2v3.2h3.2v-3.2ZM22.4 70.4h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M22.4 73.6h-3.2v3.2h3.2v-3.2ZM22.4 76.8h-3.2V80h3.2v-3.2ZM25.6 28.8h-3.2V32h3.2v-3.2ZM25.6 32h-3.2v3.2h3.2V32ZM25.6 35.2h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M25.6 38.4h-3.2v3.2h3.2v-3.2ZM25.6 44.8h-3.2V48h3.2v-3.2ZM25.6 48h-3.2v3.2h3.2V48ZM25.6 51.2h-3.2v3.2h3.2v-3.2ZM28.8 0h-3.2v3.2h3.2V0ZM28.8 19.2h-3.2v3.2h3.2v-3.2ZM28.8 22.4h-3.2v3.2h3.2v-3.2ZM28.8 28.8h-3.2V32h3.2v-3.2ZM28.8 32h-3.2v3.2h3.2V32ZM28.8 35.2h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M28.8 38.4h-3.2v3.2h3.2v-3.2ZM28.8 44.8h-3.2V48h3.2v-3.2ZM28.8 51.2h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path
|
||||
d="M28.8 54.4h-3.2v3.2h3.2v-3.2ZM28.8 64h-3.2v3.2h3.2V64ZM28.8 67.2h-3.2v3.2h3.2v-3.2ZM28.8 73.6h-3.2v3.2h3.2v-3.2ZM28.8 76.8h-3.2V80h3.2v-3.2ZM32 0h-3.2v3.2H32V0ZM32 6.4h-3.2v3.2H32V6.4ZM32 16h-3.2v3.2H32V16ZM32 22.4h-3.2v3.2H32v-3.2ZM32 25.6h-3.2v3.2H32v-3.2ZM32 32h-3.2v3.2H32V32ZM32 38.4h-3.2v3.2H32v-3.2ZM32 54.4h-3.2v3.2H32v-3.2ZM32 57.6h-3.2v3.2H32v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M32 60.8h-3.2V64H32v-3.2ZM32 76.8h-3.2V80H32v-3.2ZM35.2 0H32v3.2h3.2V0ZM35.2 9.6H32v3.2h3.2V9.6ZM35.2 12.8H32V16h3.2v-3.2ZM35.2 19.2H32v3.2h3.2v-3.2ZM35.2 28.8H32V32h3.2v-3.2ZM35.2 38.4H32v3.2h3.2v-3.2ZM35.2 41.6H32v3.2h3.2v-3.2ZM35.2 48H32v3.2h3.2V48ZM35.2 57.6H32v3.2h3.2v-3.2ZM35.2 64H32v3.2h3.2V64ZM35.2 76.8H32V80h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M38.4 0h-3.2v3.2h3.2V0ZM38.4 3.2h-3.2v3.2h3.2V3.2ZM38.4 6.4h-3.2v3.2h3.2V6.4Z" fill="#171717" />
|
||||
<path
|
||||
d="M38.4 9.6h-3.2v3.2h3.2V9.6ZM38.4 12.8h-3.2V16h3.2v-3.2ZM38.4 16h-3.2v3.2h3.2V16ZM38.4 51.2h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M38.4 54.4h-3.2v3.2h3.2v-3.2ZM38.4 60.8h-3.2V64h3.2v-3.2ZM38.4 64h-3.2v3.2h3.2V64ZM38.4 70.4h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M38.4 73.6h-3.2v3.2h3.2v-3.2ZM38.4 76.8h-3.2V80h3.2v-3.2ZM41.6 3.2h-3.2v3.2h3.2V3.2ZM41.6 12.8h-3.2V16h3.2v-3.2ZM41.6 19.2h-3.2v3.2h3.2v-3.2ZM41.6 22.4h-3.2v3.2h3.2v-3.2ZM41.6 25.6h-3.2v3.2h3.2v-3.2ZM41.6 32h-3.2v3.2h3.2V32ZM41.6 41.6h-3.2v3.2h3.2v-3.2ZM41.6 48h-3.2v3.2h3.2V48ZM41.6 51.2h-3.2v3.2h3.2v-3.2ZM41.6 57.6h-3.2v3.2h3.2v-3.2ZM41.6 67.2h-3.2v3.2h3.2v-3.2ZM41.6 73.6h-3.2v3.2h3.2v-3.2ZM41.6 76.8h-3.2V80h3.2v-3.2ZM44.8 0h-3.2v3.2h3.2V0ZM44.8 6.4h-3.2v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path d="M44.8 22.4h-3.2v3.2h3.2v-3.2ZM44.8 32h-3.2v3.2h3.2V32ZM44.8 41.6h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M44.8 44.8h-3.2V48h3.2v-3.2ZM44.8 51.2h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M44.8 54.4h-3.2v3.2h3.2v-3.2ZM44.8 57.6h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path
|
||||
d="M44.8 60.8h-3.2V64h3.2v-3.2ZM44.8 64h-3.2v3.2h3.2V64ZM44.8 67.2h-3.2v3.2h3.2v-3.2ZM44.8 70.4h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M44.8 73.6h-3.2v3.2h3.2v-3.2ZM44.8 76.8h-3.2V80h3.2v-3.2ZM48 0h-3.2v3.2H48V0ZM48 16h-3.2v3.2H48V16ZM48 19.2h-3.2v3.2H48v-3.2ZM48 25.6h-3.2v3.2H48v-3.2ZM48 44.8h-3.2V48H48v-3.2ZM48 54.4h-3.2v3.2H48v-3.2ZM48 57.6h-3.2v3.2H48v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M48 60.8h-3.2V64H48v-3.2ZM48 67.2h-3.2v3.2H48v-3.2ZM51.2 6.4H48v3.2h3.2V6.4ZM51.2 12.8H48V16h3.2v-3.2ZM51.2 22.4H48v3.2h3.2v-3.2ZM51.2 25.6H48v3.2h3.2v-3.2ZM51.2 28.8H48V32h3.2v-3.2ZM51.2 32H48v3.2h3.2V32ZM51.2 35.2H48v3.2h3.2v-3.2ZM51.2 41.6H48v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M51.2 44.8H48V48h3.2v-3.2ZM51.2 54.4H48v3.2h3.2v-3.2ZM51.2 60.8H48V64h3.2v-3.2ZM51.2 64H48v3.2h3.2V64ZM54.4 6.4h-3.2v3.2h3.2V6.4ZM54.4 12.8h-3.2V16h3.2v-3.2ZM54.4 19.2h-3.2v3.2h3.2v-3.2ZM54.4 28.8h-3.2V32h3.2v-3.2ZM54.4 32h-3.2v3.2h3.2V32ZM54.4 38.4h-3.2v3.2h3.2v-3.2ZM54.4 48h-3.2v3.2h3.2V48Z"
|
||||
fill="#171717" />
|
||||
<path d="M54.4 51.2h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M54.4 54.4h-3.2v3.2h3.2v-3.2ZM54.4 57.6h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path
|
||||
d="M54.4 60.8h-3.2V64h3.2v-3.2ZM54.4 64h-3.2v3.2h3.2V64ZM54.4 67.2h-3.2v3.2h3.2v-3.2ZM54.4 70.4h-3.2v3.2h3.2v-3.2ZM54.4 76.8h-3.2V80h3.2v-3.2ZM57.6 32h-3.2v3.2h3.2V32ZM57.6 41.6h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M57.6 44.8h-3.2V48h3.2v-3.2ZM57.6 51.2h-3.2v3.2h3.2v-3.2ZM57.6 64h-3.2v3.2h3.2V64ZM57.6 70.4h-3.2v3.2h3.2v-3.2ZM57.6 76.8h-3.2V80h3.2v-3.2ZM60.8 0h-3.2v3.2h3.2V0ZM60.8 3.2h-3.2v3.2h3.2V3.2ZM60.8 6.4h-3.2v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M60.8 9.6h-3.2v3.2h3.2V9.6ZM60.8 12.8h-3.2V16h3.2v-3.2ZM60.8 16h-3.2v3.2h3.2V16ZM60.8 19.2h-3.2v3.2h3.2v-3.2ZM60.8 25.6h-3.2v3.2h3.2v-3.2ZM60.8 35.2h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M60.8 38.4h-3.2v3.2h3.2v-3.2ZM60.8 44.8h-3.2V48h3.2v-3.2ZM60.8 48h-3.2v3.2h3.2V48ZM60.8 51.2h-3.2v3.2h3.2v-3.2ZM60.8 57.6h-3.2v3.2h3.2v-3.2ZM60.8 64h-3.2v3.2h3.2V64ZM60.8 67.2h-3.2v3.2h3.2v-3.2ZM64 0h-3.2v3.2H64V0ZM64 19.2h-3.2v3.2H64v-3.2ZM64 28.8h-3.2V32H64v-3.2ZM64 38.4h-3.2v3.2H64v-3.2ZM64 48h-3.2v3.2H64V48ZM64 51.2h-3.2v3.2H64v-3.2ZM64 64h-3.2v3.2H64V64ZM64 73.6h-3.2v3.2H64v-3.2ZM67.2 0H64v3.2h3.2V0ZM67.2 6.4H64v3.2h3.2V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M67.2 9.6H64v3.2h3.2V9.6ZM67.2 12.8H64V16h3.2v-3.2ZM67.2 19.2H64v3.2h3.2v-3.2ZM67.2 28.8H64V32h3.2v-3.2ZM67.2 41.6H64v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M67.2 44.8H64V48h3.2v-3.2ZM67.2 51.2H64v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M67.2 54.4H64v3.2h3.2v-3.2ZM67.2 57.6H64v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M67.2 60.8H64V64h3.2v-3.2ZM67.2 64H64v3.2h3.2V64ZM67.2 70.4H64v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path d="M67.2 73.6H64v3.2h3.2v-3.2ZM70.4 0h-3.2v3.2h3.2V0ZM70.4 6.4h-3.2v3.2h3.2V6.4Z" fill="#171717" />
|
||||
<path
|
||||
d="M70.4 9.6h-3.2v3.2h3.2V9.6ZM70.4 12.8h-3.2V16h3.2v-3.2ZM70.4 19.2h-3.2v3.2h3.2v-3.2ZM70.4 28.8h-3.2V32h3.2v-3.2ZM70.4 32h-3.2v3.2h3.2V32ZM70.4 35.2h-3.2v3.2h3.2v-3.2ZM70.4 44.8h-3.2V48h3.2v-3.2ZM70.4 48h-3.2v3.2h3.2V48ZM70.4 70.4h-3.2v3.2h3.2v-3.2ZM70.4 76.8h-3.2V80h3.2v-3.2ZM73.6 0h-3.2v3.2h3.2V0Z"
|
||||
fill="#171717" />
|
||||
<path d="M73.6 6.4h-3.2v3.2h3.2V6.4Z" fill="#171717" />
|
||||
<path
|
||||
d="M73.6 9.6h-3.2v3.2h3.2V9.6ZM73.6 12.8h-3.2V16h3.2v-3.2ZM73.6 19.2h-3.2v3.2h3.2v-3.2ZM73.6 28.8h-3.2V32h3.2v-3.2ZM73.6 35.2h-3.2v3.2h3.2v-3.2ZM73.6 44.8h-3.2V48h3.2v-3.2ZM73.6 48h-3.2v3.2h3.2V48ZM73.6 51.2h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M73.6 54.4h-3.2v3.2h3.2v-3.2ZM73.6 70.4h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path
|
||||
d="M73.6 73.6h-3.2v3.2h3.2v-3.2ZM76.8 0h-3.2v3.2h3.2V0ZM76.8 19.2h-3.2v3.2h3.2v-3.2ZM76.8 28.8h-3.2V32h3.2v-3.2ZM76.8 35.2h-3.2v3.2h3.2v-3.2ZM76.8 41.6h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M76.8 44.8h-3.2V48h3.2v-3.2ZM76.8 51.2h-3.2v3.2h3.2v-3.2Z" fill="#171717" />
|
||||
<path
|
||||
d="M76.8 54.4h-3.2v3.2h3.2v-3.2ZM76.8 64h-3.2v3.2h3.2V64ZM76.8 67.2h-3.2v3.2h3.2v-3.2ZM76.8 70.4h-3.2v3.2h3.2v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M76.8 73.6h-3.2v3.2h3.2v-3.2ZM80 0h-3.2v3.2H80V0ZM80 3.2h-3.2v3.2H80V3.2ZM80 6.4h-3.2v3.2H80V6.4Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M80 9.6h-3.2v3.2H80V9.6ZM80 12.8h-3.2V16H80v-3.2ZM80 16h-3.2v3.2H80V16ZM80 19.2h-3.2v3.2H80v-3.2ZM80 25.6h-3.2v3.2H80v-3.2ZM80 32h-3.2v3.2H80V32ZM80 35.2h-3.2v3.2H80v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path
|
||||
d="M80 38.4h-3.2v3.2H80v-3.2ZM80 44.8h-3.2V48H80v-3.2ZM80 48h-3.2v3.2H80V48ZM80 57.6h-3.2v3.2H80v-3.2ZM80 64h-3.2v3.2H80V64ZM80 67.2h-3.2v3.2H80v-3.2ZM80 70.4h-3.2v3.2H80v-3.2Z"
|
||||
fill="#171717" />
|
||||
<path d="M80 73.6h-3.2v3.2H80v-3.2ZM80 76.8h-3.2V80H80v-3.2Z" fill="#171717" />
|
||||
</svg>
|
After Width: | Height: | Size: 9.9 KiB |
83
src/styles/tailwind.css
Normal file
@ -0,0 +1,83 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@plugin '@tailwindcss/forms';
|
||||
|
||||
@theme {
|
||||
--text-*: initial;
|
||||
--text-xs: 0.75rem;
|
||||
--text-xs--line-height: 1rem;
|
||||
--text-sm: 0.875rem;
|
||||
--text-sm--line-height: 1.5rem;
|
||||
--text-base: 1rem;
|
||||
--text-base--line-height: 1.5rem;
|
||||
--text-lg: 1.125rem;
|
||||
--text-lg--line-height: 2rem;
|
||||
--text-xl: 1.25rem;
|
||||
--text-xl--line-height: 1.75rem;
|
||||
--text-2xl: 1.5rem;
|
||||
--text-2xl--line-height: 2rem;
|
||||
--text-3xl: 2rem;
|
||||
--text-3xl--line-height: 3rem;
|
||||
--text-4xl: 2.5rem;
|
||||
--text-4xl--line-height: 3rem;
|
||||
--text-5xl: 3rem;
|
||||
--text-5xl--line-height: 1;
|
||||
--text-6xl: 3.75rem;
|
||||
--text-6xl--line-height: 1;
|
||||
--text-7xl: 4.5rem;
|
||||
--text-7xl--line-height: 1;
|
||||
--text-8xl: 6rem;
|
||||
--text-8xl--line-height: 1;
|
||||
--text-9xl: 8rem;
|
||||
--text-9xl--line-height: 1;
|
||||
|
||||
--animate-fade-in: fade-in 0.5s linear forwards;
|
||||
--animate-spin-slow: spin 4s linear infinite;
|
||||
--animate-spin-slower: spin 6s linear infinite;
|
||||
--animate-spin-reverse: spin-reverse 1s linear infinite;
|
||||
--animate-spin-reverse-slow: spin-reverse 4s linear infinite;
|
||||
--animate-spin-reverse-slower: spin-reverse 6s linear infinite;
|
||||
|
||||
--radius-4xl: 2rem;
|
||||
--radius-5xl: 2.5rem;
|
||||
|
||||
--color-gray-50: oklch(0.985 0 0);
|
||||
--color-gray-100: oklch(0.97 0 0);
|
||||
--color-gray-200: oklch(0.922 0 0);
|
||||
--color-gray-300: oklch(0.87 0 0);
|
||||
--color-gray-400: oklch(0.708 0 0);
|
||||
--color-gray-500: oklch(0.556 0 0);
|
||||
--color-gray-600: oklch(0.439 0 0);
|
||||
--color-gray-700: oklch(0.371 0 0);
|
||||
--color-gray-800: oklch(0.269 0 0);
|
||||
--color-gray-900: oklch(0.205 0 0);
|
||||
--color-gray-950: oklch(0.145 0 0);
|
||||
|
||||
--font-sans: var(--font-inter);
|
||||
|
||||
--container-2xl: 40rem;
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes spin-reverse {
|
||||
to {
|
||||
transform: rotate(-360deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--animate-marquee: marquee var(--marquee-duration) linear infinite;
|
||||
|
||||
@keyframes marquee {
|
||||
100% {
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
}
|
28
tsconfig.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"allowJs": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "esnext",
|
||||
"moduleResolution": "bundler",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"plugins": [
|
||||
{
|
||||
"name": "next"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|