Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -470,15 +470,29 @@
"title1": "The ",
"title2": "professional",
"title3": " page your clients expect.",
"description": "Your listings. Your reviews. Your calendar. One link that works as hard as you do.",
"description": "Create your professional real estate agent page in minutes. Share your listings, reviews, and links — one URL for everything. The smarter alternative to Linktree for realtors.",
"join": "Claim my page",
"free": "No credit card required"
},
"preview": {
"badge": "Preview",
"title1": "See it in ",
"title2": "action",
"your-page": "your page, live in minutes"
"free": "No credit card required",
"cards": {
"stats": {
"title": "This Week",
"views": "Page Views",
"leads": "New Leads",
"growth": "↑ 22% this week"
},
"review": {
"text": "Found our dream home in 3 weeks!",
"author": "— John & Sarah M."
},
"qr": {
"title": "Your link"
},
"status": {
"title": "Response rate",
"value": "98%",
"label": "avg. reply in 2h"
}
}
},
"logo-strip": {
"label": "Trusted by agents from"
Expand Down Expand Up @@ -708,7 +722,7 @@
"title1": "Ready to",
"title2": " stand out ",
"title3": "?",
"description": "Join now to upgrade online presence.",
"description": "Join now to upgrade your online presence.",
"join": "Claim my page"
}
},
Expand Down Expand Up @@ -862,6 +876,7 @@
"tagline": "The professional page your clients expect.",
"product": "Product",
"legal": "Legal",
"register": "Register",
"copyright": "© {year} Agentpage. All rights reserved.",
"made-with": "Made with 💙 for you"
},
Expand All @@ -885,6 +900,7 @@
"refund": "Refund policy",
"cookies": "Cookies Policy",
"blog": "Blog",
"forgot-password": "Forgot password",
"features": "Features",
"how-it-works": "How It Works",
"pricing": "Pricing",
Expand Down
Binary file removed public/images/home/house.jpg
Binary file not shown.
Binary file added public/images/home/johndoe.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/images/home/preview.png
Binary file not shown.
16 changes: 14 additions & 2 deletions src/app/[locale]/(main)/home/final-cta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const FinalCta: React.FC = () => {

return (
<motion.section
className="py-16 bg-secondary/10 rounded-box overflow-hidden"
className="py-16 relative isolate bg-secondary/10 rounded-box overflow-hidden"
{...fadeUpView(0)}
>
<div className="relative max-w-6xl mx-auto px-4">
Expand All @@ -45,7 +45,7 @@ const FinalCta: React.FC = () => {
<div>
<h2
className={cn(
"text-3xl sm:text-4xl md:text-5xl font-bold",
"text-3xl sm:text-4xl md:text-5xl font-bold mb-4",
font.className
)}
>
Expand Down Expand Up @@ -87,6 +87,18 @@ const FinalCta: React.FC = () => {
</div>
</div>
</div>
<div
aria-hidden="true"
className="absolute top-0 left-1/2 -z-10 -translate-x-1/2 blur-3xl xl:-top-6"
>
<div
style={{
clipPath:
"polygon(74.1% 44.1%, 100% 61.6%, 97.5% 26.9%, 85.5% 0.1%, 80.7% 2%, 72.5% 32.5%, 60.2% 62.4%, 52.4% 68.1%, 47.5% 58.3%, 45.2% 34.5%, 27.5% 76.7%, 0.1% 64.9%, 17.9% 100%, 27.6% 76.8%, 76.1% 97.7%, 74.1% 44.1%)",
}}
className="aspect-1155/678 w-288.75 bg-linear-to-tr from-[#ff80b5] to-secondary opacity-30"
></div>
</div>
</motion.section>
);
};
Expand Down
251 changes: 181 additions & 70 deletions src/app/[locale]/(main)/home/hero.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";

import Image from "next/image";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { Merriweather as Font } from "next/font/google";
import { useTranslations } from "next-intl";
import { useState } from "react";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
import { domain } from "@/config/seo";
import { cn } from "@/lib/utils";
import { Sparkles } from "lucide-react";

const font = Font({
subsets: ["latin"],
Expand All @@ -21,7 +22,7 @@ const fadeUp = (delay = 0) => ({
});

const fadeUpView = (delay = 0) => ({
initial: { opacity: 0, y: -28 },
initial: { opacity: 0, y: 20 },
whileInView: { opacity: 1, y: 0 },
viewport: { once: true },
transition: { duration: 0.7, ease: [0.16, 1, 0.3, 1] as const, delay },
Expand All @@ -43,77 +44,187 @@ const Hero: React.FC = () => {

return (
<motion.section
className="relative w-full h-[calc(100svh-90px)] rounded-box shadow-xl overflow-hidden"
className="relative z-50 w-full min-h-[calc(100svh-90px)] overflow-x-hidden text-left"
{...fadeUp(0)}
>
<Image
src="/images/home/house.jpg"
alt="house"
className="absolute size-full object-cover"
fill
priority
/>
<div className="size-full bg-gradient-to-b from-black/50 to-black/40 text-white backdrop-blur-sm flex flex-col items-center justify-center gap-y-6">
<motion.div className="flex -mb-2" {...fadeUpView(0)}>
<div className="badge badge-lg badge-soft rounded-full">
<span className="status status-success"></span>
<p>{t("badge")}</p>
</div>
</motion.div>

<motion.h1
className={cn(
"text-5xl sm:text-6xl md:text-7xl max-w-[840px] text-center px-4",
font.className
)}
{...fadeUpView(0.2)}
>
{t("title1")}
<span className="italic text-secondary contrast-150">
{t("title2")}
</span>
{t("title3")}
</motion.h1>

<motion.p
className="text-center max-w-[540px] px-6"
{...fadeUpView(0.4)}
>
{t("description")}
</motion.p>

<motion.div
className="join join-vertical sm:join-horizontal text-black font-[500] w-full px-4 sm:w-auto sm:px-0"
{...fadeUpView(0.6)}
>
<label className="input input-lg lg:input-xl join-item gap-x-0 px-6 text-base! focus:border-primary/80 focus:outline-none! outline-offset-0 outline-none w-full sm:w-auto">
<span className="opacity-80">
{domain.replace("https://", "")}/
<div className="flex flex-col lg:flex-row items-center lg:items-center lg:h-[calc(100svh-90px)] py-14 lg:py-0 gap-y-10 lg:gap-y-0">
<div className="w-full lg:w-3/7 flex flex-col items-center lg:items-start justify-center text-center lg:text-left gap-y-6 flex-shrink-0">
<motion.div className="flex -mb-2" {...fadeUpView(0)}>
<div className="badge badge-lg badge-secondary badge-soft border border-secondary/20">
<Sparkles className="size-5 text-amber-300 fill-amber-300" />
<p className="text-neutral">{t("badge")}</p>
</div>
</motion.div>

<motion.h1
className={cn(
"text-4xl sm:text-5xl md:text-5xl xl:text-6xl max-w-180",
font.className
)}
{...fadeUpView(0.2)}
>
{t("title1")}
<span className="italic text-secondary">
{t("title2")}
</span>
<input
type="text"
placeholder="johndoe"
className="focus:border-none focus:outline-none"
value={username}
onChange={(e) => setUsername(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && handleJoin()}
/>
</label>

<button
className="btn btn-lg lg:btn-xl btn-primary pl-4 text-base! join-item"
onClick={handleJoin}
{t("title3")}
</motion.h1>

<motion.p
className="text-sm xl:text-base max-w-md lg:max-w-none"
{...fadeUpView(0.4)}
>
{t("description")}
</motion.p>

<motion.div
className="join join-vertical sm:join-horizontal text-black font-[500] w-full max-w-sm lg:max-w-none"
{...fadeUpView(0.6)}
>
<label className="input input-lg join-item gap-x-0 px-4 text-sm! focus:border-primary/80 focus:outline-none! outline-offset-0 outline-none w-full sm:w-auto">
<span className="opacity-80 text-sm">
{domain.replace("https://", "")}/
</span>
<input
type="text"
placeholder="johndoe"
className="focus:border-none focus:outline-none text-sm"
value={username}
onChange={(e) => setUsername(e.target.value)}
onKeyDown={(e) =>
e.key === "Enter" && handleJoin()
}
/>
</label>

<button
className="btn btn-lg btn-primary pl-4 text-sm! join-item"
onClick={handleJoin}
>
{t("join")}
</button>
</motion.div>

<motion.div
{...fadeUpView(0.7)}
className="text-neutral/50 -mt-5 text-sm"
>
{t("free")}
</motion.div>
</div>

<div className="flex relative w-full lg:w-3/5 h-[540px] sm:h-[620px] lg:h-full items-center justify-center">
<motion.div
className="mockup-phone border-neutral rounded-[60px] w-52 xl:w-72 max-h-[85%] aspect-9/18"
{...fadeUpView(0.3)}
>
<div className="mockup-phone-camera" />
<div className="mockup-phone-display rounded-[66px]">
<Image
src="/images/home/johndoe.png"
alt="AgentPage preview"
width={480}
height={800}
className="w-full h-auto"
/>
</div>
</motion.div>

<motion.div
className="absolute top-[8%] right-4 sm:right-8 xl:right-16 w-44 bg-base-100 rounded-2xl shadow-xl border border-base-300 p-3.5"
style={{ rotate: "3deg" }}
{...fadeUpView(0.5)}
>
<p className="text-[10px] text-neutral/40 font-semibold uppercase tracking-widest mb-2.5">
{t("cards.stats.title")}
</p>
<div className="grid grid-cols-2 gap-2 mb-2">
<div>
<p className="text-xl font-bold leading-none">
847
</p>
<p className="text-[10px] text-neutral/50 mt-0.5">
{t("cards.stats.views")}
</p>
</div>
<div>
<p className="text-xl font-bold leading-none text-secondary">
12
</p>
<p className="text-[10px] text-neutral/50 mt-0.5">
{t("cards.stats.leads")}
</p>
</div>
</div>
<p className="text-[10px] text-green-500 font-semibold">
{t("cards.stats.growth")}
</p>
</motion.div>

<motion.div
className="absolute top-[22%] left-4 sm:left-8 xl:left-16 w-40 bg-base-100 rounded-2xl shadow-xl border border-base-300 p-3"
style={{ rotate: "-2deg" }}
{...fadeUpView(0.7)}
>
<div className="flex items-center gap-1.5 mb-1.5">
<span className="size-2 rounded-full bg-green-500 animate-pulse shrink-0" />
<p className="text-[9px] text-neutral/40 font-semibold uppercase tracking-widest">
{t("cards.status.title")}
</p>
</div>
<p className="text-2xl font-bold text-green-500 leading-none mb-0.5">
{t("cards.status.value")}
</p>
<p className="text-[9px] text-neutral/50">
{t("cards.status.label")}
</p>
</motion.div>

<motion.div
className="absolute bottom-[16%] left-4 sm:left-8 xl:left-16 w-52 bg-base-100 rounded-2xl shadow-xl border border-base-300 p-3.5"
style={{ rotate: "-3deg" }}
{...fadeUpView(0.6)}
>
<div className="flex gap-0.5 mb-2">
{[...Array(5)].map((_, i) => (
<span
key={i}
className="text-yellow-400 text-sm"
>
</span>
))}
</div>
<p className="text-xs font-medium leading-relaxed">
&ldquo;{t("cards.review.text")}&rdquo;
</p>
<p className="text-[10px] text-neutral/40 mt-2">
{t("cards.review.author")}
</p>
</motion.div>

<motion.div
className="absolute bottom-[14%] right-4 sm:right-8 xl:right-16 w-36 bg-base-100 rounded-2xl shadow-xl border border-base-300 p-3"
style={{ rotate: "4deg" }}
{...fadeUpView(0.8)}
>
{t("join")}
</button>
</motion.div>

<motion.div
{...fadeUpView(0.7)}
className="text-base-300 -mt-4"
>
{t("free")}
</motion.div>
<p className="text-[9px] text-neutral/40 font-semibold uppercase tracking-widest mb-2">
{t("cards.qr.title")}
</p>
<p className="text-[9px] font-mono font-bold text-secondary truncate mb-2">
agentpage.me/johndoe
</p>
<div className="flex justify-center">
<Image
src="/images/home/qr.png"
alt="QR code"
width={80}
height={80}
className="rounded-lg"
/>
</div>
</motion.div>
</div>
</div>
</motion.section>
);
Expand Down
Loading
Loading