← back to Homesonspec

apps/web/src/app/layout.tsx

100 lines

import type { Metadata, Viewport } from "next";
import Link from "next/link";
import { Fraunces, Inter } from "next/font/google";
import "./globals.css";
import AdSlot, { AdSenseLoader } from "../components/AdSlot";
import PWARegister from "../components/PWARegister";
// InstallPrompt import disabled (TK-11155): the "Add to Home Screen" nag is
// retired in favor of the native iOS/Android app as the install path. The
// component itself is left intact — re-add the import + <InstallPrompt />
// below to bring it back.
// import InstallPrompt from "../components/InstallPrompt";

// Display serif + body sans, exposed as CSS variables the @theme layer reads.
const display = Fraunces({
  subsets: ["latin"],
  variable: "--font-fraunces",
  weight: ["400", "500", "600", "700"],
  style: ["normal", "italic"],
});
const sans = Inter({ subsets: ["latin"], variable: "--font-inter" });

export const metadata: Metadata = {
  title: "HomesOnSpec — Every new home. Every builder. One search.",
  description:
    "Discover, compare, and contact builders for newly constructed homes. Verified from the source, with live verification labels — new builders and markets added continuously.",
  applicationName: "Homes on Spec",
  // Installable-app metadata. Next auto-links the manifest from app/manifest.ts.
  appleWebApp: {
    capable: true,
    title: "HomesOnSpec",
    statusBarStyle: "default",
  },
  icons: {
    icon: [
      { url: "/favicon-32.png", sizes: "32x32", type: "image/png" },
      { url: "/icon.svg", type: "image/svg+xml" },
    ],
    apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
  },
};

// Next 16 requires theme color / viewport in the `viewport` export (not `metadata`).
export const viewport: Viewport = {
  themeColor: "#1b4645",
  width: "device-width",
  initialScale: 1,
  viewportFit: "cover",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en" className={`${display.variable} ${sans.variable}`}>
      <body className="min-h-screen bg-neutral-50 font-sans text-neutral-900 antialiased">
        <PWARegister />
        {/* InstallPrompt disabled (TK-11155) — see import comment above */}
        <AdSenseLoader />
        {/* Live inventory only — every listing is real builder data with a verification label. */}
        <div className="bg-brand-950 px-4 py-1.5 text-center text-xs font-medium text-brand-100">
          Early preview — every listing is verified from the builder&rsquo;s own website and shows its
          verification label and last-verified time. New builders are added continuously.
        </div>

        <header className="sticky top-0 z-[500] border-b border-neutral-200/70 bg-white/85 backdrop-blur">
          <nav className="mx-auto flex max-w-7xl items-center justify-between px-4 py-3">
            <Link href="/" className="font-display text-xl font-semibold tracking-tight text-brand-800">
              HomesOnSpec<span className="text-accent-500">.</span>
            </Link>
            <div className="flex items-center gap-1 text-sm text-neutral-600">
              <Link href="/search" className="rounded-full px-3 py-1.5 hover:bg-brand-50 hover:text-brand-700">Search</Link>
              <Link href="/map" className="rounded-full px-3 py-1.5 hover:bg-brand-50 hover:text-brand-700">Map</Link>
              <Link href="/builders" className="rounded-full px-3 py-1.5 hover:bg-brand-50 hover:text-brand-700">Builders</Link>
              <Link href="/compare" className="rounded-full px-3 py-1.5 hover:bg-brand-50 hover:text-brand-700">Compare</Link>
              <Link href="/saved" className="rounded-full px-3 py-1.5 hover:bg-brand-50 hover:text-brand-700">Saved</Link>
              <Link href="/contact" className="btn-accent ml-2">Contact a builder</Link>
            </div>
          </nav>
        </header>

        {/* Top leaderboard banner — gated; renders only when NEXT_PUBLIC_ADSENSE_CLIENT is set. */}
        <AdSlot slot={process.env.NEXT_PUBLIC_ADSENSE_SLOT_TOP} className="mt-4 mb-2" />

        <main>{children}</main>

        {/* Footer banner — separated from listings, honoring the no-mixed-placement promise. */}
        <AdSlot slot={process.env.NEXT_PUBLIC_ADSENSE_SLOT_FOOTER} className="mt-12 mb-2" />

        <footer className="mt-16 border-t border-neutral-200 bg-white py-10 text-center text-xs text-neutral-500">
          <p className="font-display text-sm text-brand-800">
            Every new home. Every builder. One search.
          </p>
          <p className="mt-1">Verified from the source.</p>
          <p className="mt-3">
            Builder inclusion never implies sponsorship. Organic results are never mixed with paid placement.
          </p>
        </footer>
      </body>
    </html>
  );
}