Files
multica/apps/web/app/layout.tsx
T
5f1f08e466 feat(web): add use-cases content pipeline with welcome page (MUL-2349) (#2795)
* feat(web): add use-cases content pipeline with welcome page (MUL-2349)

Wire fumadocs-mdx into apps/web with an independent collection rooted at
content/use-cases/. Add the first page at /use-cases/welcome (header + H1 +
prose + screenshot + footer) using the about-page visual shell.

- source.config.ts + lib/use-cases-source.ts (separate from apps/docs)
- features/landing/components/mdx/screenshot.tsx wraps next/image
- public/use-cases/welcome/screenshot-1.png placeholder (55KB)
- next.config.ts wraps NextConfig with createMDX()
- .gitignore + eslint ignore .source/

Co-authored-by: multica-agent <[email protected]>

* feat(web): bilingual db-boy use case with cookie locale (MUL-2349)

Extends the use-cases pipeline into the first real article.

- ZH + EN MDX (auto-data-analysis.{zh,en}.mdx) sharing three real
  screenshots; sensitive fields on db-boy-profile.png (RDS host, DB
  name, password) are blurred in-place.
- Cookie-based locale: /use-cases/<slug> reads multica-locale
  server-side via lib/use-cases-i18n.ts (mirrors LandingLayout's
  cookie + Accept-Language fallback). Same URL serves either language;
  no [lang] segment so all other landing routes stay unchanged.
- Frontmatter schema (source.config.ts): z.looseObject with declared
  hero_image / updated_at (required) / category (optional); a
  preprocess converts YAML-auto-parsed Date back to a YYYY-MM-DD string.
- MDX components factory createMdxComponents(locale) routes the
  secondary CTA to /docs/zh (ZH) or /docs (EN); internal MDX links
  use <Link> for SPA nav; full-width and half-width colons both
  trigger [CTA: ...] / [占位图: ...] markers; 副 and Secondary
  both work as the secondary CTA prefix.
- Index page localizes hero / subtitle / card CTA / metadata; sort
  fallback uses an epoch placeholder so undefined-order disappears.
- Landing header + footer surface use-cases entry in both locales.
- Detail route: sticky header, right-rail TOC with anchor jumps,
  scroll-mt-[100px] on H2/H3 so anchor jumps don't slip under the
  sticky header.
- Drop welcome demo page.

Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>

* fix(web): resolve code review blockers on use-cases PR

- Add `use-cases` to reserved_slugs.json + regenerate TS (P1: prevent
  future workspace slug collision)
- Fix dead links in both MDX files: /features/* → /docs/* (P2)
- Remove duplicate brand suffix in page title metadata (nit)

Co-Authored-By: Claude Opus 4.7 <[email protected]>
Co-authored-by: multica-agent <[email protected]>

* fix(web): align usecases locale routing

* chore: refresh web mdx lockfile

* fix(web): type mdx next config adapter

* fix(web): wrap settings route page

---------

Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]>
2026-05-25 10:05:17 +08:00

136 lines
4.5 KiB
TypeScript

import type { Metadata, Viewport } from "next";
import { Inter, Geist_Mono, Source_Serif_4 } from "next/font/google";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@multica/ui/components/ui/sonner";
import { cn } from "@multica/ui/lib/utils";
import { WebProviders } from "@/components/web-providers";
import type { SupportedLocale } from "@multica/core/i18n";
import { RESOURCES } from "@multica/views/locales";
import { getRequestLocale } from "@/lib/request-locale";
import "./globals.css";
// Font stack: Inter for Latin UI text + system Chinese fonts for zh content.
// Desktop app uses the same stack via apps/desktop/src/renderer/src/globals.css —
// keep the CJK fallback tail in sync across both files. The Inter primary family
// differs by design: next/font produces `__Inter_xxx` (with a synthetic size-adjusted
// fallback face to prevent FOUT layout shift); desktop uses fontsource's "Inter Variable".
// Both resolve to Inter glyphs, so rendering is identical in practice.
// Currently covers English + Simplified Chinese. When ja/ko i18n lands, extend
// the tail with Hiragino Kaku Gothic ProN / Yu Gothic / Apple SD Gothic Neo / Malgun Gothic.
// Per-character fallback: Latin chars render with Inter, Chinese chars with
// PingFang SC (macOS) / Microsoft YaHei (Windows) / Noto Sans CJK SC (Linux).
const inter = Inter({
subsets: ["latin"],
variable: "--font-sans",
fallback: [
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"PingFang SC",
"Microsoft YaHei",
"Noto Sans CJK SC",
"sans-serif",
],
});
// Mono font has no explicit CJK fallback: CJK chars in code blocks are inherently
// non-aligned with a mono grid (Chinese is proportional), so listing CJK fonts
// here would falsely signal alignment guarantees. Browser default fallback handles
// the rare mixed case correctly.
const geistMono = Geist_Mono({
subsets: ["latin"],
variable: "--font-mono",
fallback: ["ui-monospace", "SFMono-Regular", "Menlo", "Consolas", "monospace"],
});
// Editorial serif used for onboarding headlines. Italic support for h1 em
// accents (e.g. "...on one shared board."). Only loaded on routes that
// render the font; layout-shift-prevention handled by next/font's synthetic
// fallback metrics, same as Inter.
const sourceSerif = Source_Serif_4({
subsets: ["latin"],
style: ["normal", "italic"],
variable: "--font-serif",
fallback: [
"ui-serif",
"Iowan Old Style",
"Apple Garamond",
"Baskerville",
"Times New Roman",
"serif",
],
});
export const viewport: Viewport = {
width: "device-width",
initialScale: 1,
themeColor: [
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
{ media: "(prefers-color-scheme: dark)", color: "#05070b" },
],
};
export const metadata: Metadata = {
metadataBase: new URL("https://www.multica.ai"),
title: {
default: "Multica — Project Management for Human + Agent Teams",
template: "%s | Multica",
},
description:
"Open-source platform that turns coding agents into real teammates. Assign tasks, track progress, compound skills.",
icons: {
icon: [{ url: "/favicon.svg", type: "image/svg+xml" }],
shortcut: ["/favicon.svg"],
},
openGraph: {
type: "website",
siteName: "Multica",
locale: "en_US",
},
twitter: {
card: "summary_large_image",
site: "@multica_hq",
creator: "@multica_hq",
},
alternates: {
canonical: "/",
},
robots: {
index: true,
follow: true,
},
};
// HTML lang attribute uses BCP-47 region tags that screen readers and font
// stacks recognize widely. i18next keeps `zh-Hans` as its internal locale
// (script subtag is what we actually translate against), but the html element
// expects a region-flavoured tag for accessibility tooling and CJK fallback.
const HTML_LANG: Record<SupportedLocale, string> = {
en: "en",
"zh-Hans": "zh-CN",
};
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const locale = await getRequestLocale();
const resources = { [locale]: RESOURCES[locale] };
return (
<html
lang={HTML_LANG[locale]}
suppressHydrationWarning
className={cn("antialiased font-sans h-full", inter.variable, geistMono.variable, sourceSerif.variable)}
>
<body className="h-full overflow-hidden">
<ThemeProvider>
<WebProviders locale={locale} resources={resources}>
{children}
</WebProviders>
<Toaster />
</ThemeProvider>
</body>
</html>
);
}