2d7e7f2fe1
- Add a light theme with a header toggle; the choice is remembered and applied before paint - Make the globe, product visuals and footer logo follow the theme - Give the message field a full outline, strengthen the captcha box border and keep autofilled fields readable - Accept website addresses without https:// - List the info email under every office - Keep the phone header on one row: languages move into the menu below 480px
99 lines
2.9 KiB
TypeScript
99 lines
2.9 KiB
TypeScript
import { Suspense } from "react";
|
|
import type { Metadata, Viewport } from "next";
|
|
import { Inter, Noto_Sans_Arabic } from "next/font/google";
|
|
import "../globals.css";
|
|
import SiteHeader from "@/components/site-header";
|
|
import SiteFooter from "@/components/site-footer";
|
|
import Analytics, { AnalyticsNoScript } from "@/components/analytics";
|
|
import { themeScript } from "@/components/theme-toggle";
|
|
import { dirOf, locales, localize, ogLocale } from "@/i18n/config";
|
|
import { mainNav } from "@/i18n/nav";
|
|
import { getUI } from "@/i18n/server";
|
|
import { localeAlternates } from "@/lib/metadata";
|
|
import { SITE_URL } from "@/lib/routes";
|
|
|
|
const inter = Inter({
|
|
variable: "--font-inter",
|
|
subsets: ["latin"],
|
|
display: "swap",
|
|
});
|
|
|
|
// Only Arabic pages use it, so it isn't preloaded everywhere.
|
|
const arabic = Noto_Sans_Arabic({
|
|
variable: "--font-arabic",
|
|
subsets: ["arabic"],
|
|
display: "swap",
|
|
preload: false,
|
|
});
|
|
|
|
export function generateStaticParams() {
|
|
return locales.map((lang) => ({ lang }));
|
|
}
|
|
|
|
export async function generateMetadata(): Promise<Metadata> {
|
|
const { locale, t } = await getUI();
|
|
return {
|
|
metadataBase: new URL(SITE_URL),
|
|
title: { default: t.meta.title, template: "%s" },
|
|
description: t.meta.description,
|
|
alternates: localeAlternates("/", locale),
|
|
// Google Search Console ownership.
|
|
verification: { google: "LnRawiMDpDtTVBC2X34We70f8OHBLMVC6e9IWoHCZLs" },
|
|
openGraph: {
|
|
type: "website",
|
|
url: localize("/", locale),
|
|
siteName: "AI Growth Technologies",
|
|
locale: ogLocale[locale],
|
|
title: t.meta.title,
|
|
description: t.meta.description,
|
|
},
|
|
};
|
|
}
|
|
|
|
export const viewport: Viewport = {
|
|
themeColor: "#000000",
|
|
colorScheme: "dark light",
|
|
};
|
|
|
|
export default async function RootLayout({ children }: LayoutProps<"/[lang]">) {
|
|
const { locale, t } = await getUI();
|
|
const headerLabels = {
|
|
primary: t.nav.primary,
|
|
bookCall: t.bookCall,
|
|
openMenu: t.nav.openMenu,
|
|
closeMenu: t.nav.closeMenu,
|
|
language: t.nav.language,
|
|
logoHome: t.logoHome,
|
|
lightTheme: t.nav.lightTheme,
|
|
darkTheme: t.nav.darkTheme,
|
|
};
|
|
|
|
return (
|
|
<html
|
|
lang={locale}
|
|
dir={dirOf(locale)}
|
|
className={`${inter.variable} ${arabic.variable}`}
|
|
data-scroll-behavior="smooth"
|
|
// The theme script sets data-theme before React hydrates.
|
|
suppressHydrationWarning
|
|
>
|
|
<head>
|
|
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
|
</head>
|
|
<body>
|
|
<AnalyticsNoScript />
|
|
<a href="#main" className="skip-link">
|
|
{t.skip}
|
|
</a>
|
|
{/* The header reads the pathname, which is request data on dynamic routes. */}
|
|
<Suspense>
|
|
<SiteHeader locale={locale} nav={mainNav(t, locale)} t={headerLabels} />
|
|
</Suspense>
|
|
<main id="main">{children}</main>
|
|
<SiteFooter />
|
|
<Analytics />
|
|
</body>
|
|
</html>
|
|
);
|
|
}
|