Merge remote-tracking branch 'origin/main' into SR-002-Services-Section

This commit is contained in:
2023-05-20 17:47:26 -05:00
9 changed files with 276 additions and 25 deletions

View File

@@ -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

View File

@@ -0,0 +1,5 @@
import type { PropsWithChildren } from "react";
export const Container: React.FC<PropsWithChildren> = ({ children }) => {
return <div className="container">{children}</div>;
};

View 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>
);
};

View 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>
);
};

View File

@@ -10,6 +10,7 @@ const roadster = localFont({
preload: true,
variable: "--font-roadster",
});
const jakarta = Plus_Jakarta_Sans({
subsets: ["latin"],
weight: ["400", "600", "800"],

29
src/constants/projects.ts Normal file
View 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. ",
},
];

View 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;

View File

@@ -1,12 +1,10 @@
import { type NextPage } from "next";
import Head from "next/head";
import Web from "../../public/icons/web.svg";
import Mobile from "../../public/icons/mobile.svg";
// import Design from "../../public/icons/design.svg";
interface BorderProps {
color: string;
}
import Image from "next/image";
import { Container } from "~/components/Container";
import { Polaroid } from "~/components/Polaroid";
import { ProjectDescription } from "~/components/ProjectDescription";
import { projects } from "~/constants/projects";
const Home: NextPage = () => {
return (
@@ -19,7 +17,7 @@ const Home: NextPage = () => {
<>
<Hero />
<Services />
{/* <Projects /> */}
<Projects />
{/* <About /> */}
{/* <Contact /> */}
</>
@@ -32,8 +30,8 @@ export default Home;
const Hero = () => {
return (
<>
<h1 className="py-3 font-roadster text-5xl text-black">Sunrise</h1>
<p className="leading-relaxed text-black">
<h1 className="py-3 font-roadster text-black text-r-3xl">Sunrise</h1>
<p className="leading-relaxed text-black text-r-xl">
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{" "}
@@ -42,20 +40,21 @@ const Hero = () => {
</>
);
};
const Services = () => {
const Services = [
{
id: 1,
icon: "web.svg",
borderColor: "border-[#F4A261]",
borderColor: "border-orange/20",
title: "Websites",
description:
"Fast. Responsive. Accessible. We specialize in building high-performing websites without leaving any users behind. ",
"Fast. Responsive. Accessible.\nWe specialize in building high-performing websites without leaving any users behind. ",
},
{
id: 2,
icon: "mobile.svg",
borderColor: "border-[#2A9D8F]",
borderColor: "border-green/20",
title: "Mobile Apps",
description:
"We build cross-platform mobile apps. Lorem ipsum dolor sit amet consectetur adipiscing elit.",
@@ -63,7 +62,7 @@ const Services = () => {
{
id: 3,
icon: "design.svg",
borderColor: "border-[#E76F51]",
borderColor: "border-red/20",
title: "UI/UX Design",
description:
"First impressions matter. We know how to craft unique and impressive digital experiences.",
@@ -71,10 +70,10 @@ const Services = () => {
];
return (
<div className=" my-10 bg-[#F0D4B2]">
<div className="container mx-auto">
<div className="my-10 bg-sand">
<Container>
<div>
<h2 className="text-center text-[32px] font-extrabold sm:text-left sm:text-[40px]">
<h2 className="text-center font-extrabold text-r-2xl sm:text-left">
What we can do for you
</h2>
</div>
@@ -84,19 +83,57 @@ const Services = () => {
key={data.id}
className="my-2 flex w-full flex-col items-center justify-start sm:w-1/3"
>
<img src={`icons/${data.icon}`} alt="service-icon" />
<h3 className="text-[32px] font-semibold">{data.title}</h3>
<Image
src={`icons/${data.icon}`}
alt="service-icon"
width={150}
height={150}
/>
<h3 className="font-semibold text-r-xl">{data.title}</h3>
<div className={`my-5 w-3/4 border-2 ${data.borderColor}`} />
<p className="w-3/4 text-center text-[20px] ">
{data.description}
{data.description.split("\n").map((line) => (
<p key={line} className="w-3/4 text-center text-r-base">
{line}
</p>
))}
</div>
))}
</div>
</div>
</Container>
</div>
);
};
// const Projects = () => {}
const Projects = () => {
return (
<Container>
<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 = () => {}

View File

@@ -1,5 +1,11 @@
import { type Config } from "tailwindcss";
import { fontFamily } from "tailwindcss/defaultTheme";
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[]>;
export default {
content: ["./src/**/*.{js,ts,jsx,tsx}"],
@@ -9,6 +15,15 @@ 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)"],
roadster: ["var(--font-roadster)", ...fontFamily.sans],
@@ -21,9 +36,46 @@ export default {
orange: "#F4A261",
red: "#E76F51",
white: "#EAD9C2",
sand: "#F0D4B2",
black: "#2E251E",
},
},
},
plugins: [],
plugins: [
// Responsive Typography
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"),
});
}),
],
} satisfies Config;