← back to karsenkoltun__kk1

Function bodies 113 total

All specs Real LLM only Function bodies
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-gradien
AboutPage 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}
                str
CalculatorPage 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 g
LoginPage 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&apos;re looking for doesn&apos;t e
HomePage 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 in
ChatWidget 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-backgro
Generated 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) retu
HomeValuationForm 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(merge
Footer 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:fle
Navbar 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 classN
BeyondRealEstate 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&apos;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 ›