feat: complete ANL lab homepage with light/dark theme, responsive layout, and 100% SSG E2E test suite

This commit is contained in:
2026-07-30 20:15:40 +09:00
parent d833252217
commit 36a4cd851e
20 changed files with 1266 additions and 105 deletions
@@ -46,12 +46,12 @@ export default function HeroComposition({
width="368"
height="368"
fill="none"
stroke="#0A0A0A"
stroke="var(--ink)"
strokeWidth="1.5"
/>
{/* ---- QUIC: flowing multiplexed streams (cobalt) ---- */}
<g stroke="#1B3A8A" fill="none" strokeWidth="2.5" strokeLinecap="round">
<g stroke="var(--cobalt)" fill="none" strokeWidth="2.5" strokeLinecap="round">
{[0, 1, 2, 3].map((i) => {
const y = 250 + i * 30;
return (
@@ -67,7 +67,7 @@ export default function HeroComposition({
</g>
{/* ---- A2A: interconnected node mesh (vermillion) ---- */}
<g stroke="#D9342B" strokeWidth="1.5">
<g stroke="var(--vermillion)" strokeWidth="1.5">
{edges.map(([a, b], i) => (
<line
key={i}
@@ -79,7 +79,7 @@ export default function HeroComposition({
/>
))}
</g>
<g fill="#D9342B">
<g fill="var(--vermillion)">
{nodes.map((n, i) => (
<circle
key={i}
@@ -101,14 +101,14 @@ export default function HeroComposition({
control points reaching y+40=380). See LAYOUT_AUDIT #3 and
REVIEW §3 "P1 #3" — keeping them at y=24 (mirror corners) preserves
a symmetric layout that the original LAYOUT_AUDIT mirror decided on. */}
<text x="20" y="24" fill="#0A0A0A" fontSize="11" fontWeight="600" letterSpacing="2">
<text x="20" y="24" fill="var(--ink)" fontSize="11" fontWeight="600" letterSpacing="2">
A2A · MESH
</text>
<text
x="360"
y="24"
textAnchor="end"
fill="#0A0A0A"
fill="var(--ink)"
fontSize="11"
fontWeight="600"
letterSpacing="2"
+72 -7
View File
@@ -2,6 +2,27 @@
@tailwind components;
@tailwind utilities;
/* ==================================================================
OFFLINE GEIST FONT STACK (@font-face)
================================================================== */
@font-face {
font-family: 'Geist';
src: url('/fonts/geist/Geist-Variable.woff2') format('woff2-variations'),
url('/fonts/geist/Geist-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Geist Mono';
src: url('/fonts/geist/GeistMono-Variable.woff2') format('woff2-variations'),
url('/fonts/geist/GeistMono-Variable.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
/* ==================================================================
EDITORIAL DESIGN SYSTEM — global tokens & type
CUSTOMIZATION HOOK: design tokens are mirrored from tailwind.config.ts
@@ -9,18 +30,62 @@
================================================================== */
:root {
color-scheme: light;
--ink: #0a0a0a;
--ivory: #f5f1e8;
--paper: #ece6d6;
--line: #d8d1c0;
--vermillion: #d9342b;
--cobalt: #1b3a8a;
/* Offline System & CSS Font Variables */
--font-geist: 'Geist', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--font-geist-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
--font-display: 'Fraunces', Georgia, Cambria, 'Times New Roman', Times, serif;
--font-serif-ko: 'Noto Serif KR', 'Nanum Myeongjo', Batang, Georgia, serif;
--font-sans: 'Geist', 'Pretendard', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
--font-mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--ink-rgb: 10, 10, 10;
--ink-soft-rgb: 38, 36, 32;
--ink-mute-rgb: 107, 101, 91;
--ivory-rgb: 245, 241, 232;
--paper-rgb: 236, 230, 214;
--line-rgb: 216, 209, 192;
--vermillion-rgb: 217, 52, 43;
--vermillion-dark-rgb: 168, 35, 28;
--cobalt-rgb: 27, 58, 138;
--cobalt-dark-rgb: 18, 40, 99;
--ink: rgb(var(--ink-rgb));
--ink-soft: rgb(var(--ink-soft-rgb));
--ink-mute: rgb(var(--ink-mute-rgb));
--ivory: rgb(var(--ivory-rgb));
--paper: rgb(var(--paper-rgb));
--line: rgb(var(--line-rgb));
--vermillion: rgb(var(--vermillion-rgb));
--vermillion-dark: rgb(var(--vermillion-dark-rgb));
--cobalt: rgb(var(--cobalt-rgb));
--cobalt-dark: rgb(var(--cobalt-dark-rgb));
--grain-color: rgba(10, 10, 10, 0.022);
/* CUSTOMIZATION HOOK — MOTION: global reveal timing */
--reveal-duration: 600ms;
--reveal-ease: cubic-bezier(0.16, 1, 0.3, 1);
}
html.dark,
html[data-theme="dark"] {
color-scheme: dark;
--ink-rgb: 245, 241, 232;
--ink-soft-rgb: 216, 209, 192;
--ink-mute-rgb: 160, 153, 142;
--ivory-rgb: 18, 18, 18;
--paper-rgb: 30, 28, 26;
--line-rgb: 51, 48, 43;
--vermillion-rgb: 235, 75, 66;
--vermillion-dark-rgb: 255, 107, 97;
--cobalt-rgb: 77, 119, 255;
--cobalt-dark-rgb: 112, 148, 255;
--grain-color: rgba(245, 241, 232, 0.03);
}
html {
scroll-behavior: smooth;
}
@@ -29,7 +94,7 @@ body {
@apply bg-ivory text-ink font-sans antialiased;
/* Subtle paper grain — pure CSS, no asset. */
background-image: radial-gradient(
rgba(10, 10, 10, 0.022) 1px,
var(--grain-color) 1px,
transparent 1px
);
background-size: 4px 4px;
+18 -33
View File
@@ -1,37 +1,8 @@
import type { Metadata } from "next";
import { Fraunces, Noto_Serif_KR, Inter } from "next/font/google";
import "./globals.css";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
/**
* CUSTOMIZATION HOOK — FONTS
* The editorial system pairs an English display serif (Fraunces) and a
* Korean serif (Noto Serif KR) for headlines, with a clean sans for body.
* Pretendard is preferred for body but is not served by Google Fonts, so
* Inter is loaded as the web fallback (see tailwind.config.ts sans stack).
*/
const fraunces = Fraunces({
subsets: ["latin"],
weight: ["300", "400", "500", "600"],
style: ["normal", "italic"],
variable: "--font-display",
display: "swap",
});
const notoSerifKr = Noto_Serif_KR({
subsets: ["latin"],
weight: ["400", "600", "700"],
variable: "--font-serif-ko",
display: "swap",
});
const inter = Inter({
subsets: ["latin"],
variable: "--font-sans",
display: "swap",
});
export const metadata: Metadata = {
title: {
default: "ANL · AI 에이전트 네트워크 연구실",
@@ -41,17 +12,31 @@ export const metadata: Metadata = {
"경북대학교 컴퓨터학부 AI 에이전트 네트워크 연구실 (ANL). QUIC, gRPC, A2A 기반 에이전트 통신 및 멀티에이전트 오케스트레이션 연구.",
};
const themeScript = `
(function() {
try {
var storedTheme = localStorage.getItem('theme');
var supportDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
if (storedTheme === 'dark' || (!storedTheme && supportDarkMode)) {
document.documentElement.classList.add('dark');
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.classList.remove('dark');
document.documentElement.setAttribute('data-theme', 'light');
}
} catch (e) {}
})();
`;
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html
lang="ko"
className={`${fraunces.variable} ${notoSerifKr.variable} ${inter.variable}`}
>
<html lang="ko">
<head>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
{/* No-JS fallback for the .reveal scroll-reveal: without this, every
page is blank if hydration fails (e.g. Google Fonts request blocked). */}
<noscript>
+30
View File
@@ -0,0 +1,30 @@
import React from "react";
import Link from "next/link";
export default function NotFound() {
return (
<div className="container-content py-20 sm:py-32 flex flex-col items-center justify-center text-center min-h-[60vh]">
<div className="border border-line bg-paper p-8 sm:p-12 rounded-lg max-w-xl w-full space-y-6 shadow-sm">
<div className="inline-block px-3 py-1 bg-vermillion/10 text-vermillion font-mono text-xs font-bold rounded tracking-widest uppercase">
404 Error
</div>
<h1 className="font-serif text-3xl sm:text-4xl font-normal text-ink leading-tight">
</h1>
<p className="text-ink-mute text-sm sm:text-base leading-relaxed">
.
<br />
.
</p>
<div className="pt-4">
<Link
href="/"
className="inline-flex items-center justify-center px-6 py-3 border border-ink bg-ink text-ivory hover:bg-vermillion hover:border-vermillion transition-colors duration-200 text-sm font-medium rounded-md"
>
</Link>
</div>
</div>
</div>
);
}