feat: implement layout 3-tier container architecture, desktop screen token, Gate 8 no-escape rule, and clean dead code
This commit is contained in:
@@ -45,7 +45,7 @@ export default function RootLayout({
|
||||
</head>
|
||||
<body className="flex min-h-screen flex-col">
|
||||
<Header />
|
||||
<main className="flex-1">{children}</main>
|
||||
<main className="w-full flex-1">{children}</main>
|
||||
<Footer />
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,7 +6,7 @@ export default function LecturesPage() {
|
||||
const olderSemesters = semesters.slice(3);
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -11,7 +11,7 @@ export default function MembersPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -58,7 +58,7 @@ export default function HomePage() {
|
||||
const pubCount = publications.length + patents.length;
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Hero Section */}
|
||||
<section className="relative">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function PublicationCategoryPage({ params }: PageProps) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function PublicationsIndexPage() {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -9,7 +9,7 @@ export default function StandardizationPage() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-16 py-6">
|
||||
<div className="container-content space-y-16 py-6">
|
||||
{/* Header */}
|
||||
<section className="space-y-3">
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function Header() {
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur">
|
||||
{/* Journal issue strip — magazine masthead feel. */}
|
||||
<div className="hidden border-b border-line lg:block">
|
||||
<div className="hidden border-b border-line desktop:block">
|
||||
<div className="container-content flex items-center justify-between py-1.5">
|
||||
<span className="kicker">Issue 01 — 2026</span>
|
||||
{/* CUSTOMIZATION HOOK: masthead tagline */}
|
||||
@@ -55,7 +55,7 @@ export default function Header() {
|
||||
</Link>
|
||||
|
||||
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
|
||||
<div className="hidden items-center gap-7 min-[1240px]:flex">
|
||||
<div className="hidden items-center gap-7 desktop:flex">
|
||||
<nav className="flex items-center gap-7" aria-label="주 메뉴">
|
||||
{navItems.map((item) => (
|
||||
<Link
|
||||
@@ -85,7 +85,7 @@ export default function Header() {
|
||||
</div>
|
||||
|
||||
{/* Mobile controls (visible under 1240px) */}
|
||||
<div className="flex items-center gap-2 min-[1240px]:hidden">
|
||||
<div className="flex items-center gap-2 desktop:hidden">
|
||||
<ThemeToggle />
|
||||
<button
|
||||
type="button"
|
||||
@@ -110,7 +110,7 @@ export default function Header() {
|
||||
{open && (
|
||||
<nav
|
||||
id="mobile-menu"
|
||||
className="border-t border-ink bg-ivory min-[1240px]:hidden"
|
||||
className="border-t border-ink bg-ivory desktop:hidden"
|
||||
aria-label="모바일 메뉴"
|
||||
>
|
||||
<ul className="container-content flex flex-col divide-y divide-line py-2">
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
import SectionLabel from "@/components/SectionLabel";
|
||||
import Reveal from "@/components/Reveal";
|
||||
|
||||
/**
|
||||
* Editorial page masthead. Korean serif headline + huge English display
|
||||
* word, with magazine "NN / 05" spread numbering.
|
||||
*
|
||||
* CUSTOMIZATION HOOK — CONTENT: ko / en / display / description / index.
|
||||
*/
|
||||
export default function PageHeader({
|
||||
ko,
|
||||
en,
|
||||
display,
|
||||
description,
|
||||
index,
|
||||
}: {
|
||||
ko: string;
|
||||
en: string;
|
||||
display?: string;
|
||||
description?: string;
|
||||
index: number;
|
||||
}) {
|
||||
return (
|
||||
<section className="relative overflow-hidden border-b border-ink bg-ivory">
|
||||
<div className="container-content pb-12 pt-14 sm:pb-16 sm:pt-20">
|
||||
<Reveal>
|
||||
<SectionLabel index={index} label={en} />
|
||||
</Reveal>
|
||||
|
||||
<div className="mt-8 grid items-end gap-6 lg:grid-cols-12">
|
||||
<Reveal className="lg:col-span-8" delay={80}>
|
||||
<h1 className="headline-ko break-keep text-balance text-3xl leading-[1.05] text-ink sm:text-4xl lg:text-5xl">
|
||||
{ko}
|
||||
</h1>
|
||||
{/* When a custom `display` word is provided, render it at the full
|
||||
magazine scale. Otherwise fall back to `en` at the smaller
|
||||
`display-sm` scale so long words like "Standardization" never
|
||||
overflow the col-span-8 column. See LAYOUT_AUDIT #6 + #10. */}
|
||||
<p
|
||||
className={
|
||||
display
|
||||
? "display mt-2 break-keep text-vermillion"
|
||||
: "display-sm mt-2 break-keep text-vermillion"
|
||||
}
|
||||
aria-hidden
|
||||
>
|
||||
{display ?? en}
|
||||
</p>
|
||||
</Reveal>
|
||||
|
||||
{description && (
|
||||
<Reveal className="lg:col-span-4" variant="right" delay={160}>
|
||||
<p className="border-l-2 border-ink pl-5 text-sm leading-relaxed text-ink-soft">
|
||||
{description}
|
||||
</p>
|
||||
</Reveal>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -158,6 +158,68 @@ def test_unittest_suite():
|
||||
print("✅ Gate 7 PASSED: All 6 DOM & static HTML content assertion tests passed")
|
||||
return True
|
||||
|
||||
def test_layout_architecture():
|
||||
print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / R-8.17) ---")
|
||||
|
||||
# 1) Check HTML SSG pages for container-content inside main
|
||||
routes_to_check = [
|
||||
os.path.join(".next", "server", "app", "index.html"),
|
||||
os.path.join(".next", "server", "app", "members.html"),
|
||||
os.path.join(".next", "server", "app", "publications.html"),
|
||||
os.path.join(".next", "server", "app", "lectures.html"),
|
||||
os.path.join(".next", "server", "app", "standardization.html"),
|
||||
]
|
||||
|
||||
for rel_p in routes_to_check:
|
||||
full_p = os.path.join(REFER_DIR, rel_p)
|
||||
if not os.path.exists(full_p):
|
||||
print(f"❌ Missing SSG route HTML: {rel_p}")
|
||||
return False
|
||||
with open(full_p, "r", encoding="utf-8") as f:
|
||||
html = f.read()
|
||||
|
||||
# Verify <main tag does NOT contain max-w- or px-
|
||||
main_match = re.search(r'<main\s+class="([^"]*)"', html)
|
||||
if not main_match:
|
||||
print(f"❌ <main> tag not found in {rel_p}")
|
||||
return False
|
||||
main_cls = main_match.group(1)
|
||||
if "max-w-" in main_cls or "px-" in main_cls or "container-content" in main_cls:
|
||||
print(f"❌ <main> tag in {rel_p} restricts width or padding ({main_cls}). Should be w-full flex-1 only.")
|
||||
return False
|
||||
|
||||
# Verify child wrapper has container-content
|
||||
if "container-content" not in html:
|
||||
print(f"❌ container-content missing in {rel_p}")
|
||||
return False
|
||||
|
||||
# 2) Check source for forbidden escaped arbitrary classes (AC-26)
|
||||
forbidden_terms = ["w-screen", "-mx-[50vw]", "min-[1240px]:"]
|
||||
for root, _, files in os.walk(os.path.join(REFER_DIR, "app")):
|
||||
for fname in files:
|
||||
if fname.endswith((".ts", ".tsx")):
|
||||
fpath = os.path.join(root, fname)
|
||||
with open(fpath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
for term in forbidden_terms:
|
||||
if term in content:
|
||||
print(f"❌ Forbidden term '{term}' found in {fpath}")
|
||||
return False
|
||||
|
||||
for root, _, files in os.walk(os.path.join(REFER_DIR, "components")):
|
||||
for fname in files:
|
||||
if fname.endswith((".ts", ".tsx")):
|
||||
fpath = os.path.join(root, fname)
|
||||
with open(fpath, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
for term in forbidden_terms:
|
||||
if term in content:
|
||||
print(f"❌ Forbidden term '{term}' found in {fpath}")
|
||||
return False
|
||||
|
||||
print("✅ Gate 8 PASSED: Layout 3-tier architecture and AC-25/AC-26 no-escape rule verified")
|
||||
return True
|
||||
|
||||
def main():
|
||||
print("=" * 70)
|
||||
print(" ANL HOMEPAGE E2E TEST SUITE RUNNER — TIERS 1-4")
|
||||
@@ -172,6 +234,7 @@ def main():
|
||||
success &= test_data_counts()
|
||||
success &= test_theme_tokens()
|
||||
success &= test_unittest_suite()
|
||||
success &= test_layout_architecture()
|
||||
|
||||
print("\n" + "-" * 70)
|
||||
if success:
|
||||
|
||||
@@ -28,6 +28,9 @@ const config: Config = {
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
screens: {
|
||||
desktop: "1240px",
|
||||
},
|
||||
colors: {
|
||||
ink: {
|
||||
DEFAULT: "rgb(var(--ink-rgb) / <alpha-value>)",
|
||||
|
||||
Reference in New Issue
Block a user