Initial commit

This commit is contained in:
2026-08-01 16:07:57 +02:00
commit 20b254e614
68 changed files with 8467 additions and 0 deletions
+131
View File
@@ -0,0 +1,131 @@
import type { Metadata, Viewport } from "next";
import { Geist, Sora, JetBrains_Mono } from "next/font/google";
import "../globals.css";
import {
locales,
getDictionary,
isValidLocale,
defaultLocale,
type Locale,
} from "@/lib/dictionaries";
import { SITE_URL, localeAlternates } from "@/lib/seo";
import Navbar from "@/components/layout/Navbar";
import Footer from "@/components/layout/Footer";
import ScanlineOverlay from "@/components/ui/ScanlineOverlay";
import LangBanner from "@/components/ui/LangBanner";
const geistSans = Geist({
subsets: ["latin"],
variable: "--font-geist-sans",
display: "swap",
});
const sora = Sora({
subsets: ["latin"],
variable: "--font-sora",
display: "swap",
weight: ["300", "400", "500", "600", "700"],
});
const jetbrains = JetBrains_Mono({
subsets: ["latin"],
variable: "--font-jetbrains",
display: "swap",
weight: ["400", "500"],
});
// Pre-render both locale shells at build time
export function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
// Tell the browser the document is dark before any CSS/JS runs. This sets the
// <meta name="color-scheme"> / theme-color in the SSR <head> so the initial
// canvas (and mobile browser chrome) paints dark, never white.
export const viewport: Viewport = {
colorScheme: "dark",
themeColor: "#0F1117",
};
export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string }>;
}): Promise<Metadata> {
const { locale } = await params;
const isSR = locale === "sr";
return {
title: {
default: isSR
? "Name — Full-stack inženjering, od početka do kraja"
: "Name — Full-stack engineering, end to end",
template: "%s · Name",
},
description: isSR
? "Premium full-stack razvoj, deployment i self-hosted infrastruktura."
: "Premium full-stack development, deployment, and self-hosted infrastructure.",
metadataBase: new URL(SITE_URL),
// Canonical — self-referencing within each locale.
// Subpages override this with their own path via localeAlternates().
alternates: localeAlternates(locale),
openGraph: {
title: isSR
? "Name — Full-stack inženjering"
: "Name — Full-stack engineering, end to end",
description: isSR
? "Premium full-stack razvoj i self-hosted infrastruktura."
: "Premium full-stack development and self-hosted infrastructure.",
type: "website",
locale: isSR ? "sr_RS" : "en_US",
alternateLocale: isSR ? ["en_US"] : ["sr_RS"],
siteName: "ksan.dev",
url: `${SITE_URL}/${locale}`,
},
twitter: {
card: "summary_large_image",
title: isSR ? "Name — Full-stack inženjering" : "Name — Full-stack engineering",
},
robots: {
index: true,
follow: true,
"max-image-preview": "large",
"max-snippet": -1,
},
};
}
export default async function LocaleLayout({
children,
params,
}: {
children: React.ReactNode;
params: Promise<{ locale: string }>;
}) {
const { locale: rawLocale } = await params;
const locale: Locale = isValidLocale(rawLocale) ? rawLocale : defaultLocale;
const dict = getDictionary(locale);
return (
<html
lang={locale}
className={`${geistSans.variable} ${sora.variable} ${jetbrains.variable}`}
// Inline so it is present in the very first HTML bytes — applied before
// the external stylesheet loads and before hydration. This is what kills
// the one-frame white flash (esp. in dev, where CSS is JS-injected).
style={{ colorScheme: "dark", backgroundColor: "#0F1117" }}
>
<body className="relative min-h-screen overflow-x-hidden">
<ScanlineOverlay />
<LangBanner locale={locale} dict={dict} />
<Navbar locale={locale} dict={dict} />
<main className="relative z-10">{children}</main>
<Footer locale={locale} dict={dict} />
</body>
</html>
);
}