Compare commits
13 Commits
SR-010-Cre
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fcd7928da | |||
|
|
18b00fd9e8 | ||
| 4aa3536b20 | |||
| d00b3b7b24 | |||
| cbb7b0052b | |||
|
|
1ba4ddf869 | ||
| d18a6ca60b | |||
|
|
c7cae41abf | ||
| f809abcf6d | |||
|
|
eb44659f7d | ||
| 317081841c | |||
| 8f738b0ad2 | |||
| da5159aa48 |
@@ -7,6 +7,16 @@ await import("./src/env.mjs");
|
||||
/** @type {import("next").NextConfig} */
|
||||
const config = {
|
||||
reactStrictMode: true,
|
||||
images: {
|
||||
remotePatterns: [
|
||||
{
|
||||
protocol: "https",
|
||||
hostname: "picsum.photos",
|
||||
port: "",
|
||||
pathname: "/**",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
/**
|
||||
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
|
||||
|
||||
71
package-lock.json
generated
71
package-lock.json
generated
@@ -8,14 +8,17 @@
|
||||
"name": "sunrise",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^1.0.0-beta.6",
|
||||
"@radix-ui/react-separator": "^1.0.2",
|
||||
"@t3-oss/env-nextjs": "^0.2.1",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"framer-motion": "^10.12.4",
|
||||
"next": "^13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^4.9.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -130,6 +133,11 @@
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@formkit/auto-animate": {
|
||||
"version": "1.0.0-beta.6",
|
||||
"resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.6.tgz",
|
||||
"integrity": "sha512-PVDhLAlr+B4Xb7e+1wozBUWmXa6BFU8xUPR/W/E+TsQhPS1qkAdAsJ25keEnFrcePSnXHrOsh3tiFbEToOzV9w=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
@@ -1210,11 +1218,38 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/class-variance-authority": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.0.tgz",
|
||||
"integrity": "sha512-qdRDgfjx3GRb9fpwpSvn+YaidnT7IUJNe4wt5/SWwM+PmUwJUhQRk/8zAyNro0PmVfmen2635UboTjIBXXxy5A==",
|
||||
"dependencies": {
|
||||
"clsx": "1.2.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://joebell.co.uk"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.5.5 < 6"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"node_modules/clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -3848,6 +3883,14 @@
|
||||
"react-dom": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/react-icons": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz",
|
||||
"integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==",
|
||||
"peerDependencies": {
|
||||
"react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -4463,7 +4506,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||
"dev": true,
|
||||
"devOptional": true,
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
@@ -4717,6 +4760,11 @@
|
||||
"integrity": "sha512-kf9RB0Fg7NZfap83B3QOqOGg9QmD9yBudqQXzzOtn3i4y7ZUXe5ONeW34Gwi+TxhH4mvj72R1Zc300KUMa9Bng==",
|
||||
"dev": true
|
||||
},
|
||||
"@formkit/auto-animate": {
|
||||
"version": "1.0.0-beta.6",
|
||||
"resolved": "https://registry.npmjs.org/@formkit/auto-animate/-/auto-animate-1.0.0-beta.6.tgz",
|
||||
"integrity": "sha512-PVDhLAlr+B4Xb7e+1wozBUWmXa6BFU8xUPR/W/E+TsQhPS1qkAdAsJ25keEnFrcePSnXHrOsh3tiFbEToOzV9w=="
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.11.8",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz",
|
||||
@@ -5434,11 +5482,24 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"class-variance-authority": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.6.0.tgz",
|
||||
"integrity": "sha512-qdRDgfjx3GRb9fpwpSvn+YaidnT7IUJNe4wt5/SWwM+PmUwJUhQRk/8zAyNro0PmVfmen2635UboTjIBXXxy5A==",
|
||||
"requires": {
|
||||
"clsx": "1.2.1"
|
||||
}
|
||||
},
|
||||
"client-only": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
|
||||
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
|
||||
},
|
||||
"clsx": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
|
||||
"integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
@@ -7256,6 +7317,12 @@
|
||||
"goober": "^2.1.10"
|
||||
}
|
||||
},
|
||||
"react-icons": {
|
||||
"version": "4.9.0",
|
||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.9.0.tgz",
|
||||
"integrity": "sha512-ijUnFr//ycebOqujtqtV9PFS7JjhWg0QU6ykURVHuL4cbofvRCf3f6GMn9+fBktEFQOIVZnuAYLZdiyadRQRFg==",
|
||||
"requires": {}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
@@ -7700,7 +7767,7 @@
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz",
|
||||
"integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==",
|
||||
"dev": true
|
||||
"devOptional": true
|
||||
},
|
||||
"unbox-primitive": {
|
||||
"version": "1.0.2",
|
||||
|
||||
@@ -9,14 +9,17 @@
|
||||
"start": "next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@formkit/auto-animate": "^1.0.0-beta.6",
|
||||
"@radix-ui/react-separator": "^1.0.2",
|
||||
"@t3-oss/env-nextjs": "^0.2.1",
|
||||
"class-variance-authority": "^0.6.0",
|
||||
"framer-motion": "^10.12.4",
|
||||
"next": "^13.2.4",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-hook-form": "^7.43.9",
|
||||
"react-hot-toast": "^2.4.1",
|
||||
"react-icons": "^4.9.0",
|
||||
"zod": "^3.21.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
BIN
public/images/ribbon.webp
Normal file
BIN
public/images/ribbon.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 B |
@@ -1,4 +1,4 @@
|
||||
<svg width="1920" height="236" viewBox="0 0 1920 236" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<svg width="100%" height="100%" preserveAspectRatio="none" viewBox="0 0 1920 236" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_57_333)">
|
||||
<path d="M0 160.48L40.0593 146.556C80.237 132.514 160.356 104.666 240.356 87.32C320.356 69.974 400.237 63.366 480.237 81.42C560.237 99.474 640.356 142.426 720.356 150.45C800.356 158.474 880.237 131.806 960.237 131.806C1040.24 131.806 1120.36 158.474 1200.36 171.454C1280.36 184.434 1360.24 183.726 1440.24 165.2C1520.24 146.674 1600.36 110.566 1680.36 102.896C1760.36 95.226 1840.24 115.994 1880.06 126.496L1920 136.88V237.18H1880.06C1840.24 237.18 1760.36 237.18 1680.36 237.18C1600.36 237.18 1520.24 237.18 1440.24 237.18C1360.24 237.18 1280.36 237.18 1200.36 237.18C1120.36 237.18 1040.24 237.18 960.237 237.18C880.237 237.18 800.356 237.18 720.356 237.18C640.356 237.18 560.237 237.18 480.237 237.18C400.237 237.18 320.356 237.18 240.356 237.18C160.356 237.18 80.237 237.18 40.0593 237.18H0V160.48Z" fill="#F0D4B2"/>
|
||||
</g>
|
||||
|
||||
|
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.1 KiB |
BIN
public/images/sunrise-display.png
Normal file
BIN
public/images/sunrise-display.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
25
src/components/Button.tsx
Normal file
25
src/components/Button.tsx
Normal file
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { cva } from "class-variance-authority";
|
||||
|
||||
const button = cva([
|
||||
"flex flex-row items-center gap-x-2 rounded-full px-6 py-4",
|
||||
"text-r-lg font-semibold text-black",
|
||||
"bg-sunrise shadow-md",
|
||||
"hover:bg-sunrise/80",
|
||||
]);
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
href?: string;
|
||||
}
|
||||
|
||||
export const Button: React.FC<Props> = ({ children, onClick, href }) => {
|
||||
const Wrapper = href ? "a" : "button";
|
||||
|
||||
return (
|
||||
<Wrapper className={button()} href={href} onClick={onClick}>
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
11
src/components/Container.tsx
Normal file
11
src/components/Container.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
import React, { type PropsWithChildren } from "react";
|
||||
|
||||
interface Props extends PropsWithChildren {
|
||||
component?: keyof React.ReactHTML; // | React.ReactNode; ?
|
||||
}
|
||||
|
||||
export const Container: React.FC<Props> = ({ component, children }) => {
|
||||
const Comp = component ?? "div";
|
||||
|
||||
return <Comp className="container">{children}</Comp>;
|
||||
};
|
||||
48
src/components/Polaroid.tsx
Normal file
48
src/components/Polaroid.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import Image from "next/image";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import useIntersectionObserver from "~/hooks/useIntersectionObserver";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
imageSrc: string;
|
||||
rotation: string;
|
||||
}
|
||||
|
||||
export const Polaroid: React.FC<Props> = ({ title, imageSrc, rotation }) => {
|
||||
const [showImage, setShowImage] = useState(false);
|
||||
const ref = useRef<HTMLDivElement | null>(null);
|
||||
const entry = useIntersectionObserver(ref, {});
|
||||
|
||||
const isOnScreen = !!entry?.isIntersecting;
|
||||
|
||||
useEffect(() => {
|
||||
if (isOnScreen) {
|
||||
setShowImage(true);
|
||||
}
|
||||
}, [isOnScreen]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`m-5 w-52 rounded bg-[#F8F2EA] text-black shadow-md sm:w-64 lg:w-80 ${rotation}`}
|
||||
>
|
||||
<div className="flex h-full w-full flex-col items-center justify-between gap-y-3 p-4 sm:gap-y-4 sm:p-5 lg:gap-y-5 lg:p-6">
|
||||
<div className="relative aspect-square w-full">
|
||||
<Image
|
||||
src={imageSrc}
|
||||
alt={`Showcase of ${title}`}
|
||||
fill
|
||||
className="object-cover shadow-inner"
|
||||
/>
|
||||
<div
|
||||
className={`absolute inset-0 transition-colors duration-1000 ${
|
||||
showImage ? "bg-transparent" : "bg-[#3D3D3D]"
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<span ref={ref} className="font-semibold text-r-lg">
|
||||
{title}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
24
src/components/ProjectDescription.tsx
Normal file
24
src/components/ProjectDescription.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import * as Separator from "@radix-ui/react-separator";
|
||||
|
||||
export interface Props {
|
||||
title: string;
|
||||
color: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const ProjectDescription: React.FC<Props> = ({
|
||||
title,
|
||||
color,
|
||||
children,
|
||||
}) => {
|
||||
return (
|
||||
<div className="max-w-4xl text-black">
|
||||
<h3 className="font-semibold text-r-xl">{title}</h3>
|
||||
<Separator.Root
|
||||
className={`${color} my-2 h-1 w-full rounded-full`}
|
||||
decorative
|
||||
/>
|
||||
<p className="leading-relaxed text-r-base">{children}</p>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -10,6 +10,7 @@ const roadster = localFont({
|
||||
preload: true,
|
||||
variable: "--font-roadster",
|
||||
});
|
||||
|
||||
const jakarta = Plus_Jakarta_Sans({
|
||||
subsets: ["latin"],
|
||||
weight: ["400", "600", "800"],
|
||||
@@ -22,9 +23,7 @@ export const RootLayout: React.FC<PropsWithChildren> = ({ children }) => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main
|
||||
className={`${jakarta.variable} ${roadster.variable} min-h-screen bg-white font-sans`}
|
||||
>
|
||||
<main className={`${jakarta.variable} ${roadster.variable} font-sans`}>
|
||||
{children}
|
||||
</main>
|
||||
<Footer />
|
||||
|
||||
29
src/constants/projects.ts
Normal file
29
src/constants/projects.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import type { Props as PolaroidProps } from "~/components/Polaroid";
|
||||
import type { Props as ProjectDescriptionProps } from "~/components/ProjectDescription";
|
||||
|
||||
export const projects: Array<PolaroidProps & ProjectDescriptionProps> = [
|
||||
{
|
||||
title: "Parallel",
|
||||
color: "bg-orange",
|
||||
imageSrc: "https://picsum.photos/seed/Parallel/200",
|
||||
rotation: "-rotate-12",
|
||||
children:
|
||||
"We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ",
|
||||
},
|
||||
{
|
||||
title: "DropNote",
|
||||
color: "bg-yellow",
|
||||
imageSrc: "https://picsum.photos/seed/DropNote/200",
|
||||
rotation: "rotate-6",
|
||||
children:
|
||||
"We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ",
|
||||
},
|
||||
{
|
||||
title: "Flurry Waitlist",
|
||||
color: "bg-green",
|
||||
imageSrc: "https://picsum.photos/seed/Flurry/200",
|
||||
rotation: "-rotate-3",
|
||||
children:
|
||||
"We started Parallel to help connect educators with content creators to make it easier to create high-quality educational content for platforms like YouTube. We built the platform with high performance, scalability, and accessibility in mind. That's why we chose powerful tools like Next.js, TailwindCSS, RadixUI, Prisma, PlanetScale, and Vercel's hosting platform. ",
|
||||
},
|
||||
];
|
||||
45
src/hooks/useIntersectionObserver.ts
Normal file
45
src/hooks/useIntersectionObserver.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { type RefObject, useEffect, useState } from "react";
|
||||
|
||||
interface Args extends IntersectionObserverInit {
|
||||
freezeOnceVisible?: boolean;
|
||||
}
|
||||
|
||||
function useIntersectionObserver(
|
||||
elementRef: RefObject<Element>,
|
||||
{
|
||||
threshold = 0,
|
||||
root = null,
|
||||
rootMargin = "0%",
|
||||
freezeOnceVisible = false,
|
||||
}: Args
|
||||
): IntersectionObserverEntry | undefined {
|
||||
const [entry, setEntry] = useState<IntersectionObserverEntry>();
|
||||
|
||||
const frozen = entry?.isIntersecting && freezeOnceVisible;
|
||||
|
||||
const updateEntry = ([entry]: IntersectionObserverEntry[]): void => {
|
||||
setEntry(entry);
|
||||
};
|
||||
|
||||
const stringifiedThreshold = JSON.stringify(threshold);
|
||||
|
||||
useEffect(() => {
|
||||
const node = elementRef?.current; // DOM Ref
|
||||
const hasIOSupport = !!window.IntersectionObserver;
|
||||
|
||||
if (!hasIOSupport || frozen || !node) return;
|
||||
|
||||
const observerParams = { threshold, root, rootMargin };
|
||||
const observer = new IntersectionObserver(updateEntry, observerParams);
|
||||
|
||||
observer.observe(node);
|
||||
|
||||
return () => observer.disconnect();
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [elementRef?.current, stringifiedThreshold, root, rootMargin, frozen]);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
export default useIntersectionObserver;
|
||||
@@ -7,7 +7,7 @@ import "~/styles/globals.css";
|
||||
const MyApp: AppType = ({ Component, pageProps }) => {
|
||||
return (
|
||||
<RootLayout>
|
||||
<Toaster position="top-center" />
|
||||
<Toaster position="bottom-right" />
|
||||
<Component {...pageProps} />
|
||||
</RootLayout>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
import { cva, cx } from "class-variance-authority";
|
||||
import { type NextPage } from "next";
|
||||
import Head from "next/head";
|
||||
import Image from "next/image";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Button } from "~/components/Button";
|
||||
import { Container } from "~/components/Container";
|
||||
import { Polaroid } from "~/components/Polaroid";
|
||||
import { ProjectDescription } from "~/components/ProjectDescription";
|
||||
import { projects } from "~/constants/projects";
|
||||
import { MdArrowCircleDown, MdConstruction } from "react-icons/md";
|
||||
import { toast } from "react-hot-toast";
|
||||
|
||||
const Home: NextPage = () => {
|
||||
// Show toast message to indicate that we're under development
|
||||
useEffect(() => {
|
||||
toast(
|
||||
"Attention: Early Visitors!\n\nWe're currently in development mode, so please bear with us while we work hard to bring you an amazing experience.",
|
||||
{
|
||||
icon: <MdConstruction size="10rem" className="text-orange" />,
|
||||
duration: Infinity,
|
||||
className: "border-l-orange border-l-4",
|
||||
}
|
||||
);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
@@ -12,7 +34,7 @@ const Home: NextPage = () => {
|
||||
<>
|
||||
<Hero />
|
||||
{/* <Services /> */}
|
||||
{/* <Projects /> */}
|
||||
<Projects />
|
||||
{/* <About /> */}
|
||||
{/* <Contact /> */}
|
||||
</>
|
||||
@@ -22,20 +44,114 @@ const Home: NextPage = () => {
|
||||
|
||||
export default Home;
|
||||
|
||||
const ribbon = cva("relative w-full transition-all duration-500", {
|
||||
variants: {
|
||||
state: {
|
||||
ribbon: "h-40 top-[20vh]",
|
||||
sunrise: "h-[95vh] top-[5vh]",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const Hero = () => {
|
||||
// Transform ribbon into sunrise on scroll
|
||||
const [showSunrise, setShowSunrise] = useState(false);
|
||||
useEffect(() => {
|
||||
const onScroll = () => {
|
||||
const showSunriseThreshold_vh = 5;
|
||||
const showSunriseThreshold_px =
|
||||
(window.innerHeight * showSunriseThreshold_vh) / 100;
|
||||
|
||||
setShowSunrise(window.pageYOffset > showSunriseThreshold_px);
|
||||
};
|
||||
|
||||
window.addEventListener("scroll", onScroll);
|
||||
return () => window.removeEventListener("scroll", onScroll);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="py-3 font-roadster text-5xl text-black">Sunrise</h1>
|
||||
<p className="leading-relaxed text-black">
|
||||
Creating software that looks and works great is our specialty at
|
||||
Sunrise. Our team of experts combines artistry and technical know-how to
|
||||
craft solutions that will make your business{" "}
|
||||
<span className="font-semibold text-orange">shine</span>.
|
||||
</p>
|
||||
<div className="relative min-h-screen w-full">
|
||||
<div className="flex flex-col items-center">
|
||||
<div className="absolute inset-0 -z-10">
|
||||
<div
|
||||
className={ribbon({ state: showSunrise ? "sunrise" : "ribbon" })}
|
||||
>
|
||||
<Image
|
||||
src="/images/ribbon.webp"
|
||||
alt="Sunrise background banner"
|
||||
className={cx(
|
||||
"transition-all duration-500",
|
||||
showSunrise ? "" : "shadow-lg scale-pixelated"
|
||||
)}
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mt-[20vh] h-40 w-full">
|
||||
{/* TODO: Create + replace with .svg */}
|
||||
<Image
|
||||
src="/images/sunrise-display.png"
|
||||
alt="Sunrise Software"
|
||||
className="object-contain px-4 py-2"
|
||||
fill
|
||||
/>
|
||||
</div>
|
||||
<p className="mb-8 mt-5 max-w-xl text-center leading-relaxed text-black text-r-lg sm:mb-20 sm:mt-8 md:max-w-2xl lg:mb-32 lg:mt-11 2xl:max-w-4xl">
|
||||
Creating software that looks and works great is our specialty at
|
||||
Sunrise.
|
||||
<br />
|
||||
Our team of experts combines artistry and technical know-how to
|
||||
craft solutions that will make your business{" "}
|
||||
<span className="font-semibold text-orange">shine</span>.
|
||||
</p>
|
||||
{/* TODO: Make functional + more interesting */}
|
||||
<Button>
|
||||
Check out our work <MdArrowCircleDown size="1.25em" />
|
||||
</Button>
|
||||
</div>
|
||||
<div className="absolute bottom-0 w-full">
|
||||
<div className="md: relative h-16 w-full sm:h-20 md:h-32 xl:h-56 2xl:h-72">
|
||||
<Image src="/images/sand-top-1.svg" alt="sand" fill />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// const Services = () => {}
|
||||
// const Projects = () => {}
|
||||
|
||||
const Projects = () => {
|
||||
return (
|
||||
<Container component="section">
|
||||
<h2 className="my-10 text-center font-extrabold text-black text-r-2xl md:mt-16">
|
||||
Here are some of our past projects
|
||||
</h2>
|
||||
<div className="pb-20 [&>*:nth-child(odd)]:md:flex-row">
|
||||
{projects.map((p) => (
|
||||
<div
|
||||
key={p.title}
|
||||
className="mb-10 flex flex-col items-center gap-x-12 gap-y-6 md:flex-row-reverse lg:my-0"
|
||||
>
|
||||
<div>
|
||||
<Polaroid
|
||||
title={p.title}
|
||||
imageSrc={p.imageSrc}
|
||||
rotation={p.rotation}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<ProjectDescription title={p.title} color={p.color}>
|
||||
{p.children}
|
||||
</ProjectDescription>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
};
|
||||
|
||||
// const About = () => {}
|
||||
// const Contact = () => {}
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
html {
|
||||
@apply bg-white;
|
||||
}
|
||||
|
||||
11
src/styles/pixelPerfectScalingPlugin.ts
Normal file
11
src/styles/pixelPerfectScalingPlugin.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
module.exports = plugin(function ({ addUtilities }) {
|
||||
const utilities = {
|
||||
".scale-pixelated": {
|
||||
imageRendering: "pixelated",
|
||||
},
|
||||
};
|
||||
|
||||
addUtilities(utilities);
|
||||
});
|
||||
41
src/styles/responsiveTextPlugin.ts
Normal file
41
src/styles/responsiveTextPlugin.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import plugin from "tailwindcss/plugin";
|
||||
|
||||
interface RecursiveKeyValuePair<K extends keyof any = string, V = string> {
|
||||
[key: string]: V | RecursiveKeyValuePair<K, V>;
|
||||
}
|
||||
type CSSRuleObject = RecursiveKeyValuePair<string, null | string | string[]>;
|
||||
|
||||
module.exports = plugin(function ({ addUtilities, theme }) {
|
||||
const fontSizes = Object.keys(theme("fontSize"));
|
||||
|
||||
const buildCSS = (fontSize: string): CSSRuleObject => {
|
||||
const fsIndex = fontSizes.indexOf(fontSize);
|
||||
|
||||
if (fsIndex > 1) {
|
||||
const desktop = fontSize;
|
||||
const tablet = fontSizes[fsIndex - 1] ?? fontSize;
|
||||
const mobile = fontSizes[fsIndex - 2] ?? fontSize;
|
||||
|
||||
return {
|
||||
"font-size": theme(`fontSize.${mobile}`),
|
||||
"@screen md": {
|
||||
"font-size": theme(`fontSize.${tablet}`),
|
||||
},
|
||||
"@screen 2xl": {
|
||||
"font-size": theme(`fontSize.${desktop}`),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return { "font-size": theme(`fontSize.${fontSize}`) };
|
||||
};
|
||||
|
||||
addUtilities({
|
||||
".text-r-3xl": buildCSS("3xl"),
|
||||
".text-r-2xl": buildCSS("2xl"),
|
||||
".text-r-xl": buildCSS("xl"),
|
||||
".text-r-lg": buildCSS("lg"),
|
||||
".text-r-base": buildCSS("base"),
|
||||
".text-r-sm": buildCSS("sm"),
|
||||
});
|
||||
});
|
||||
@@ -9,8 +9,17 @@ export default {
|
||||
center: true,
|
||||
padding: "1.5rem",
|
||||
},
|
||||
fontSize: {
|
||||
xs: ["0.875rem", { lineHeight: "1rem" }], // 14px 0.875rem
|
||||
sm: ["1rem", { lineHeight: "1.25rem" }], // 16px 1rem
|
||||
base: ["1.25rem", { lineHeight: "1.5rem" }], // 20px 1.25rem
|
||||
lg: ["1.5rem", { lineHeight: "1.75rem" }], // 24px 1.5rem
|
||||
xl: ["2rem", { lineHeight: "1.75rem" }], // 32px 2rem
|
||||
"2xl": ["2.5rem", { lineHeight: "2rem" }], // 40px 2.5rem
|
||||
"3xl": ["3rem", { lineHeight: "2.25rem" }], // 48px 3rem
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-jakarta)"],
|
||||
sans: ["var(--font-jakarta)", ...fontFamily.sans],
|
||||
roadster: ["var(--font-roadster)", ...fontFamily.sans],
|
||||
},
|
||||
colors: {
|
||||
@@ -25,5 +34,8 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [
|
||||
require("./src/styles/responsiveTextPlugin.ts"),
|
||||
require("./src/styles/pixelPerfectScalingPlugin.ts"),
|
||||
],
|
||||
} satisfies Config;
|
||||
|
||||
Reference in New Issue
Block a user