Function bodies 113 total
TimelineImage function · typescript · L22-L44 (23 LOC)src/app/about/AboutPageClient.tsx
function TimelineImage({
src,
alt,
className = "",
}: {
src: string;
alt: string;
className?: string;
}) {
return (
<div
className={`relative overflow-hidden rounded-lg border border-warm/10 bg-background shadow-[0_0_24px_rgba(232,213,163,0.04)] ${className}`}
>
<Image
src={src}
alt={alt}
fill
className="object-cover"
sizes="(max-width: 640px) 50vw, (max-width: 1024px) 33vw, 25vw"
/>
</div>
);
}AboutPageClient function · typescript · L332-L563 (232 LOC)src/app/about/AboutPageClient.tsx
export default function AboutPageClient() {
return (
<>
{/* ───────── Hero — Full-width cinematic ───────── */}
<section className="relative flex min-h-[85vh] items-end overflow-hidden bg-background">
{/* Background */}
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(180deg, #0C1220 0%, #1a2744 30%, #1e293b 60%, #111827 100%)",
}}
/>
<div className="absolute top-0 right-0 h-[500px] w-[600px] rounded-full bg-warm/4 blur-[150px]" />
<div className="absolute bottom-0 left-1/3 h-[400px] w-[500px] rounded-full bg-accent/3 blur-[120px]" />
<div className="absolute inset-0 bg-gradient-to-t from-background via-background/30 to-transparent" />
{/* Navbar placeholder: subtle warm glow line at bottom */}
<div
className="absolute top-0 left-0 right-0 h-px"
style={{
background:
"linear-gradienAboutPage function · typescript · L18-L20 (3 LOC)src/app/about/page.tsx
export default function AboutPage() {
return <AboutPageClient />;
}POST function · typescript · L143-L175 (33 LOC)src/app/api/chat/route.ts
export async function POST(req: Request) {
/* ── Rate limiting ─── */
const ip = getIP(req);
const { success: withinLimit, resetAt } = rateLimit(ip, RATE_LIMIT);
if (!withinLimit) {
return NextResponse.json(
{ error: "Too many requests. Please slow down." },
{
status: 429,
headers: {
"Retry-After": String(Math.ceil((resetAt - Date.now()) / 1000)),
},
}
);
}
const { messages } = await req.json();
/* AI SDK v6: useChat sends UIMessages (parts array).
streamText expects ModelMessages (content string).
Convert before passing to the model. */
const modelMessages = await convertToModelMessages(messages);
const result = streamText({
model: anthropic("claude-sonnet-4-20250514"),
system: SYSTEM_PROMPT,
messages: modelMessages,
maxOutputTokens: 1024,
});
return result.toUIMessageStreamResponse();
}POST function · typescript · L30-L158 (129 LOC)src/app/api/lead/route.ts
export async function POST(request: NextRequest) {
try {
/* ── Rate limiting ─── */
const ip = getIP(request);
const { success: withinLimit, remaining, resetAt } = rateLimit(ip, RATE_LIMIT);
if (!withinLimit) {
return NextResponse.json(
{ error: "Too many requests. Please try again shortly." },
{
status: 429,
headers: {
"Retry-After": String(Math.ceil((resetAt - Date.now()) / 1000)),
"X-RateLimit-Remaining": "0",
},
}
);
}
const body: LeadPayload = await request.json();
/* ── Honeypot check — bots fill hidden fields ─── */
if (body._honey) {
// Silently reject but return 200 so bots think it worked
return NextResponse.json({ success: true, ghl: false });
}
// Validation
if (!body.name || !body.email) {
return NextResponse.json(
{ error: "Name and email are required." },
{ status: 400 }
);
}
// Check BlogPageClient function · typescript · L36-L312 (277 LOC)src/app/blog/BlogPageClient.tsx
export default function BlogPageClient() {
const [searchQuery, setSearchQuery] = useState("");
const [selectedCategory, setSelectedCategory] = useState("All Categories");
const [showCategoryDropdown, setShowCategoryDropdown] = useState(false);
const [currentPage, setCurrentPage] = useState(1);
/* ─── Filter posts ─── */
const filteredPosts = allPosts.filter((post) => {
const matchesSearch =
searchQuery === "" ||
post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
post.excerpt.toLowerCase().includes(searchQuery.toLowerCase());
const matchesCategory =
selectedCategory === "All Categories" ||
post.category === selectedCategory;
return matchesSearch && matchesCategory;
});
const totalPages = Math.max(
1,
Math.ceil(filteredPosts.length / POSTS_PER_PAGE)
);
const paginatedPosts = filteredPosts.slice(
(currentPage - 1) * POSTS_PER_PAGE,
currentPage * POSTS_PER_PAGE
);
const handleCategoryChange = BlogPage function · typescript · L18-L20 (3 LOC)src/app/blog/page.tsx
export default function BlogPage() {
return <BlogPageClient />;
}Generated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
BlogPostClient function · typescript · L66-L323 (258 LOC)src/app/blog/[slug]/BlogPostClient.tsx
export default function BlogPostClient({ post }: BlogPostClientProps) {
const contentStr = post.content || "";
/* Split content on <!-- STATS --> marker to position stat boxes inline */
const statMarker = "<!-- STATS -->";
const hasStatMarker = contentStr.includes(statMarker);
const contentParts = hasStatMarker
? contentStr.split(statMarker)
: [contentStr];
/* ─── Stat boxes component ─── */
const StatBoxes = () =>
post.stats && post.stats.length > 0 ? (
<div className="my-10 grid grid-cols-1 gap-4 sm:grid-cols-3">
{post.stats.map((stat) => (
<div
key={stat.label}
className="border border-border bg-background-secondary p-6 text-center"
>
<p className="font-heading text-2xl font-semibold tracking-tight text-warm sm:text-3xl">
{stat.value}
</p>
<p className="mt-2 text-xs tracking-[0.15em] text-text-muted uppercase">
{stat.label}
generateStaticParams function · typescript · L14-L16 (3 LOC)src/app/blog/[slug]/page.tsx
export async function generateStaticParams() {
return getAllSlugs().map((slug) => ({ slug }));
}generateMetadata function · typescript · L19-L63 (45 LOC)src/app/blog/[slug]/page.tsx
export async function generateMetadata({
params,
}: PageProps): Promise<Metadata> {
const { slug } = await params;
const post = getPostBySlug(slug);
if (!post) return {};
const url = `${BASE_URL}/blog/${post.slug}`;
const description =
post.metaDescription || post.excerpt;
return {
title: post.title,
description,
alternates: { canonical: url },
openGraph: {
type: "article",
locale: "en_CA",
url,
title: `${post.title} | Karsen Koltun`,
description,
siteName: "Karsen Koltun | Kelowna Real Estate",
publishedTime: new Date(post.date).toISOString(),
authors: ["Karsen Koltun"],
...(post.image
? {
images: [
{
url: post.image.startsWith("http")
? post.image
: `${BASE_URL}${post.image}`,
width: 1200,
height: 630,
},
],
}
: {}),
},
twitter:BlogPostPage function · typescript · L66-L112 (47 LOC)src/app/blog/[slug]/page.tsx
export default async function BlogPostPage({ params }: PageProps) {
const { slug } = await params;
const post = getPostBySlug(slug);
/* Only render posts that have full content */
if (!post || !post.content) notFound();
/* JSON-LD Article structured data for SEO */
const jsonLd = {
"@context": "https://schema.org",
"@type": "Article",
headline: post.title,
description: post.metaDescription || post.excerpt,
datePublished: new Date(post.date).toISOString(),
dateModified: new Date(post.date).toISOString(),
author: {
"@type": "Person",
name: "Karsen Koltun",
url: BASE_URL,
},
publisher: {
"@type": "Organization",
name: "Karsen Koltun | Kelowna Real Estate",
},
mainEntityOfPage: {
"@type": "WebPage",
"@id": `${BASE_URL}/blog/${post.slug}`,
},
...(post.image
? {
image: post.image.startsWith("http")
? post.image
: `${BASE_URL}${post.image}`,
BuyPage function · typescript · L20-L22 (3 LOC)src/app/buy/page.tsx
export default function BuyPage() {
return <BuyPageClient />;
}clamp function · typescript · L83-L85 (3 LOC)src/app/calculator/MortgageCalculatorClient.tsx
function clamp(val: number, min: number, max: number) {
return Math.max(min, Math.min(max, val));
}parseCurrencyInput function · typescript · L87-L89 (3 LOC)src/app/calculator/MortgageCalculatorClient.tsx
function parseCurrencyInput(raw: string): number {
return Number(raw.replace(/[^0-9.]/g, "")) || 0;
}Tooltip function · typescript · L95-L126 (32 LOC)src/app/calculator/MortgageCalculatorClient.tsx
function Tooltip({ text }: { text: string }) {
const [show, setShow] = useState(false);
return (
<span className="relative inline-flex">
<button
type="button"
aria-label="More info"
className="ml-1.5 inline-flex h-4 w-4 items-center justify-center rounded-full border border-accent/30 text-[10px] text-accent transition-colors hover:border-accent hover:bg-accent/10"
onMouseEnter={() => setShow(true)}
onMouseLeave={() => setShow(false)}
onFocus={() => setShow(true)}
onBlur={() => setShow(false)}
>
i
</button>
<AnimatePresence>
{show && (
<motion.div
initial={{ opacity: 0, y: 4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 4 }}
className="absolute bottom-full left-1/2 z-50 mb-2 w-64 -translate-x-1/2 rounded-lg border border-border bg-background-secondary p-3 text-xs leading-relaxed text-text-secondary shadow-lg"
Want this analysis on your repo? https://repobility.com/scan/
DonutChart function · typescript · L138-L213 (76 LOC)src/app/calculator/MortgageCalculatorClient.tsx
function DonutChart({
segments,
centerLabel,
centerValue,
}: {
segments: DonutSegment[];
centerLabel: string;
centerValue: string;
}) {
const total = segments.reduce((sum, s) => sum + s.value, 0);
if (total === 0) return null;
const radius = 80;
const stroke = 24;
const circumference = 2 * Math.PI * radius;
let cumulativeOffset = 0;
return (
<div className="relative mx-auto w-full max-w-[280px]">
<svg viewBox="0 0 200 200" className="w-full">
{segments
.filter((s) => s.value > 0)
.map((seg) => {
const pct = seg.value / total;
const dashLength = pct * circumference;
const dashOffset = -cumulativeOffset * circumference;
cumulativeOffset += pct;
return (
<circle
key={seg.label}
cx="100"
cy="100"
r={radius}
fill="none"
stroke={seg.color}
strCalculatorPage function · typescript · L23-L25 (3 LOC)src/app/calculator/page.tsx
export default function CalculatorPage() {
return <MortgageCalculatorClient />;
}validateField function · typescript · L117-L125 (9 LOC)src/app/contact/ContactPageClient.tsx
function validateField(name: string, value: string): string {
if (name === "name" && value.trim().length < 2) return "Please enter your name";
if (name === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value))
return "Please enter a valid email";
if (name === "inquiry" && !value) return "Please select an inquiry type";
if (name === "message" && value.trim().length < 10)
return "Please enter at least 10 characters";
return "";
}ContactPage function · typescript · L17-L19 (3 LOC)src/app/contact/page.tsx
export default function ContactPage() {
return <ContactPageClient />;
}HomeValueClient function · typescript · L7-L116 (110 LOC)src/app/home-value/HomeValueClient.tsx
export default function HomeValueClient() {
return (
<section className="relative min-h-screen overflow-hidden bg-background">
{/* Background effects */}
<div
className="absolute inset-0"
style={{
background:
"linear-gradient(135deg, #070B14 0%, #0C1220 20%, #1a2744 45%, #1e293b 65%, #111827 85%, #070B14 100%)",
}}
/>
<div className="absolute top-1/4 right-1/3 h-[500px] w-[600px] rounded-full bg-warm/5 blur-[150px]" />
<div className="absolute bottom-1/3 left-0 h-[400px] w-[500px] rounded-full bg-accent/4 blur-[120px]" />
<div className="absolute inset-0 bg-gradient-to-r from-background/60 via-transparent to-background/20" />
{/* KARSEN KOLTUN branding top center */}
<div className="relative z-10 flex justify-center pt-28 pb-4">
<motion.p
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }}
HomeValuePage function · typescript · L19-L21 (3 LOC)src/app/home-value/page.tsx
export default function HomeValuePage() {
return <HomeValueClient />;
}RootLayout function · typescript · L157-L210 (54 LOC)src/app/layout.tsx
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<head>
{/* JSON-LD structured data */}
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
</head>
<body className={`${cormorant.variable} ${inter.variable} antialiased`}>
{/* Skip navigation link for keyboard users */}
<a
href="#main-content"
className="sr-only focus:not-sr-only focus:fixed focus:left-4 focus:top-4 focus:z-[100] focus:rounded focus:bg-accent focus:px-6 focus:py-3 focus:text-sm focus:font-medium focus:text-background"
>
Skip to main content
</a>
<AuthProvider>
<ScrollRestoration />
<ScrollProgress />
<Navbar />
<PageBlurEdges />
<main id="main-content">{children}</main>
<Footer />
<BackToTop />
Loading function · typescript · L1-L55 (55 LOC)src/app/loading.tsx
export default function Loading() {
return (
<div className="flex min-h-screen items-center justify-center bg-background">
<div className="flex flex-col items-center gap-6">
{/* Pulsing ring spinner */}
<div className="relative h-12 w-12">
<div
className="absolute inset-0 rounded-full border-2 border-accent/20"
aria-hidden="true"
/>
<div
className="absolute inset-0 rounded-full border-2 border-transparent border-t-accent"
style={{
animation: "kk-spin 1s cubic-bezier(0.55, 0.15, 0.45, 0.85) infinite",
}}
aria-hidden="true"
/>
<div
className="absolute inset-2 rounded-full border-2 border-transparent border-t-accent/50"
style={{
animation:
"kk-spin 1.4s cubic-bezier(0.55, 0.15, 0.45, 0.85) infinite reverse",
}}
aria-hidden="true"
/>
Repobility — the code-quality scanner for AI-generated software · https://repobility.com
LoginPageClient function · typescript · L8-L63 (56 LOC)src/app/login/LoginPageClient.tsx
export default function LoginPageClient() {
return (
<section className="flex min-h-[calc(100vh-80px)] items-center justify-center px-4 py-24">
<div className="shadow-input mx-auto w-full max-w-md rounded-2xl bg-background-secondary p-8">
{/* Heading */}
<h2 className="font-heading text-3xl font-light tracking-wider text-text-primary">
Welcome Back
</h2>
<p className="mt-2 max-w-sm text-sm leading-relaxed text-text-secondary">
Sign in to save your favourite listings, track your home value, and
get personalized Kelowna real estate insights.
</p>
{/* Divider */}
<div className="my-8 h-px w-full bg-gradient-to-r from-transparent via-accent/30 to-transparent" />
{/* Google OAuth Button */}
<button
onClick={() => signIn("google", { callbackUrl: "/" })}
className={cn(
"group/btn shadow-input relative flex h-12 w-full items-center justify-center gLoginPage function · typescript · L10-L12 (3 LOC)src/app/login/page.tsx
export default function LoginPage() {
return <LoginPageClient />;
}manifest function · typescript · L3-L26 (24 LOC)src/app/manifest.ts
export default function manifest(): MetadataRoute.Manifest {
return {
name: "Karsen Koltun | Kelowna Real Estate",
short_name: "Karsen Koltun",
description:
"Premium real estate services in Kelowna, BC. Buy, sell, or get your home value with Karsen Koltun at Royal LePage.",
start_url: "/",
display: "standalone",
background_color: "#0A0A0A",
theme_color: "#0A0A0A",
icons: [
{
src: "/icon-192.png",
sizes: "192x192",
type: "image/png",
},
{
src: "/icon-512.png",
sizes: "512x512",
type: "image/png",
},
],
};
}NotFound function · typescript · L3-L51 (49 LOC)src/app/not-found.tsx
export default function NotFound() {
return (
<div className="relative flex min-h-screen flex-col items-center justify-center overflow-hidden bg-background px-6 text-center">
{/* Subtle radial gradient background */}
<div
className="pointer-events-none absolute inset-0"
aria-hidden="true"
style={{
background:
"radial-gradient(ellipse 60% 40% at 50% 40%, rgba(96,165,250,0.07) 0%, transparent 70%)",
}}
/>
{/* Large 404 display */}
<p
className="font-heading text-[10rem] font-bold leading-none tracking-tight text-accent/10 sm:text-[14rem]"
aria-hidden="true"
>
404
</p>
{/* Heading */}
<h1 className="-mt-10 font-heading text-4xl font-semibold text-text-primary sm:text-5xl">
Page not found
</h1>
{/* Description */}
<p className="mt-4 max-w-md text-lg text-text-secondary">
The page you're looking for doesn't eHomePage function · typescript · L10-L23 (14 LOC)src/app/page.tsx
export default function HomePage() {
return (
<>
<Hero />
<StatsBar />
<AboutPreview />
<FeaturedListings />
<Testimonials />
<BeyondRealEstate />
<SocialBar />
<Newsletter />
</>
);
}PrivacyPage function · typescript · L8-L73 (66 LOC)src/app/privacy/page.tsx
export default function PrivacyPage() {
return (
<>
<section className="bg-background pt-32 pb-16">
<div className="mx-auto max-w-3xl px-6 text-center lg:px-10">
<h1 className="font-heading text-5xl font-light tracking-tight text-text-primary md:text-6xl">
Privacy <span className="italic text-accent">Policy</span>
</h1>
<p className="mt-4 text-text-secondary">
Last updated: February 2026
</p>
</div>
</section>
<section className="bg-background pb-24 md:pb-32">
<div className="mx-auto max-w-3xl space-y-8 px-6 lg:px-10">
<div>
<h2 className="font-heading text-2xl font-medium text-text-primary">
Information We Collect
</h2>
<p className="mt-3 text-sm leading-relaxed text-text-secondary">
We collect information you provide directly to us, including your
name, email address, phone number, and robots function · typescript · L3-L17 (15 LOC)src/app/robots.ts
export default function robots(): MetadataRoute.Robots {
const baseUrl =
process.env.NEXT_PUBLIC_SITE_URL || "https://karsenkoltun.ca";
return {
rules: [
{
userAgent: "*",
allow: "/",
disallow: "/api/",
},
],
sitemap: `${baseUrl}/sitemap.xml`,
};
}formatPrice function · typescript · L139-L145 (7 LOC)src/app/search/page.tsx
function formatPrice(price: number) {
return new Intl.NumberFormat("en-CA", {
style: "currency",
currency: "CAD",
maximumFractionDigits: 0,
}).format(price);
}Methodology: Repobility · https://repobility.com/research/state-of-ai-code-2026/
SearchPage function · typescript · L153-L406 (254 LOC)src/app/search/page.tsx
export default function SearchPage() {
return (
<>
{/* Hero */}
<section className="bg-background pt-28 pb-12 sm:pt-32 sm:pb-16">
<div className="mx-auto max-w-7xl px-4 text-center sm:px-6 lg:px-10">
<p className="text-xs font-medium tracking-[0.3em] text-accent uppercase">
MLS Listings
</p>
<h1 className="mt-4 font-heading text-4xl font-light tracking-tight text-text-primary sm:text-5xl md:text-6xl">
Search <span className="italic text-accent">Properties</span>
</h1>
<p className="mx-auto mt-4 max-w-lg text-text-secondary">
Browse all available listings across Kelowna and the Okanagan.
Updated daily from the MLS.
</p>
</div>
</section>
{/* Search Interface */}
<section className="bg-background pb-6">
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-10">
{/* Property Type Tabs */}
<div
SellPage function · typescript · L20-L22 (3 LOC)src/app/sell/page.tsx
export default function SellPage() {
return <SellPageClient />;
}sitemap function · typescript · L3-L75 (73 LOC)src/app/sitemap.ts
export default function sitemap(): MetadataRoute.Sitemap {
const baseUrl =
process.env.NEXT_PUBLIC_SITE_URL || "https://karsenkoltun.ca";
return [
{
url: baseUrl,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1.0,
},
{
url: `${baseUrl}/sell`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.9,
},
{
url: `${baseUrl}/buy`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.9,
},
{
url: `${baseUrl}/home-value`,
lastModified: new Date(),
changeFrequency: "weekly",
priority: 0.9,
},
{
url: `${baseUrl}/search`,
lastModified: new Date(),
changeFrequency: "daily",
priority: 0.8,
},
{
url: `${baseUrl}/about`,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 0.7,
},
{
url: `${baseUrl}/contact`,
lastModified: new Date(),
TermsPage function · typescript · L8-L73 (66 LOC)src/app/terms/page.tsx
export default function TermsPage() {
return (
<>
<section className="bg-background pt-32 pb-16">
<div className="mx-auto max-w-3xl px-6 text-center lg:px-10">
<h1 className="font-heading text-5xl font-light tracking-tight text-text-primary md:text-6xl">
Terms of <span className="italic text-accent">Service</span>
</h1>
<p className="mt-4 text-text-secondary">
Last updated: February 2026
</p>
</div>
</section>
<section className="bg-background pb-24 md:pb-32">
<div className="mx-auto max-w-3xl space-y-8 px-6 lg:px-10">
<div>
<h2 className="font-heading text-2xl font-medium text-text-primary">
Use of This Website
</h2>
<p className="mt-3 text-sm leading-relaxed text-text-secondary">
By accessing and using karsenkoltun.ca, you agree to these terms
of service. This website is provided for inChatWidget function · typescript · L16-L158 (143 LOC)src/components/chat/ChatWidget.tsx
export default function ChatWidget() {
const [isOpen, setIsOpen] = useState(false);
const [isExpanded, setIsExpanded] = useState(false);
const [isMobile, setIsMobile] = useState(false);
const [hasBeenOpened, setHasBeenOpened] = useState(false);
/* Detect mobile */
useEffect(() => {
const check = () => setIsMobile(window.innerWidth < 768);
check();
window.addEventListener("resize", check);
return () => window.removeEventListener("resize", check);
}, []);
/* Lock body scroll on mobile when chat is open */
useEffect(() => {
if (isOpen && isMobile) {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "";
}
return () => {
document.body.style.overflow = "";
};
}, [isOpen, isMobile]);
const handleOpen = () => {
setIsOpen(true);
setHasBeenOpened(true);
};
const handleClose = () => {
setIsOpen(false);
setIsExpanded(false);
};
const handleToggleExpand = () => {
useAutoResizeTextarea function · typescript · L32-L54 (23 LOC)src/components/chat/ChatWindow.tsx
function useAutoResizeTextarea(minHeight: number, maxHeight: number) {
const ref = useRef<HTMLTextAreaElement>(null);
const adjustHeight = useCallback(
(reset?: boolean) => {
const textarea = ref.current;
if (!textarea) return;
if (reset) {
textarea.style.height = `${minHeight}px`;
return;
}
textarea.style.height = `${minHeight}px`;
const newHeight = Math.max(
minHeight,
Math.min(textarea.scrollHeight, maxHeight)
);
textarea.style.height = `${newHeight}px`;
},
[minHeight, maxHeight]
);
return { ref, adjustHeight };
}getMessageText function · typescript · L58-L69 (12 LOC)src/components/chat/ChatWindow.tsx
function getMessageText(message: { parts?: Array<{ type: string; text?: string }>; content?: string }): string {
// AI SDK v6 uses parts array
if (message.parts) {
return message.parts
.filter((p) => p.type === "text" && p.text)
.map((p) => p.text)
.join("");
}
// Fallback to content
if (typeof message.content === "string") return message.content;
return "";
}MessageBubble function · typescript · L73-L122 (50 LOC)src/components/chat/ChatWindow.tsx
function MessageBubble({
role,
content,
}: {
role: "user" | "assistant";
content: string;
}) {
const isUser = role === "user";
return (
<motion.div
className={cn("flex gap-3 px-4 py-3", isUser && "flex-row-reverse")}
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, ease: "easeOut" }}
>
{/* Avatar */}
<div
className={cn(
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full",
isUser
? "bg-accent/20 text-accent"
: "bg-warm/20 text-warm"
)}
>
{isUser ? (
<User className="h-3.5 w-3.5" />
) : (
<Sparkles className="h-3.5 w-3.5" />
)}
</div>
{/* Bubble */}
<div
className={cn(
"max-w-[80%] rounded-2xl px-4 py-2.5 text-sm leading-relaxed",
isUser
? "bg-accent/15 text-text-primary rounded-br-md"
: "bg-backgroGenerated by Repobility's multi-pass static-analysis pipeline (https://repobility.com)
TypingIndicator function · typescript · L126-L157 (32 LOC)src/components/chat/ChatWindow.tsx
function TypingIndicator() {
return (
<motion.div
className="flex gap-3 px-4 py-3"
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
>
<div className="bg-warm/20 text-warm flex h-7 w-7 shrink-0 items-center justify-center rounded-full">
<Sparkles className="h-3.5 w-3.5" />
</div>
<div className="bg-background-secondary/80 border border-border flex items-center gap-1.5 rounded-2xl rounded-bl-md px-4 py-3">
{[0, 1, 2].map((dot) => (
<motion.div
key={dot}
className="bg-warm h-1.5 w-1.5 rounded-full"
animate={{
opacity: [0.3, 0.9, 0.3],
scale: [0.85, 1.1, 0.85],
}}
transition={{
duration: 1.2,
repeat: Infinity,
delay: dot * 0.15,
ease: "easeInOut",
}}
/>
))}
</div>
</motion.div>
);
}ChatWindow function · typescript · L167-L404 (238 LOC)src/components/chat/ChatWindow.tsx
export default function ChatWindow({
isExpanded,
onClose,
onToggleExpand,
}: ChatWindowProps) {
const { messages, sendMessage, status, error } = useChat({
onError: (err) => {
console.error("[Chat error]", err);
},
});
const [input, setInput] = useState("");
const scrollRef = useRef<HTMLDivElement>(null);
const { ref: textareaRef, adjustHeight } = useAutoResizeTextarea(44, 120);
const [hasInteracted, setHasInteracted] = useState(false);
const isBusy = status === "submitted" || status === "streaming";
/* Auto-scroll to bottom on new messages */
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, status]);
/* Handle quick prompt click */
const handleQuickPrompt = (prompt: string) => {
setHasInteracted(true);
sendMessage({ text: prompt });
};
/* Handle send */
const handleSend = () => {
const text = input.trim();
if (!text || isBusy) retuHomeValuationForm function · typescript · L155-L194 (40 LOC)src/components/forms/HomeValuationForm.tsx
export default function HomeValuationForm() {
const handleFormSubmit = async (data: Record<string, unknown>) => {
const payload = {
name: `${data.firstName} ${data.lastName}`,
email: data.email as string,
phone: data.phone as string,
source: "Home Valuation Form",
tags: ["home-valuation-lead", "kelowna"],
customFields: {
address: data.address as string,
unit: (data.unit as string) || "",
city: data.city as string,
postalCode: data.postalCode as string,
propertyType: data.propertyType as string,
bedrooms: data.bedrooms as string,
timeline: data.timeline as string,
},
};
const res = await fetch("/api/lead", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ ...payload, _honey: "" }),
});
if (!res.ok) {
throw new Error("Failed to submit — please try again.");
}
};
return (
<MultiStepForm
Honeypot function · typescript · L17-L44 (28 LOC)src/components/forms/Honeypot.tsx
export default function Honeypot({ value, onChange }: HoneypotProps) {
return (
<div
aria-hidden="true"
tabIndex={-1}
style={{
position: "absolute",
left: "-9999px",
top: "-9999px",
height: 0,
width: 0,
overflow: "hidden",
opacity: 0,
}}
>
<label htmlFor="_honey">Leave this empty</label>
<input
type="text"
id="_honey"
name="_honey"
value={value}
onChange={(e) => onChange(e.target.value)}
tabIndex={-1}
autoComplete="off"
/>
</div>
);
}MultiStepForm function · typescript · L65-L320 (256 LOC)src/components/forms/MultiStepForm.tsx
export default function MultiStepForm({
steps,
onSubmit,
className,
submitLabel = "Submit",
successTitle = "Thank You!",
successMessage = "We\u2019ll be in touch soon.",
onReset,
}: MultiStepFormProps) {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState<FieldValues>({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isComplete, setIsComplete] = useState(false);
/* Current step's schema drives validation */
const currentSchema = steps[step].schema;
const {
register,
handleSubmit,
formState: { errors },
reset,
} = useForm<FieldValues>({
resolver: zodResolver(currentSchema),
defaultValues: formData,
});
const progress = ((step + 1) / steps.length) * 100;
/* ── Advance or submit ── */
const handleNext = async (data: FieldValues) => {
const merged = { ...formData, ...data };
setFormData(merged);
if (step < steps.length - 1) {
setStep(step + 1);
reset(mergeFooter function · typescript · L56-L271 (216 LOC)src/components/layout/Footer.tsx
export default function Footer() {
const [email, setEmail] = useState("");
const [submitted, setSubmitted] = useState(false);
const handleNewsletter = async (e: React.FormEvent) => {
e.preventDefault();
if (!email.trim()) return;
try {
await fetch("/api/lead", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: email.split("@")[0],
email,
source: "Footer Newsletter",
tags: ["newsletter", "footer-signup"],
}),
});
} catch {
// Silently fail — still show success to user
}
setSubmitted(true);
setEmail("");
setTimeout(() => setSubmitted(false), 4000);
};
return (
<footer className="border-t border-border bg-background">
{/* ---- Newsletter Row ---- */}
<div className="border-b border-border">
<div className="mx-auto flex max-w-7xl flex-col items-center gap-6 px-4 py-8 sm:px-6 sm:py-10 md:fleNavbar function · typescript · L39-L203 (165 LOC)src/components/layout/Navbar.tsx
export default function Navbar() {
const pathname = usePathname();
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
/* Lock body scroll when mobile menu is open (handles iOS overscroll) */
useEffect(() => {
if (isMobileMenuOpen) {
const scrollY = window.scrollY;
document.body.style.overflow = "hidden";
document.body.style.position = "fixed";
document.body.style.width = "100%";
document.body.style.top = `-${scrollY}px`;
} else {
const scrollY = document.body.style.top;
document.body.style.overflow = "";
document.body.style.position = "";
document.body.style.width = "";
document.body.style.top = "";
if (scrollY) {
window.scrollTo(0, parseInt(scrollY || "0") * -1);
}
}
return () => {
document.body.style.overflow = "";
document.body.style.position = "";
document.body.style.width = "";
document.body.style.top = "";
};
}, [isMobileMenuOpen]);
AuthProvider function · typescript · L5-L11 (7 LOC)src/components/providers/AuthProvider.tsx
export default function AuthProvider({
children,
}: {
children: React.ReactNode;
}) {
return <SessionProvider>{children}</SessionProvider>;
}Want this analysis on your repo? https://repobility.com/scan/
AboutPreview function · typescript · L9-L111 (103 LOC)src/components/sections/AboutPreview.tsx
export default function AboutPreview() {
const sectionRef = useRef<HTMLElement>(null);
const { scrollYProgress } = useScroll({
target: sectionRef,
offset: ["start end", "end start"],
});
const lineWidth = useTransform(scrollYProgress, [0.1, 0.5], [0, 1]);
return (
<section ref={sectionRef} className="bg-background py-16 md:py-24 lg:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-10">
<div className="grid items-center gap-12 md:grid-cols-2 md:gap-16">
{/* Photo placeholder with accent border offset */}
<AnimateIn direction="left">
<div className="relative mx-auto max-w-sm overflow-hidden md:mx-0 md:max-w-md md:overflow-visible">
<div className="relative aspect-[3/4]">
{/* Main photo container */}
<div className="absolute inset-0 bg-gradient-to-br from-background-secondary to-background-tertiary" />
{/* Placeholder text */}
<div classNBeyondRealEstate function · typescript · L37-L120 (84 LOC)src/components/sections/BeyondRealEstate.tsx
export default function BeyondRealEstate() {
return (
<section className="bg-background-secondary py-16 md:py-24 lg:py-32">
<div className="mx-auto max-w-7xl px-6 lg:px-10">
<AnimateIn>
<div className="text-center">
<p className="text-xs font-medium tracking-[0.3em] text-accent uppercase">
Beyond Real Estate
</p>
<h2 className="mt-4 font-heading text-4xl font-light tracking-tight text-text-primary md:text-5xl">
More Than an Agent
</h2>
<p className="mx-auto mt-4 max-w-lg text-text-secondary">
Real estate is at the core, but there's more to the story.
</p>
</div>
</AnimateIn>
{/* Full-width alternating layout */}
<div className="mt-16 space-y-8">
{ventures.map((venture, i) => {
const isReversed = i % 2 !== 0;
return (
<AnimateIn
key={venture.formatPrice function · typescript · L72-L78 (7 LOC)src/components/sections/FeaturedListings.tsx
function formatPrice(price: number) {
return new Intl.NumberFormat("en-CA", {
style: "currency",
currency: "CAD",
maximumFractionDigits: 0,
}).format(price);
}page 1 / 3next ›