14 Commits

Author SHA1 Message Date
b95515d7c0 Merge remote-tracking branch 'origin/main' into SR-002-Services-Section 2023-05-20 17:47:26 -05:00
Zeke
1ba4ddf869 Merge pull request #9 from zyrrus/003-Projects
Updated with responsive typography
2023-05-20 17:23:14 -05:00
d18a6ca60b Updated with responsive typography 2023-05-20 17:21:39 -05:00
Zeke
c7cae41abf Merge pull request #8 from zyrrus/009-Add-responsive-typography-classes
009 add responsive typography classes
2023-05-20 16:57:28 -05:00
f809abcf6d SR-009: Created responsive typography plugin 2023-05-20 16:54:46 -05:00
Zeke
eb44659f7d Merge pull request #6 from zyrrus/003-Projects
003 projects
2023-05-12 00:34:47 -05:00
317081841c Finished a first pass of the project section 2023-05-12 00:31:11 -05:00
8f738b0ad2 Partially implemented the projects section 2023-05-11 12:28:16 -05:00
da5159aa48 Started writing responsive typography classes 2023-04-30 20:02:45 -05:00
BlackDives
f09f6bfdf1 SR-002: Background change 2023-04-30 13:06:35 -05:00
BlackDives
5f1cdfda5b SR-002: Responsive styling
Made the services section responsive. Fixed some text as well and changed the border color under each service.
2023-04-30 12:29:40 -05:00
BlackDives
0aacc551d6 SR-002: Fixing font sizes 2023-04-30 12:04:11 -05:00
BlackDives
adeedd3570 SP-002: Fixed layout styles 2023-04-30 04:02:45 -05:00
BlackDives
9804acf49b SR-002: Basic layout created 2023-04-30 03:00:17 -05:00
9 changed files with 319 additions and 7 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,5 +1,10 @@
import { type NextPage } from "next";
import Head from "next/head";
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 (
@@ -11,8 +16,8 @@ const Home: NextPage = () => {
</Head>
<>
<Hero />
{/* <Services /> */}
{/* <Projects /> */}
<Services />
<Projects />
{/* <About /> */}
{/* <Contact /> */}
</>
@@ -25,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{" "}
@@ -35,7 +40,100 @@ const Hero = () => {
</>
);
};
// const Services = () => {}
// const Projects = () => {}
const Services = () => {
const Services = [
{
id: 1,
icon: "web.svg",
borderColor: "border-orange/20",
title: "Websites",
description:
"Fast. Responsive. Accessible.\nWe specialize in building high-performing websites without leaving any users behind. ",
},
{
id: 2,
icon: "mobile.svg",
borderColor: "border-green/20",
title: "Mobile Apps",
description:
"We build cross-platform mobile apps. Lorem ipsum dolor sit amet consectetur adipiscing elit.",
},
{
id: 3,
icon: "design.svg",
borderColor: "border-red/20",
title: "UI/UX Design",
description:
"First impressions matter. We know how to craft unique and impressive digital experiences.",
},
];
return (
<div className="my-10 bg-sand">
<Container>
<div>
<h2 className="text-center font-extrabold text-r-2xl sm:text-left">
What we can do for you
</h2>
</div>
<div className="flex flex-col sm:flex-col md:flex-row">
{Services.map((data) => (
<div
key={data.id}
className="my-2 flex w-full flex-col items-center justify-start sm:w-1/3"
>
<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}`} />
{data.description.split("\n").map((line) => (
<p key={line} className="w-3/4 text-center text-r-base">
{line}
</p>
))}
</div>
))}
</div>
</Container>
</div>
);
};
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;