cleaner landing page
This commit is contained in:
parent
9e783b506d
commit
27f8ed4c7c
@ -41,6 +41,22 @@ invariant(
|
|||||||
`Please define the "WEB_PUSH_VAPID_PUBLIC_KEY" environment variable`,
|
`Please define the "WEB_PUSH_VAPID_PUBLIC_KEY" environment variable`,
|
||||||
);
|
);
|
||||||
invariant(typeof process.env.FATHOM_SITE_ID === "string", `Please define the "FATHOM_SITE_ID" environment variable`);
|
invariant(typeof process.env.FATHOM_SITE_ID === "string", `Please define the "FATHOM_SITE_ID" environment variable`);
|
||||||
|
invariant(
|
||||||
|
typeof process.env.MAILCHIMP_API_KEY === "string",
|
||||||
|
`Please define the "MAILCHIMP_API_KEY" environment variable`,
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
typeof process.env.MAILCHIMP_AUDIENCE_ID === "string",
|
||||||
|
`Please define the "MAILCHIMP_AUDIENCE_ID" environment variable`,
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
typeof process.env.DISCORD_WEBHOOK_ID === "string",
|
||||||
|
`Please define the "DISCORD_WEBHOOK_ID" environment variable`,
|
||||||
|
);
|
||||||
|
invariant(
|
||||||
|
typeof process.env.DISCORD_WEBHOOK_TOKEN === "string",
|
||||||
|
`Please define the "DISCORD_WEBHOOK_TOKEN" environment variable`,
|
||||||
|
);
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
app: {
|
app: {
|
||||||
@ -61,10 +77,18 @@ export default {
|
|||||||
secretAccessKey: process.env.AWS_S3_ACCESS_KEY_SECRET,
|
secretAccessKey: process.env.AWS_S3_ACCESS_KEY_SECRET,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
discord: {
|
||||||
|
webhookId: process.env.DISCORD_WEBHOOK_ID,
|
||||||
|
webhookToken: process.env.DISCORD_WEBHOOK_TOKEN,
|
||||||
|
},
|
||||||
fathom: {
|
fathom: {
|
||||||
siteId: process.env.FATHOM_SITE_ID,
|
siteId: process.env.FATHOM_SITE_ID,
|
||||||
domain: process.env.FATHOM_CUSTOM_DOMAIN,
|
domain: process.env.FATHOM_CUSTOM_DOMAIN,
|
||||||
},
|
},
|
||||||
|
mailchimp: {
|
||||||
|
apiKey: process.env.MAILCHIMP_API_KEY,
|
||||||
|
audienceId: process.env.MAILCHIMP_AUDIENCE_ID,
|
||||||
|
},
|
||||||
redis: {
|
redis: {
|
||||||
url: process.env.REDIS_URL,
|
url: process.env.REDIS_URL,
|
||||||
password: process.env.REDIS_PASSWORD,
|
password: process.env.REDIS_PASSWORD,
|
||||||
|
23
app/features/public-area/actions/index.ts
Normal file
23
app/features/public-area/actions/index.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { type ActionFunction, json } from "@remix-run/node";
|
||||||
|
|
||||||
|
import { addSubscriber } from "~/utils/mailchimp.server";
|
||||||
|
import { executeWebhook } from "~/utils/discord.server";
|
||||||
|
|
||||||
|
export type JoinWaitlistActionData = { submitted: true };
|
||||||
|
|
||||||
|
const action: ActionFunction = async ({ request }) => {
|
||||||
|
const formData = await request.formData();
|
||||||
|
const email = formData.get("email");
|
||||||
|
if (!formData.get("email") || typeof email !== "string") {
|
||||||
|
throw new Error("Something wrong happened");
|
||||||
|
}
|
||||||
|
|
||||||
|
// await addSubscriber(email);
|
||||||
|
const res = await executeWebhook(email);
|
||||||
|
console.log(res.status);
|
||||||
|
console.log(await res.text());
|
||||||
|
|
||||||
|
return json<JoinWaitlistActionData>({ submitted: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
export default action;
|
@ -1,20 +0,0 @@
|
|||||||
import type { FunctionComponent, PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
import Header from "./header";
|
|
||||||
import Footer from "./footer";
|
|
||||||
|
|
||||||
const BaseLayout: FunctionComponent<PropsWithChildren<{}>> = ({ children }) => (
|
|
||||||
<>
|
|
||||||
<section className="font-inter antialiased bg-white text-gray-900 tracking-tight">
|
|
||||||
<section className="flex flex-col min-h-screen overflow-hidden">
|
|
||||||
<Header />
|
|
||||||
|
|
||||||
<main className="flex-grow">{children}</main>
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default BaseLayout;
|
|
41
app/features/public-area/components/button.tsx
Normal file
41
app/features/public-area/components/button.tsx
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import type { ButtonHTMLAttributes } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
const baseStyles = {
|
||||||
|
solid: "group inline-flex items-center justify-center rounded-full py-2 px-4 text-sm font-semibold focus:outline-none focus-visible:outline-2 focus-visible:outline-offset-2",
|
||||||
|
outline: "group inline-flex ring-1 items-center justify-center rounded-full py-2 px-4 text-sm focus:outline-none",
|
||||||
|
};
|
||||||
|
|
||||||
|
const variantStyles = {
|
||||||
|
solid: {
|
||||||
|
slate: "bg-slate-900 text-white hover:bg-slate-700 hover:text-slate-100 active:bg-slate-800 active:text-slate-300 focus-visible:outline-slate-900",
|
||||||
|
primary:
|
||||||
|
"bg-primary-600 text-white hover:text-slate-100 hover:bg-primary-500 active:bg-primary-800 active:text-primary-100 focus-visible:outline-primary-600",
|
||||||
|
white: "bg-white text-slate-900 hover:bg-primary-50 active:bg-primary-200 active:text-slate-600 focus-visible:outline-white",
|
||||||
|
},
|
||||||
|
outline: {
|
||||||
|
slate: "ring-slate-200 text-slate-700 hover:text-slate-900 hover:ring-slate-300 active:bg-slate-100 active:text-slate-600 focus-visible:outline-primary-600 focus-visible:ring-slate-300",
|
||||||
|
white: "ring-slate-700 text-white hover:ring-slate-500 active:ring-slate-700 active:text-slate-400 focus-visible:outline-white",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = ButtonHTMLAttributes<HTMLButtonElement> &
|
||||||
|
(
|
||||||
|
| {
|
||||||
|
variant: "solid";
|
||||||
|
color: "slate" | "primary" | "white";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
variant: "outline";
|
||||||
|
color: "slate" | "white";
|
||||||
|
}
|
||||||
|
) & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Button({ variant, color, className, ...props }: Props) {
|
||||||
|
// @ts-ignore
|
||||||
|
const fullClassName = clsx(baseStyles[variant], variantStyles[variant][color], className);
|
||||||
|
|
||||||
|
return <button className={fullClassName} {...props} />;
|
||||||
|
}
|
62
app/features/public-area/components/call-to-action.tsx
Normal file
62
app/features/public-area/components/call-to-action.tsx
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { Form, useActionData } from "@remix-run/react";
|
||||||
|
|
||||||
|
import type { JoinWaitlistActionData } from "~/features/public-area/actions";
|
||||||
|
import Button from "./button";
|
||||||
|
import Container from "./container";
|
||||||
|
import { TextField } from "./fields";
|
||||||
|
|
||||||
|
import backgroundImage from "../images/background-call-to-action.jpg";
|
||||||
|
import Alert from "~/features/core/components/alert";
|
||||||
|
|
||||||
|
export default function CallToAction() {
|
||||||
|
const actionData = useActionData<JoinWaitlistActionData>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<section id="get-started-today" className="relative overflow-hidden bg-blue-600 py-32">
|
||||||
|
<img
|
||||||
|
className="absolute top-1/2 left-1/2 max-w-none -translate-x-1/2 -translate-y-1/2"
|
||||||
|
src={backgroundImage}
|
||||||
|
alt=""
|
||||||
|
width={2347}
|
||||||
|
height={1244}
|
||||||
|
/>
|
||||||
|
<Container className="relative">
|
||||||
|
<div className="mx-auto max-w-lg text-center">
|
||||||
|
<h2 className="font-mackinac font-bold text-3xl tracking-tight text-white sm:text-4xl">
|
||||||
|
Request access
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg tracking-tight text-white">
|
||||||
|
Shellphone is currently invite-only but we onboard new users on a regular basis. Enter your
|
||||||
|
email address to join the waitlist and receive important updates in your inbox.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Form method="post" className="max-w-2xl mx-auto flex mt-10 space-x-4">
|
||||||
|
{actionData?.submitted ? (
|
||||||
|
<div className="m-auto">
|
||||||
|
<Alert
|
||||||
|
title="You made it!"
|
||||||
|
message="You're on the list, we will be in touch soon"
|
||||||
|
variant="success"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<TextField
|
||||||
|
id="email"
|
||||||
|
name="email"
|
||||||
|
type="email"
|
||||||
|
autoComplete="email"
|
||||||
|
className="w-full"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button type="submit" variant="solid" color="white" className="w-40">
|
||||||
|
<span>Join waitlist</span>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Form>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
10
app/features/public-area/components/container.tsx
Normal file
10
app/features/public-area/components/container.tsx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { HTMLAttributes } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
type Props = HTMLAttributes<HTMLDivElement> & {
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Container({ className, ...props }: Props) {
|
||||||
|
return <div className={clsx("mx-auto max-w-7xl px-4 sm:px-6 lg:px-8", className)} {...props} />;
|
||||||
|
}
|
@ -1,32 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
|
|
||||||
export default function CTAForm() {
|
|
||||||
// TODO
|
|
||||||
const [{ isSubmitted }, setState] = useState({ isSubmitted: false });
|
|
||||||
const onSubmit = () => setState({ isSubmitted: true });
|
|
||||||
|
|
||||||
return (
|
|
||||||
<form onSubmit={onSubmit}>
|
|
||||||
{isSubmitted ? (
|
|
||||||
<p className="text-center md:text-left mt-2 opacity-75 text-green-900 text-md">
|
|
||||||
You're on the list! We will be in touch soon
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<div className="flex flex-col sm:flex-row justify-center w-full md:max-w-md md:mx-0">
|
|
||||||
<input
|
|
||||||
name="email"
|
|
||||||
type="email"
|
|
||||||
className="form-input w-full mb-2 sm:mb-0 sm:mr-2 focus:outline-none focus:ring-rebeccapurple-500 focus:border-rebeccapurple-500"
|
|
||||||
placeholder="Enter your email address"
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
className="btn text-white bg-rebeccapurple-500 hover:bg-rebeccapurple-400 flex-shrink-0"
|
|
||||||
>
|
|
||||||
Request access
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</form>
|
|
||||||
);
|
|
||||||
}
|
|
@ -2,7 +2,97 @@ import type { FunctionComponent, PropsWithChildren } from "react";
|
|||||||
import { Disclosure, Transition } from "@headlessui/react";
|
import { Disclosure, Transition } from "@headlessui/react";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
export default function FAQs() {
|
import Container from "./container";
|
||||||
|
import backgroundImage from "../images/background-faqs.jpg";
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "Does TaxPal handle VAT?",
|
||||||
|
answer: "Well no, but if you move your company offshore you can probably ignore it.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Can I pay for my subscription via purchase order?",
|
||||||
|
answer: "Absolutely, we are happy to take your money in all forms.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "How do I apply for a job at TaxPal?",
|
||||||
|
answer: "We only hire our customers, so subscribe for a minimum of 6 months and then let’s talk.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "What was that testimonial about tax fraud all about?",
|
||||||
|
answer: "TaxPal is just a software application, ultimately your books are your responsibility.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "TaxPal sounds horrible but why do I still feel compelled to purchase?",
|
||||||
|
answer: "This is the power of excellent visual design. You just can’t resist it, no matter how poorly it actually functions.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "I found other companies called TaxPal, are you sure you can use this name?",
|
||||||
|
answer: "Honestly not sure at all. We haven’t actually incorporated or anything, we just thought it sounded cool and made this website.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "How do you generate reports?",
|
||||||
|
answer: "You just tell us what data you need a report for, and we get our kids to create beautiful charts for you using only the finest crayons.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Can we expect more inventory features?",
|
||||||
|
answer: "In life it’s really better to never expect anything at all.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "I lost my password, how do I get into my account?",
|
||||||
|
answer: "Send us an email and we will send you a copy of our latest password spreadsheet so you can find your information.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function Faqs() {
|
||||||
|
return (
|
||||||
|
<section id="faq" aria-labelledby="faq-title" className="relative overflow-hidden bg-slate-50 py-20 sm:py-32">
|
||||||
|
<img
|
||||||
|
className="absolute top-0 left-1/2 max-w-none translate-x-[-30%] -translate-y-1/4"
|
||||||
|
src={backgroundImage}
|
||||||
|
alt=""
|
||||||
|
width={1558}
|
||||||
|
height={946}
|
||||||
|
/>
|
||||||
|
<Container className="relative">
|
||||||
|
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||||
|
<h2
|
||||||
|
id="faq-title"
|
||||||
|
className="font-mackinac font-bold text-3xl tracking-tight text-slate-900 sm:text-4xl"
|
||||||
|
>
|
||||||
|
Frequently asked questions
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<ul className="mt-16 grid grid-cols-1 max-w-3xl mx-auto pl-12 lg:mx-0">
|
||||||
|
<Accordion title="How does it work?">
|
||||||
|
Shellphone is your go-to app to use your phone number over the internet. It integrates
|
||||||
|
seamlessly with Twilio to provide the best experience for your personal cloud phone.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="What do I need to use Shellphone?">
|
||||||
|
Shellphone is still in its early stages and we're working hard to make it as easy-to-use as
|
||||||
|
possible. Currently, you must have a Twilio account to set up your personal cloud phone with
|
||||||
|
Shellphone.
|
||||||
|
</Accordion>
|
||||||
|
<Accordion title="Why would I use this over an eSIM?">
|
||||||
|
Chances are you're currently using an eSIM-compatible device. eSIMs are a reasonable way of
|
||||||
|
using a phone number internationally but they are still subject to some irky limitations. For
|
||||||
|
example, you can only use an eSIM on one device at a time and you are still subject to
|
||||||
|
exorbitant rates from your carrier.
|
||||||
|
</Accordion>
|
||||||
|
<span className="block border-t border-gray-200" aria-hidden="true" />
|
||||||
|
</ul>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function FAQs() {
|
||||||
return (
|
return (
|
||||||
<section className="max-w-6xl mx-auto px-4 sm:px-6">
|
<section className="max-w-6xl mx-auto px-4 sm:px-6">
|
||||||
<div className="py-12 md:py-20">
|
<div className="py-12 md:py-20">
|
||||||
|
27
app/features/public-area/components/fields.tsx
Normal file
27
app/features/public-area/components/fields.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import type { InputHTMLAttributes, HTMLAttributes, PropsWithChildren } from "react";
|
||||||
|
|
||||||
|
const formClasses =
|
||||||
|
"block w-full appearance-none rounded-md border border-gray-200 bg-gray-50 px-3 py-2 text-gray-900 placeholder-gray-400 focus:border-blue-500 focus:bg-white focus:outline-none focus:ring-blue-500 sm:text-sm";
|
||||||
|
|
||||||
|
function Label({ id, children }: PropsWithChildren<HTMLAttributes<HTMLLabelElement>>) {
|
||||||
|
return (
|
||||||
|
<label htmlFor={id} className="mb-3 block text-sm font-medium text-gray-700">
|
||||||
|
{children}
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TextField({
|
||||||
|
id,
|
||||||
|
label,
|
||||||
|
type = "text",
|
||||||
|
className = "",
|
||||||
|
...props
|
||||||
|
}: InputHTMLAttributes<HTMLInputElement> & { label?: string }) {
|
||||||
|
return (
|
||||||
|
<div className={className}>
|
||||||
|
{label && <Label id={id}>{label}</Label>}
|
||||||
|
<input id={id} type={type} {...props} className={formClasses} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,40 +0,0 @@
|
|||||||
import type { FunctionComponent } from "react";
|
|
||||||
import { Link, type LinkProps } from "@remix-run/react";
|
|
||||||
|
|
||||||
export default function Footer() {
|
|
||||||
// TODO
|
|
||||||
const isDisabled = true;
|
|
||||||
if (isDisabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<footer className="bg-white">
|
|
||||||
<div className="max-w-7xl mx-auto py-12 px-4 overflow-hidden sm:px-6 lg:px-8">
|
|
||||||
<nav className="-mx-5 -my-2 flex flex-wrap justify-center" aria-label="Footer">
|
|
||||||
<FooterLink to="/blog" name="Blog" />
|
|
||||||
<FooterLink to="/privacy" name="Privacy Policy" />
|
|
||||||
<FooterLink to="/terms" name="Terms of Service" />
|
|
||||||
<FooterLink to="mailto:support@shellphone.app" name="Email Us" />
|
|
||||||
</nav>
|
|
||||||
<p className="mt-8 text-center text-base text-gray-400">
|
|
||||||
© 2021 Capsule Corp. Dev Pte. Ltd. All rights reserved.
|
|
||||||
{/*© 2021 Mokhtar Mial All rights reserved.*/}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
to: LinkProps["to"];
|
|
||||||
name: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const FooterLink: FunctionComponent<Props> = ({ to, name }) => (
|
|
||||||
<div className="px-5 py-2">
|
|
||||||
<Link to={to} className="text-base text-gray-500 hover:text-gray-900">
|
|
||||||
{name}
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
@ -1,240 +1,34 @@
|
|||||||
import { Fragment, useState, useRef, useEffect } from "react";
|
import { Link } from "@remix-run/react";
|
||||||
import { Link, type LinkProps } from "@remix-run/react";
|
|
||||||
import { Dialog, Transition } from "@headlessui/react";
|
|
||||||
import { IoClose } from "react-icons/io5";
|
|
||||||
|
|
||||||
function Header() {
|
import Button from "./button";
|
||||||
|
import Container from "./container";
|
||||||
|
import Logo from "./logo";
|
||||||
|
import NavLink from "./nav-link";
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
return (
|
return (
|
||||||
<header className="absolute inset-x-0 top-0 z-10 w-full">
|
<header className="py-10">
|
||||||
<div className="px-4 mx-auto sm:px-6 lg:px-8">
|
<Container>
|
||||||
<div className="flex items-center justify-between h-16 lg:h-20">
|
<nav className="relative z-50 flex justify-between">
|
||||||
<div className="hidden lg:flex lg:items-center lg:justify-center lg:ml-10 lg:mr-auto lg:space-x-10">
|
<div className="flex items-center md:gap-x-12">
|
||||||
<a
|
<Link to="/" aria-label="Home">
|
||||||
href="#"
|
<Logo />
|
||||||
title=""
|
|
||||||
className="text-base text-black transition-all duration-200 hover:text-opacity-80"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Features{" "}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
title=""
|
|
||||||
className="text-base text-black transition-all duration-200 hover:text-opacity-80"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Solutions{" "}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
title=""
|
|
||||||
className="text-base text-black transition-all duration-200 hover:text-opacity-80"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Resources{" "}
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<a
|
|
||||||
href="#"
|
|
||||||
title=""
|
|
||||||
className="text-base text-black transition-all duration-200 hover:text-opacity-80"
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
Pricing{" "}
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function Headerold() {
|
|
||||||
return (
|
|
||||||
<header className="absolute w-full z-30 inset-x-0 top-0">
|
|
||||||
<div className="px-4 mx-auto sm:px-6 lg:px-8">
|
|
||||||
<div className="flex items-center justify-between h-20">
|
|
||||||
<div className="flex-shrink-0 mr-5">
|
|
||||||
<Link to="/" className="block">
|
|
||||||
<img className="w-10 h-10" src="/shellphone.png" alt="Shellphone logo" />
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex items-center gap-x-5 md:gap-x-8">
|
||||||
<nav className="hidden md:flex md:flex-grow">
|
<NavLink href="/sign-in">Have an account?</NavLink>
|
||||||
<ul className="flex items-center justify-center ml-10 mr-auto space-x-10">
|
<Button
|
||||||
<li>
|
variant="solid"
|
||||||
<DesktopNavLink to="/features" label="Features" />
|
color="primary"
|
||||||
</li>
|
onClick={() => {
|
||||||
<li>
|
document.querySelector("#get-started-today")?.scrollIntoView({ behavior: "smooth" });
|
||||||
<DesktopNavLink to="/roadmap" label="Roadmap" />
|
}}
|
||||||
</li>
|
>
|
||||||
<li>
|
<span>Request access</span>
|
||||||
<DesktopNavLink to="/open" label="Open Metrics" />
|
</Button>
|
||||||
</li>
|
</div>
|
||||||
<li>
|
|
||||||
<DesktopNavLink to="/pricing" label="Pricing" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
</nav>
|
||||||
|
</Container>
|
||||||
<MobileNav />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
type NavLinkProps = {
|
|
||||||
to: LinkProps["to"];
|
|
||||||
label: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
function DesktopNavLink({ to, label }: NavLinkProps) {
|
|
||||||
return (
|
|
||||||
<Link to={to} className="text-base text-gray-600 hover:text-gray-900 transition duration-150 ease-in-out">
|
|
||||||
{label}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function MobileNav() {
|
|
||||||
const [mobileNavOpen, setMobileNavOpen] = useState(false);
|
|
||||||
|
|
||||||
const trigger = useRef<HTMLButtonElement>(null);
|
|
||||||
const mobileNav = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
// close the mobile menu on click outside
|
|
||||||
useEffect(() => {
|
|
||||||
const clickHandler = ({ target }: MouseEvent) => {
|
|
||||||
if (!mobileNav.current || !trigger.current) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
console.log(mobileNav.current.contains(target as Node));
|
|
||||||
if (
|
|
||||||
!mobileNavOpen ||
|
|
||||||
mobileNav.current.contains(target as Node) ||
|
|
||||||
trigger.current.contains(target as Node)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setMobileNavOpen(false);
|
|
||||||
};
|
|
||||||
document.addEventListener("click", clickHandler);
|
|
||||||
return () => document.removeEventListener("click", clickHandler);
|
|
||||||
});
|
|
||||||
|
|
||||||
// close the mobile menu if the esc key is pressed
|
|
||||||
useEffect(() => {
|
|
||||||
const keyHandler = ({ keyCode }: KeyboardEvent) => {
|
|
||||||
if (!mobileNavOpen || keyCode !== 27) return;
|
|
||||||
setMobileNavOpen(false);
|
|
||||||
};
|
|
||||||
document.addEventListener("keydown", keyHandler);
|
|
||||||
return () => document.removeEventListener("keydown", keyHandler);
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="inline-flex md:hidden">
|
|
||||||
<button
|
|
||||||
ref={trigger}
|
|
||||||
className={`hamburger ${mobileNavOpen && "active"}`}
|
|
||||||
aria-controls="mobile-nav"
|
|
||||||
aria-expanded={mobileNavOpen}
|
|
||||||
onClick={() => setMobileNavOpen(!mobileNavOpen)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Menu</span>
|
|
||||||
<svg
|
|
||||||
className="w-6 h-6 fill-current text-gray-900 hover:text-gray-900 transition duration-150 ease-in-out"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<rect y="4" width="24" height="2" rx="1" />
|
|
||||||
<rect y="11" width="24" height="2" rx="1" />
|
|
||||||
<rect y="18" width="24" height="2" rx="1" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<Transition.Root show={mobileNavOpen} as={Fragment}>
|
|
||||||
<Dialog as="div" className="fixed z-40 inset-0 overflow-hidden" onClose={setMobileNavOpen}>
|
|
||||||
<div className="absolute inset-0 overflow-hidden">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="ease-in-out duration-500"
|
|
||||||
enterFrom="opacity-0"
|
|
||||||
enterTo="opacity-100"
|
|
||||||
leave="ease-in-out duration-500"
|
|
||||||
leaveFrom="opacity-100"
|
|
||||||
leaveTo="opacity-0"
|
|
||||||
>
|
|
||||||
<Dialog.Overlay className="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
|
|
||||||
</Transition.Child>
|
|
||||||
|
|
||||||
<div className="fixed inset-y-0 right-0 pl-10 max-w-full flex">
|
|
||||||
<Transition.Child
|
|
||||||
as={Fragment}
|
|
||||||
enter="transform transition ease-in-out duration-500 sm:duration-700"
|
|
||||||
enterFrom="translate-x-full"
|
|
||||||
enterTo="translate-x-0"
|
|
||||||
leave="transform transition ease-in-out duration-500 sm:duration-700"
|
|
||||||
leaveFrom="translate-x-0"
|
|
||||||
leaveTo="translate-x-full"
|
|
||||||
>
|
|
||||||
<div ref={mobileNav} className="w-screen max-w-[16rem] sm:max-w-sm">
|
|
||||||
<div className="h-full flex flex-col py-6 bg-white shadow-xl overflow-y-scroll">
|
|
||||||
<div className="px-4 sm:px-6">
|
|
||||||
<div className="flex items-start justify-between">
|
|
||||||
<Dialog.Title className="text-lg font-medium text-gray-900">
|
|
||||||
Shellphone
|
|
||||||
</Dialog.Title>
|
|
||||||
<div className="ml-3 h-7 flex items-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="bg-white rounded-md text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-rebeccapurple-500"
|
|
||||||
onClick={() => setMobileNavOpen(false)}
|
|
||||||
>
|
|
||||||
<span className="sr-only">Close panel</span>
|
|
||||||
<IoClose className="h-6 w-6" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="mt-6 relative flex-1 px-4 sm:px-6">
|
|
||||||
<div className="absolute inset-0 px-4 sm:px-6">
|
|
||||||
<ul className="space-y-4">
|
|
||||||
<li>
|
|
||||||
<MobileNavLink to="/features" label="Features" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<MobileNavLink to="/roadmap" label="Roadmap" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<MobileNavLink to="open" label="Open Metrics" />
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<MobileNavLink to="/pricing" label="Pricing" />
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Transition.Child>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Dialog>
|
|
||||||
</Transition.Root>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
function MobileNavLink({ to, label }: NavLinkProps) {
|
|
||||||
return (
|
|
||||||
<Link to={to} onClick={() => setMobileNavOpen(false)} className="text-base flex text-gray-600 hover:text-gray-900">
|
|
||||||
{label}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Headerold;
|
|
||||||
|
@ -1,51 +1,38 @@
|
|||||||
import CTAForm from "./cta-form";
|
import Button from "./button";
|
||||||
|
import Container from "./container";
|
||||||
|
|
||||||
import mockupImage from "../images/phone-mockup.png";
|
/*
|
||||||
|
height: calc(100vh - 120px);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin-top: -120px;
|
||||||
|
*/
|
||||||
|
|
||||||
export default function Hero() {
|
export default function Hero() {
|
||||||
return (
|
return (
|
||||||
<div className="relative bg-gradient-to-b from-rebeccapurple-100 to-rebeccapurple-200">
|
<Container className="pt-20 pb-16 text-center lg:pt-32 landing-hero">
|
||||||
<section className="overflow-hidden">
|
<h1 className="mx-auto max-w-4xl font-mackinac font-heading text-5xl font-medium tracking-tight text-[#24185B] sm:text-7xl">
|
||||||
<div className="flex flex-col lg:flex-row lg:items-stretch lg:min-h-screen lg:max-h-[900px]">
|
<span className="background-primary bg-clip-text decoration-clone text-transparent">
|
||||||
<div className="flex items-center justify-center w-full lg:order-2 lg:w-7/12">
|
Calling your bank from abroad
|
||||||
<div className="h-full px-4 pt-24 pb-16 sm:px-6 lg:px-24 2xl:px-32 lg:pt-40 lg:pb-14">
|
|
||||||
<div className="flex flex-col flex-1 justify-center h-full space-y-8">
|
|
||||||
<h1 className="font-heading text-4xl leading-none lg:leading-tight xl:text-5xl xl:leading-tight">
|
|
||||||
<span className="bg-gradient-to-br from-rebeccapurple-500 to-indigo-600 bg-clip-text decoration-clone text-transparent">
|
|
||||||
Take your phone number
|
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
<span className="text-[#24185B]">anywhere you go</span>
|
just got{" "}
|
||||||
|
<span className="relative whitespace-nowrap">
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 418 42"
|
||||||
|
className="absolute top-2/3 left-0 h-[0.58em] w-full fill-rebeccapurple-300/70"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
>
|
||||||
|
<path d="M203.371.916c-26.013-2.078-76.686 1.963-124.73 9.946L67.3 12.749C35.421 18.062 18.2 21.766 6.004 25.934 1.244 27.561.828 27.778.874 28.61c.07 1.214.828 1.121 9.595-1.176 9.072-2.377 17.15-3.92 39.246-7.496C123.565 7.986 157.869 4.492 195.942 5.046c7.461.108 19.25 1.696 19.17 2.582-.107 1.183-7.874 4.31-25.75 10.366-21.992 7.45-35.43 12.534-36.701 13.884-2.173 2.308-.202 4.407 4.442 4.734 2.654.187 3.263.157 15.593-.78 35.401-2.686 57.944-3.488 88.365-3.143 46.327.526 75.721 2.23 130.788 7.584 19.787 1.924 20.814 1.98 24.557 1.332l.066-.011c1.201-.203 1.53-1.825.399-2.335-2.911-1.31-4.893-1.604-22.048-3.261-57.509-5.556-87.871-7.36-132.059-7.842-23.239-.254-33.617-.116-50.627.674-11.629.54-42.371 2.494-46.696 2.967-2.359.259 8.133-3.625 26.504-9.81 23.239-7.825 27.934-10.149 28.304-14.005.417-4.348-3.529-6-16.878-7.066Z" />
|
||||||
|
</svg>
|
||||||
|
<span className="relative">easier</span>
|
||||||
|
</span>{" "}
|
||||||
|
!
|
||||||
</h1>
|
</h1>
|
||||||
|
<p className="mx-auto mt-6 max-w-2xl text-lg tracking-tight text-slate-700">
|
||||||
<p className="text-base lg:text-lg xl:text-xl text-black">
|
Coming soon, the personal cloud phone for digital nomads! Take your phone number anywhere you go 🌏
|
||||||
Coming soon! 🐚 Keep your phone number and pay less for your communications,
|
|
||||||
even abroad.
|
|
||||||
</p>
|
</p>
|
||||||
|
</Container>
|
||||||
<CTAForm />
|
|
||||||
|
|
||||||
<div className="max-w-lg mx-auto md:mx-0">
|
|
||||||
<span className="block md:inline mx-2">
|
|
||||||
<em>✓ </em>Free trial
|
|
||||||
</span>
|
|
||||||
<span className="block md:inline mx-2">
|
|
||||||
<em>✓ </em>No credit card required
|
|
||||||
</span>
|
|
||||||
<span className="block md:inline mx-2">
|
|
||||||
<em>✓ </em>Cancel anytime
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="relative w-full overflow-hidden lg:w-5/12 lg:order-1">
|
|
||||||
<div className="lg:absolute lg:bottom-0 lg:left-0">
|
|
||||||
<img className="w-full" src={mockupImage} alt="App screenshot on a phone" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,25 +0,0 @@
|
|||||||
import type { FunctionComponent, PropsWithChildren } from "react";
|
|
||||||
|
|
||||||
import BaseLayout from "./base-layout";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
title?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
const Layout: FunctionComponent<PropsWithChildren<Props>> = ({ children, title }) => (
|
|
||||||
<BaseLayout>
|
|
||||||
<section className="max-w-6xl mx-auto px-4 sm:px-6">
|
|
||||||
<div className="pt-32 pb-10 md:pt-34 md:pb-16">
|
|
||||||
{title ? (
|
|
||||||
<div className="max-w-5xl mx-auto">
|
|
||||||
<h1 className="h1 mb-16 text-navy font-extrabold font-mackinac">{title}</h1>
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="max-w-3xl mx-auto text-lg xl:text-xl flow-root">{children}</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</BaseLayout>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default Layout;
|
|
3
app/features/public-area/components/logo.tsx
Normal file
3
app/features/public-area/components/logo.tsx
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export default function Logo() {
|
||||||
|
return <img className="w-10 h-10" src="/shellphone.png" alt="Shellphone logo" />;
|
||||||
|
}
|
13
app/features/public-area/components/nav-link.tsx
Normal file
13
app/features/public-area/components/nav-link.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { PropsWithChildren } from "react";
|
||||||
|
import { Link } from "@remix-run/react";
|
||||||
|
|
||||||
|
export default function NavLink({ href, children }: PropsWithChildren<{ href: string }>) {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={href}
|
||||||
|
className="inline-block rounded-lg py-1 px-2 text-sm text-slate-700 hover:bg-slate-100 hover:text-slate-900"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
@ -1,36 +0,0 @@
|
|||||||
import { IoClose } from "react-icons/io5";
|
|
||||||
|
|
||||||
export default function ReferralBanner() {
|
|
||||||
// TODO
|
|
||||||
const isDisabled = true;
|
|
||||||
if (isDisabled) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="relative bg-rebeccapurple-600 z-40">
|
|
||||||
<div className="max-w-7xl mx-auto py-3 px-3 sm:px-6 lg:px-8">
|
|
||||||
<div className="pr-16 sm:text-center sm:px-16">
|
|
||||||
<p className="font-medium text-white">
|
|
||||||
<span>🎉 New: Get one month free for every friend that joins and subscribe!</span>
|
|
||||||
<span className="block sm:ml-2 sm:inline-block">
|
|
||||||
<a href="#" className="text-white font-bold underline">
|
|
||||||
{" "}
|
|
||||||
Learn more <span aria-hidden="true">→</span>
|
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="absolute inset-y-0 right-0 pt-1 pr-1 flex items-start sm:pt-1 sm:pr-2 sm:items-start">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="flex p-2 rounded-md hover:bg-rebeccapurple-500 focus:outline-none focus:ring-2 focus:ring-white"
|
|
||||||
>
|
|
||||||
<span className="sr-only">Dismiss</span>
|
|
||||||
<IoClose className="h-6 w-6 text-white" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
export default function Testimonials() {
|
|
||||||
return (
|
|
||||||
<div className="bg-rebeccapurple-600">
|
|
||||||
<div className="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:py-16 lg:px-8">
|
|
||||||
<p className="text-xl text-white text-center text-base font-semibold uppercase text-gray-600 tracking-wider">
|
|
||||||
Trusted by digital nomads in
|
|
||||||
<div className="h-[2rem] relative flex">
|
|
||||||
<span className="location">Bali</span>
|
|
||||||
<span className="location">Tulum</span>
|
|
||||||
<span className="location">Tbilissi</span>
|
|
||||||
<span className="location">Bansko</span>
|
|
||||||
<span className="location">Zanzibar</span>
|
|
||||||
<span className="location">Mauritius</span>
|
|
||||||
<span className="location">Amsterdam</span>
|
|
||||||
</div>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
BIN
app/features/public-area/images/background-call-to-action.jpg
Normal file
BIN
app/features/public-area/images/background-call-to-action.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 162 KiB |
BIN
app/features/public-area/images/background-faqs.jpg
Normal file
BIN
app/features/public-area/images/background-faqs.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
Before Width: | Height: | Size: 54 KiB |
Binary file not shown.
Before Width: | Height: | Size: 45 KiB |
Binary file not shown.
Before Width: | Height: | Size: 195 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.8 MiB |
@ -1,23 +1,17 @@
|
|||||||
import Header from "../components/header";
|
import Header from "../components/header";
|
||||||
import Footer from "../components/footer";
|
|
||||||
import ReferralBanner from "../components/referral-banner";
|
|
||||||
import Hero from "../components/hero";
|
import Hero from "../components/hero";
|
||||||
import FAQs from "../components/faqs";
|
import CallToAction from "../components/call-to-action";
|
||||||
|
import Faqs from "../components/faqs";
|
||||||
|
|
||||||
export default function IndexPage() {
|
export default function IndexPage() {
|
||||||
return (
|
return (
|
||||||
<section className="font-inter antialiased bg-white text-gray-900 tracking-tight">
|
<section className="flex h-full flex-col">
|
||||||
<section className="flex flex-col min-h-screen overflow-hidden">
|
|
||||||
<Header />
|
<Header />
|
||||||
|
<main>
|
||||||
<main className="flex-grow">
|
|
||||||
<ReferralBanner />
|
|
||||||
<Hero />
|
<Hero />
|
||||||
<FAQs />
|
<CallToAction />
|
||||||
|
<Faqs />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Footer />
|
|
||||||
</section>
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import type { LinksFunction, MetaFunction } from "@remix-run/node";
|
import type { LinksFunction, MetaFunction } from "@remix-run/node";
|
||||||
|
|
||||||
import IndexPage from "~/features/public-area/pages";
|
import joinWaitlistAction from "~/features/public-area/actions/index";
|
||||||
|
import IndexPage from "~/features/public-area/pages/index";
|
||||||
import { getSeoMeta } from "~/utils/seo";
|
import { getSeoMeta } from "~/utils/seo";
|
||||||
|
|
||||||
import styles from "../styles/index.css";
|
import styles from "../styles/index.css";
|
||||||
|
|
||||||
|
export const action = joinWaitlistAction;
|
||||||
|
|
||||||
export const meta: MetaFunction = () => ({
|
export const meta: MetaFunction = () => ({
|
||||||
...getSeoMeta({ title: "", description: "Welcome to Remixtape!" }),
|
...getSeoMeta({ title: "", description: "Welcome to Remixtape!" }),
|
||||||
});
|
});
|
||||||
|
12
app/utils/discord.server.ts
Normal file
12
app/utils/discord.server.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import config from "~/config/config.server";
|
||||||
|
|
||||||
|
const { webhookId, webhookToken } = config.discord;
|
||||||
|
|
||||||
|
export function executeWebhook(email: string) {
|
||||||
|
const url = `https://discord.com/api/webhooks/${webhookId}/${webhookToken}`;
|
||||||
|
return fetch(url, {
|
||||||
|
body: JSON.stringify({ content: `\`${email}\` just joined Shellphone's waitlist` }),
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
22
app/utils/mailchimp.server.ts
Normal file
22
app/utils/mailchimp.server.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import config from "~/config/config.server";
|
||||||
|
|
||||||
|
export async function addSubscriber(email: string) {
|
||||||
|
const { apiKey, audienceId } = config.mailchimp;
|
||||||
|
const region = apiKey.split("-")[1];
|
||||||
|
const url = `https://${region}.api.mailchimp.com/3.0/lists/${audienceId}/members`;
|
||||||
|
const data = {
|
||||||
|
email_address: email,
|
||||||
|
status: "subscribed",
|
||||||
|
};
|
||||||
|
const base64ApiKey = Buffer.from(`any:${apiKey}`).toString("base64");
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
Authorization: `Basic ${base64ApiKey}`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return fetch(url, {
|
||||||
|
body: JSON.stringify(data),
|
||||||
|
headers,
|
||||||
|
method: "post",
|
||||||
|
});
|
||||||
|
}
|
@ -56,3 +56,13 @@
|
|||||||
@apply font-mackinac tracking-tight font-bold;
|
@apply font-mackinac tracking-tight font-bold;
|
||||||
word-spacing: 0.025em;
|
word-spacing: 0.025em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.background-primary {
|
||||||
|
@apply bg-gradient-to-br from-rebeccapurple-500 to-indigo-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.landing-hero {
|
||||||
|
height: calc(100vh - 120px);
|
||||||
|
margin-top: -120px;
|
||||||
|
@apply flex flex-col justify-center;
|
||||||
|
}
|
||||||
|
@ -95,7 +95,7 @@ module.exports = {
|
|||||||
navy: "#24185B",
|
navy: "#24185B",
|
||||||
},
|
},
|
||||||
fontSize: {
|
fontSize: {
|
||||||
xs: ["0.75rem", { lineHeight: "1.5" }],
|
/*xs: ["0.75rem", { lineHeight: "1.5" }],
|
||||||
sm: ["0.875rem", { lineHeight: "1.5" }],
|
sm: ["0.875rem", { lineHeight: "1.5" }],
|
||||||
base: ["1rem", { lineHeight: "1.5" }],
|
base: ["1rem", { lineHeight: "1.5" }],
|
||||||
lg: ["1.125rem", { lineHeight: "1.5" }],
|
lg: ["1.125rem", { lineHeight: "1.5" }],
|
||||||
@ -104,10 +104,20 @@ module.exports = {
|
|||||||
"3xl": ["2.63rem", { lineHeight: "1.24" }],
|
"3xl": ["2.63rem", { lineHeight: "1.24" }],
|
||||||
"4xl": ["3.5rem", { lineHeight: "1.18" }],
|
"4xl": ["3.5rem", { lineHeight: "1.18" }],
|
||||||
"5xl": ["4rem", { lineHeight: "1.16" }],
|
"5xl": ["4rem", { lineHeight: "1.16" }],
|
||||||
"6xl": ["5.5rem", { lineHeight: "1.11" }],
|
"6xl": ["5.5rem", { lineHeight: "1.11" }],*/
|
||||||
},
|
xs: ["0.75rem", { lineHeight: "1rem" }],
|
||||||
boxShadow: {
|
sm: ["0.875rem", { lineHeight: "1.5rem" }],
|
||||||
"2xl": "0 25px 50px -12px rgba(0, 0, 0, 0.08)",
|
base: ["1rem", { lineHeight: "1.75rem" }],
|
||||||
|
lg: ["1.125rem", { lineHeight: "2rem" }],
|
||||||
|
xl: ["1.25rem", { lineHeight: "2rem" }],
|
||||||
|
"2xl": ["1.5rem", { lineHeight: "2rem" }],
|
||||||
|
"3xl": ["2rem", { lineHeight: "2.5rem" }],
|
||||||
|
"4xl": ["2.5rem", { lineHeight: "3.5rem" }],
|
||||||
|
"5xl": ["3rem", { lineHeight: "3.5rem" }],
|
||||||
|
"6xl": ["3.75rem", { lineHeight: "1" }],
|
||||||
|
"7xl": ["4.5rem", { lineHeight: "1.1" }],
|
||||||
|
"8xl": ["6rem", { lineHeight: "1" }],
|
||||||
|
"9xl": ["8rem", { lineHeight: "1" }],
|
||||||
},
|
},
|
||||||
outline: {
|
outline: {
|
||||||
blue: "2px solid rgba(0, 112, 244, 0.5)",
|
blue: "2px solid rgba(0, 112, 244, 0.5)",
|
||||||
@ -130,25 +140,6 @@ module.exports = {
|
|||||||
wider: "0.02em",
|
wider: "0.02em",
|
||||||
widest: "0.4em",
|
widest: "0.4em",
|
||||||
},
|
},
|
||||||
minWidth: {
|
|
||||||
10: "2.5rem",
|
|
||||||
},
|
|
||||||
scale: {
|
|
||||||
98: ".98",
|
|
||||||
},
|
|
||||||
animation: {
|
|
||||||
float: "float 5s ease-in-out infinite",
|
|
||||||
},
|
|
||||||
keyframes: {
|
|
||||||
float: {
|
|
||||||
"0%, 100%": { transform: "translateY(0)" },
|
|
||||||
"50%": { transform: "translateY(-10%)" },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
zIndex: {
|
|
||||||
"-1": "-1",
|
|
||||||
"-10": "-10",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
variants: {
|
variants: {
|
||||||
|
Loading…
Reference in New Issue
Block a user