diff --git a/messages/en.json b/messages/en.json
index 9294c07..3addc30 100644
--- a/messages/en.json
+++ b/messages/en.json
@@ -172,5 +172,32 @@
"WorkspaceEditorFooter": {
"Row": "Row",
"Column": "Column"
+ },
+ "HomePage": {
+ "MainView": {
+ "title": "Judge4c",
+ "description": "All in one place to support coding learning and teaching:",
+ "features": {
+ "feature1": "Integrated Language Services with Code Completion & Diagnostics",
+ "feature2": "Multi-language Support + Real-time Execution Feedback for Faster Learning",
+ "feature3": "World's First Open-Source Next.js + Monaco LSP Implementation"
+ },
+ "quickStart": "Quickstart",
+ "contactUs": "Contact Us"
+ },
+ "FAQs": {
+ "title": "Frequently Asked Questions",
+ "description": "Can't find your answer? Contact us!",
+ "questions": {
+ "question1": "Which code editor is used?",
+ "answer1": "Built with Microsoft's Monaco Editor",
+ "question2": "What programming languages are supported?",
+ "answer2": "Full support for C/C++ (with LSP integration), syntax highlighting for other languages",
+ "question3": "How do theme and language settings work?",
+ "answer3": "Themes auto-switch (system-aware) or can be manually set; language defaults to browser preference with manual override",
+ "question4": "What styling solutions are used for the editor and docs?",
+ "answer4": "Editor uses @shikijs/monaco themes, documentation rendered with github-markdown-css"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/messages/zh.json b/messages/zh.json
index ec17cf9..7b97b99 100644
--- a/messages/zh.json
+++ b/messages/zh.json
@@ -172,5 +172,32 @@
"WorkspaceEditorFooter": {
"Row": "行",
"Column": "列"
+ },
+ "HomePage": {
+ "MainView": {
+ "title": "Judge4c",
+ "description": "一站式助力编程学习与教学:",
+ "features": {
+ "feature1": "语言服务集成,内置代码补全与诊断",
+ "feature2": "多语言支持 + 实时运行反馈,学习更高效",
+ "feature3": "全球首个 Next.js + Monaco LSP 开源解决方案"
+ },
+ "quickStart": "即刻启程",
+ "contactUs": "联系我们"
+ },
+ "FAQs": {
+ "title": "常见问题",
+ "description": "未找到答案?欢迎联系我们!",
+ "questions": {
+ "question1": "使用什么代码编辑器?",
+ "answer1": "基于 @monaco-editor/react 开发",
+ "question2": "支持哪些编程语言?",
+ "answer2": "支持 C/C++ (集成LSP),其他语言支持语法高亮",
+ "question3": "如何设置主题和语言?",
+ "answer3": "主题支持自动切换(跟随系统)或手动设置;语言默认跟随浏览器,可手动修改",
+ "question4": "编辑器主题和文档样式方案?",
+ "answer4": "编辑器采用 @shikijs/monaco, 文档采用 github-markdown-css 样式"
+ }
+ }
}
}
\ No newline at end of file
diff --git a/public/background.png b/public/background.png
new file mode 100644
index 0000000..e8ea6d3
Binary files /dev/null and b/public/background.png differ
diff --git a/src/app/(app)/page.tsx b/src/app/(app)/page.tsx
index 38a4833..7bc5eb0 100644
--- a/src/app/(app)/page.tsx
+++ b/src/app/(app)/page.tsx
@@ -1,5 +1,15 @@
-import { redirect } from "next/navigation";
+import FAQs from "@/components/faqs";
+import { Header } from "@/components/header";
+import { Footer } from "@/components/footer";
+import { MainView } from "@/components/main-view";
export default function HomePage() {
- redirect("/problemset");
+ return (
+ <>
+
+
+
+
+ >
+ )
}
diff --git a/src/components/container.tsx b/src/components/container.tsx
new file mode 100644
index 0000000..8d3fc67
--- /dev/null
+++ b/src/components/container.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils";
+
+export function Container({
+ className,
+ ...props
+}: React.ComponentPropsWithoutRef<"div">) {
+ return (
+
+ );
+}
diff --git a/src/components/faqs.tsx b/src/components/faqs.tsx
new file mode 100644
index 0000000..8fbb72a
--- /dev/null
+++ b/src/components/faqs.tsx
@@ -0,0 +1,56 @@
+import { useTranslations } from "next-intl";
+
+export default function FAQs() {
+ const t = useTranslations("HomePage.FAQs");
+
+ const faqs = [
+ {
+ id: 1,
+ question: t("questions.question1"),
+ answer: t("questions.answer1"),
+ },
+ {
+ id: 2,
+ question: t("questions.question2"),
+ answer: t("questions.answer2"),
+ },
+ {
+ id: 3,
+ question: t("questions.question3"),
+ answer: t("questions.answer3"),
+ },
+ {
+ id: 4,
+ question: t("questions.question4"),
+ answer: t("questions.answer4"),
+ }
+ ];
+ return (
+
+
+
+
+ {t("title")}
+
+
+ {t("description")}
+
+
+
+
+ {faqs.map((faq) => (
+
+
-
+ {faq.question}
+
+ -
+ {faq.answer}
+
+
+ ))}
+
+
+
+
+ );
+}
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
new file mode 100644
index 0000000..7106c45
--- /dev/null
+++ b/src/components/footer.tsx
@@ -0,0 +1,47 @@
+import { SVGProps } from "react";
+import { siteConfig } from "@/config/site";
+import { Container } from "@/components/container";
+
+const navigation = {
+ social: [
+ {
+ name: "GitHub",
+ href: siteConfig.url.repo.github,
+ icon: (props: SVGProps) => (
+
+ ),
+ },
+ ],
+};
+
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/src/components/header.tsx b/src/components/header.tsx
new file mode 100644
index 0000000..a9c2f6c
--- /dev/null
+++ b/src/components/header.tsx
@@ -0,0 +1,25 @@
+import Link from "next/link";
+import { Logo } from "@/components/logo";
+import { Container } from "@/components/container";
+import { ThemeToggle } from "@/components/theme-toggle";
+import { LanguageSettings } from "@/components/language-settings";
+
+export async function Header() {
+ return (
+
+
+
+ );
+}
diff --git a/src/components/logo.tsx b/src/components/logo.tsx
new file mode 100644
index 0000000..5c4f3d2
--- /dev/null
+++ b/src/components/logo.tsx
@@ -0,0 +1,94 @@
+export function Logomark(props: React.ComponentPropsWithoutRef<"svg">) {
+ return (
+
+ );
+}
+
+export function Logo(props: React.ComponentPropsWithoutRef<"svg">) {
+ return (
+
+ );
+}
diff --git a/src/components/main-view.tsx b/src/components/main-view.tsx
new file mode 100644
index 0000000..1a49829
--- /dev/null
+++ b/src/components/main-view.tsx
@@ -0,0 +1,62 @@
+import Link from "next/link";
+import Image from "next/image";
+import { siteConfig } from "@/config/site";
+import { useTranslations } from "next-intl";
+import { Button } from "@/components/ui/button";
+import { Container } from "@/components/container";
+import { TypingEffect } from "@/components/typing-effect";
+
+export function MainView() {
+ const t = useTranslations("HomePage.MainView");
+
+ return (
+
+
+
+
+
+ {t("title")}
+
+
+ {t("description")}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx
new file mode 100644
index 0000000..ad95307
--- /dev/null
+++ b/src/components/theme-toggle.tsx
@@ -0,0 +1,48 @@
+"use client";
+
+import { useTheme } from "next-themes";
+import { useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
+
+function SunIcon(props: React.ComponentPropsWithoutRef<"svg">) {
+ return (
+
+ );
+}
+
+function MoonIcon(props: React.ComponentPropsWithoutRef<"svg">) {
+ return (
+
+ );
+}
+
+export function ThemeToggle() {
+ const { resolvedTheme, setTheme } = useTheme();
+ const otherTheme = resolvedTheme === "dark" ? "light" : "dark";
+ const [mounted, setMounted] = useState(false);
+
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ return (
+
+ );
+}
diff --git a/src/components/typing-effect.tsx b/src/components/typing-effect.tsx
new file mode 100644
index 0000000..324293a
--- /dev/null
+++ b/src/components/typing-effect.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { motion } from "framer-motion";
+import { useTranslations } from "next-intl";
+import { useEffect, useState, useMemo } from "react";
+
+export function TypingEffect() {
+ const t = useTranslations("HomePage.MainView.features");
+ const texts = useMemo(
+ () => [t("feature1"), t("feature2"), t("feature3")],
+ [t]
+ );
+ const [index, setIndex] = useState(0);
+ const [displayText, setDisplayText] = useState("");
+ const [isDeleting, setIsDeleting] = useState(false);
+
+ useEffect(() => {
+ const handleTyping = () => {
+ const currentText = texts[index];
+
+ if (isDeleting) {
+ setDisplayText(currentText.substring(0, displayText.length - 1));
+ } else {
+ setDisplayText(currentText.substring(0, displayText.length + 1));
+ }
+
+ if (!isDeleting && displayText === currentText) {
+ setTimeout(() => setIsDeleting(true), 1000);
+ } else if (isDeleting && displayText === "") {
+ setIsDeleting(false);
+ setIndex((prevIndex) => (prevIndex + 1) % texts.length);
+ }
+ };
+
+ const typingSpeed = isDeleting ? 50 : 100;
+ const timer = setTimeout(handleTyping, typingSpeed);
+
+ return () => clearTimeout(timer);
+ }, [displayText, isDeleting, index, texts]);
+
+ return (
+
+ {displayText}
+ |
+
+ );
+}