Skip to content

Commit f2a35af

Browse files
Add companies teaser, grain pricing cards, and a full-width hide-on-scroll nav.
Co-authored-by: Cursor <cursoragent@cursor.com>
1 parent 4cca18c commit f2a35af

12 files changed

Lines changed: 285 additions & 22 deletions

File tree

public/media/companies-portal.jpg

106 KB
Loading

src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useEffect } from "react"
22
import { Route, Routes, useLocation } from "react-router-dom"
33
import { LandingReveal, OpeningProvider } from "./components/OpeningScreen"
44
import { PageShell } from "./components/PageShell"
5+
import { CompaniesPage } from "./pages/CompaniesPage"
56
import { DesignsPage } from "./pages/DesignsPage"
67
import { DirectoryPage } from "./pages/DirectoryPage"
78
import { HelpPage } from "./pages/HelpPage"
@@ -35,6 +36,7 @@ export default function App() {
3536
<Routes>
3637
<Route element={<PageShell />}>
3738
<Route path="/" element={<HomePage />} />
39+
<Route path="/companies" element={<CompaniesPage />} />
3840
<Route path="/designs" element={<DesignsPage />} />
3941
<Route path="/directory" element={<DirectoryPage />} />
4042
<Route path="/help" element={<HelpPage />} />

src/components/CompaniesTeaser.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { Link } from "react-router-dom"
2+
import { motion } from "framer-motion"
3+
import { fadeUp } from "../motion/variants"
4+
import { withBase } from "../lib/base"
5+
6+
export function CompaniesTeaser() {
7+
return (
8+
<section className="bg-canvas-dim px-5 py-20 md:px-10 md:py-24">
9+
<motion.div
10+
initial="hidden"
11+
whileInView="show"
12+
viewport={{ once: true, amount: 0.35 }}
13+
variants={fadeUp}
14+
className="mx-auto max-w-[1440px]"
15+
>
16+
<div className="flex flex-col gap-8 md:flex-row md:items-end md:justify-between">
17+
<div className="max-w-xl">
18+
<h2 className="font-display text-[clamp(2.1rem,4.6vw,4.4rem)] leading-[0.95] tracking-[-0.045em] lowercase">
19+
companies portal.
20+
</h2>
21+
<p className="mt-5 text-[15px] leading-relaxed text-ink-soft">
22+
Issue branded cards, keep team profiles current, and send people to
23+
your company — not a stack of paper.
24+
</p>
25+
</div>
26+
<Link
27+
to="/companies"
28+
className="inline-flex min-h-10 shrink-0 items-center border border-ink/40 px-5 text-[11px] tracking-[0.14em] text-ink uppercase transition-colors hover:bg-ink/5 md:mb-1"
29+
>
30+
Open portal
31+
</Link>
32+
</div>
33+
34+
<img
35+
src={withBase("/media/companies-portal.jpg")}
36+
alt=""
37+
className="mt-12 h-[min(52vh,420px)] w-full rounded-[2.75rem] object-cover md:mt-16 md:rounded-[3.5rem]"
38+
/>
39+
</motion.div>
40+
</section>
41+
)
42+
}

src/components/ContactSection.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export function ContactSection({ heading = "Let's talk" }: ContactSectionProps)
9393
</label>
9494
<button
9595
type="submit"
96-
className="shine-hover mt-4 inline-flex min-h-12 w-fit items-center rounded-full bg-accent px-8 text-sm font-medium text-ink"
96+
className="mt-4 inline-flex min-h-12 w-fit items-center rounded-full bg-ink px-8 text-sm font-medium text-white transition-opacity hover:opacity-85"
9797
>
9898
Send message
9999
</button>

src/components/Footer.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export function Footer() {
2121
Why Deets
2222
</Link>
2323
</li>
24+
<li>
25+
<Link className="hover:text-magenta" to="/companies">
26+
Companies
27+
</Link>
28+
</li>
2429
<li>
2530
<Link className="hover:text-magenta" to="/designs">
2631
Designs
@@ -31,6 +36,11 @@ export function Footer() {
3136
Functions
3237
</Link>
3338
</li>
39+
<li>
40+
<Link className="hover:text-magenta" to="/#pricing">
41+
Pricing
42+
</Link>
43+
</li>
3444
<li>
3545
<a className="hover:text-magenta" href={STORE_URL}>
3646
Buy Now

src/components/PricingSection.tsx

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { Link } from "react-router-dom"
2+
import { motion } from "framer-motion"
3+
import { pricingPlans } from "../data/site"
4+
import { fadeUp, stagger } from "../motion/variants"
5+
6+
const ctaClass =
7+
"mt-10 inline-flex min-h-10 w-fit items-center border border-white/50 px-5 text-[11px] tracking-[0.14em] text-white uppercase transition-colors hover:bg-white/10"
8+
9+
export function PricingSection() {
10+
return (
11+
<section
12+
id="pricing"
13+
className="scroll-mt-24 bg-canvas px-5 py-20 md:px-10 md:py-28"
14+
>
15+
<motion.div
16+
initial="hidden"
17+
whileInView="show"
18+
viewport={{ once: true, amount: 0.4 }}
19+
variants={fadeUp}
20+
className="mx-auto max-w-[1440px] text-center"
21+
>
22+
<h2 className="font-display text-[clamp(2.1rem,4.6vw,4.4rem)] leading-[0.95] tracking-[-0.045em] lowercase">
23+
plans.
24+
</h2>
25+
<p className="mx-auto mt-5 max-w-md text-[15px] leading-relaxed text-ink-soft">
26+
One card, a pair, a team, or the whole company.
27+
</p>
28+
</motion.div>
29+
30+
<motion.ul
31+
initial="hidden"
32+
whileInView="show"
33+
viewport={{ once: true, amount: 0.2 }}
34+
variants={stagger}
35+
className="mx-auto mt-16 grid max-w-[1440px] grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"
36+
>
37+
{pricingPlans.map((plan) => (
38+
<motion.li
39+
key={plan.id}
40+
variants={fadeUp}
41+
className="grain-panel grain-card flex flex-col rounded-[1.35rem] px-7 py-8 text-white"
42+
style={{ background: plan.wash }}
43+
>
44+
<div className="relative z-10 flex flex-1 flex-col">
45+
<p className="text-[13px] tracking-[0.18em] text-white/70 uppercase">
46+
{plan.note}
47+
</p>
48+
<h3 className="mt-4 font-display text-[1.85rem] leading-none tracking-tight">
49+
{plan.name}
50+
</h3>
51+
<p className="mt-6 font-display text-[2.4rem] leading-none tracking-[-0.04em]">
52+
{plan.price}
53+
{plan.unit ? (
54+
<span className="ml-1.5 font-sans text-sm tracking-wide text-white/70">
55+
{plan.unit}
56+
</span>
57+
) : null}
58+
</p>
59+
<ul className="mt-8 flex-1 space-y-2 text-[15px] leading-relaxed text-white/80">
60+
{plan.features.map((item) => (
61+
<li key={item}>{item}</li>
62+
))}
63+
</ul>
64+
{plan.href.startsWith("/") ? (
65+
<Link to={plan.href} className={ctaClass}>
66+
{plan.cta}
67+
</Link>
68+
) : (
69+
<a href={plan.href} className={ctaClass}>
70+
{plan.cta}
71+
</a>
72+
)}
73+
</div>
74+
</motion.li>
75+
))}
76+
</motion.ul>
77+
</section>
78+
)
79+
}

src/components/SiteNav.tsx

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,57 @@
1-
import { useEffect, useState } from "react"
1+
import { useEffect, useRef, useState } from "react"
22
import { Link, NavLink, useLocation } from "react-router-dom"
33
import { navLinks, STORE_URL } from "../data/site"
44
import { BrandLogo } from "./BrandLogo"
55

66
export function SiteNav() {
7-
const [compact, setCompact] = useState(false)
8-
const [open, setOpen] = useState(false)
97
const location = useLocation()
10-
const overHero = location.pathname === "/" && !compact && !open
8+
const [open, setOpen] = useState(false)
9+
const [pastHero, setPastHero] = useState(location.pathname !== "/")
10+
const [shown, setShown] = useState(true)
11+
const lastY = useRef(0)
1112

1213
useEffect(() => {
1314
setOpen(false)
15+
setShown(true)
1416
}, [location.pathname, location.hash])
1517

1618
useEffect(() => {
17-
const onScroll = () => setCompact(window.scrollY > 24)
18-
onScroll()
19-
window.addEventListener("scroll", onScroll, { passive: true })
20-
return () => window.removeEventListener("scroll", onScroll)
21-
}, [])
19+
if (location.pathname !== "/") {
20+
setPastHero(true)
21+
lastY.current = window.scrollY
22+
return
23+
}
24+
25+
const update = () => {
26+
const y = window.scrollY
27+
const delta = y - lastY.current
28+
lastY.current = y
29+
const past = y > window.innerHeight - 48
30+
setPastHero(past)
31+
32+
if (open) {
33+
setShown(true)
34+
return
35+
}
36+
if (!past || y < 16) {
37+
setShown(true)
38+
return
39+
}
40+
if (delta > 4) setShown(false)
41+
else if (delta < -4) setShown(true)
42+
}
43+
44+
lastY.current = window.scrollY
45+
update()
46+
window.addEventListener("scroll", update, { passive: true })
47+
window.addEventListener("resize", update)
48+
return () => {
49+
window.removeEventListener("scroll", update)
50+
window.removeEventListener("resize", update)
51+
}
52+
}, [location.pathname, open])
53+
54+
const overHero = location.pathname === "/" && !pastHero && !open
2255

2356
const linkClass = overHero
2457
? "text-[13px] tracking-wide text-white/80 transition-colors hover:text-white"
@@ -30,11 +63,11 @@ export function SiteNav() {
3063

3164
return (
3265
<header
33-
className={`fixed inset-x-0 top-0 z-40 transition-[padding,background-color,backdrop-filter] duration-500 ${
34-
compact || open
66+
className={`fixed inset-x-0 top-0 z-40 transition-[transform,padding,background-color,backdrop-filter] duration-500 ease-[cubic-bezier(0.16,1,0.3,1)] ${
67+
pastHero || open
3568
? "bg-white/80 py-3 backdrop-blur-md"
3669
: "bg-transparent py-6"
37-
}`}
70+
} ${shown ? "translate-y-0" : "-translate-y-full"}`}
3871
>
3972
<div className="mx-auto flex max-w-[1440px] items-center justify-between px-5 md:px-10">
4073
<Link to="/" className="flex items-center">

src/components/TemplateCarousel.tsx

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export function TemplateCarousel() {
102102
</p>
103103
</motion.div>
104104

105-
<div className="relative mt-12">
105+
<div className="carousel-edge-fade relative mt-12">
106106
<div
107107
ref={scrollerRef}
108108
className="liquid-x cursor-grab overflow-x-auto overflow-y-hidden active:cursor-grabbing"
@@ -133,14 +133,6 @@ export function TemplateCarousel() {
133133
))}
134134
</div>
135135
</div>
136-
<div
137-
className="pointer-events-none absolute inset-y-0 left-0 z-10 w-20 bg-gradient-to-r from-canvas-dim via-canvas-dim/80 to-transparent md:w-36"
138-
aria-hidden
139-
/>
140-
<div
141-
className="pointer-events-none absolute inset-y-0 right-0 z-10 w-20 bg-gradient-to-l from-canvas-dim via-canvas-dim/80 to-transparent md:w-36"
142-
aria-hidden
143-
/>
144136
</div>
145137

146138
<div className="mt-10 px-5 md:px-10">

src/data/site.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,51 @@ export const directoryPeople = [
126126
},
127127
]
128128

129+
export const pricingPlans = [
130+
{
131+
id: "solo",
132+
name: "Solo",
133+
price: "149",
134+
unit: "SAR",
135+
note: "One card",
136+
features: ["NFC and QR", "Your profile, your design", "No app required"],
137+
cta: "Order Now",
138+
href: STORE_URL,
139+
wash: "linear-gradient(115deg, #161b26 0%, #3d5368 52%, #7d93a8 100%)",
140+
},
141+
{
142+
id: "pair",
143+
name: "Pair",
144+
price: "269",
145+
unit: "SAR",
146+
note: "Two cards",
147+
features: ["Share a look", "Two profiles", "One order"],
148+
cta: "Order Now",
149+
href: STORE_URL,
150+
wash: "linear-gradient(115deg, #2a1810 0%, #8a4a28 48%, #d4b49a 100%)",
151+
},
152+
{
153+
id: "team",
154+
name: "Team",
155+
price: "599",
156+
unit: "SAR",
157+
note: "Five cards",
158+
features: ["Branded set", "Five profiles", "Ready for a small crew"],
159+
cta: "Order Now",
160+
href: STORE_URL,
161+
wash: "linear-gradient(115deg, #121a14 0%, #3d5a42 52%, #8fa88c 100%)",
162+
},
163+
{
164+
id: "company",
165+
name: "Company",
166+
price: "Custom",
167+
unit: "",
168+
note: "Portal and bulk",
169+
features: ["Companies portal", "Issue and update cards", "Volume pricing"],
170+
cta: "Open portal",
171+
href: "/companies",
172+
wash: "linear-gradient(115deg, #1a1a1a 0%, #4a4540 50%, #9a938c 100%)",
173+
},
174+
] as const
175+
129176
export { CONTACT_EMAIL, STORE_URL }

src/index.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,31 @@
6565
display: none;
6666
}
6767

68+
.carousel-edge-fade {
69+
-webkit-mask-image: linear-gradient(
70+
to right,
71+
transparent 0%,
72+
rgb(0 0 0 / 0.12) 3%,
73+
rgb(0 0 0 / 0.45) 7%,
74+
#000 14%,
75+
#000 86%,
76+
rgb(0 0 0 / 0.45) 93%,
77+
rgb(0 0 0 / 0.12) 97%,
78+
transparent 100%
79+
);
80+
mask-image: linear-gradient(
81+
to right,
82+
transparent 0%,
83+
rgb(0 0 0 / 0.12) 3%,
84+
rgb(0 0 0 / 0.45) 7%,
85+
#000 14%,
86+
#000 86%,
87+
rgb(0 0 0 / 0.45) 93%,
88+
rgb(0 0 0 / 0.12) 97%,
89+
transparent 100%
90+
);
91+
}
92+
6893
.shine-hover {
6994
position: relative;
7095
overflow: hidden;
@@ -116,6 +141,11 @@
116141
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='180' height='180' filter='url(%23n)'/%3E%3C/svg%3E");
117142
}
118143

144+
.grain-card::after {
145+
opacity: 0.72;
146+
mix-blend-mode: overlay;
147+
}
148+
119149
.foil {
120150
background:
121151
linear-gradient(

0 commit comments

Comments
 (0)