fix: resolve review issues B1-B6 (color token alpha contract, Header contrast/budget B plan, Publications Overview page, N1-N8 cleanups) with 100% E2E PASS
This commit is contained in:
@@ -39,16 +39,16 @@
|
||||
--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: 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));
|
||||
@@ -72,16 +72,16 @@ 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;
|
||||
--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);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ export default function HomePage() {
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-serif text-3xl text-cobalt font-normal">
|
||||
220+
|
||||
{Math.floor((publications.length + patents.length) / 10) * 10}+
|
||||
</div>
|
||||
<div className="font-mono text-xs text-ink-mute uppercase tracking-wider mt-1">
|
||||
Publications
|
||||
|
||||
@@ -5,7 +5,6 @@ import { PubCategory } from "../../../content/types";
|
||||
|
||||
interface CategoryNavProps {
|
||||
currentCategory?: PubCategory;
|
||||
totalCount?: number;
|
||||
}
|
||||
|
||||
export function CategoryNav({ currentCategory }: CategoryNavProps) {
|
||||
@@ -20,7 +19,7 @@ export function CategoryNav({ currentCategory }: CategoryNavProps) {
|
||||
href={`/publications/${cat.slug}`}
|
||||
className={`px-4 py-2 rounded text-xs font-mono tracking-wider transition-colors ${
|
||||
isActive
|
||||
? "bg-vermillion text-ivory font-bold"
|
||||
? "bg-vermillion text-white dark:text-ivory font-bold"
|
||||
: "bg-paper text-ink border border-line hover:border-vermillion"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -1,5 +1,128 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import Link from "next/link";
|
||||
import { CategoryNav } from "./_components/CategoryNav";
|
||||
import { PUB_CATEGORIES } from "../../content/categories";
|
||||
import { publications, patents } from "../../content/publications";
|
||||
|
||||
export const metadata = {
|
||||
title: "Publications Overview | ANL",
|
||||
description: "AI 에이전트 네트워크 연구실(ANL) 연구 실적 개요 및 카테고리별 집계",
|
||||
};
|
||||
|
||||
export default function PublicationsIndexPage() {
|
||||
redirect("/publications/intl-journal-conf");
|
||||
const totalPubCount = publications.length + patents.length;
|
||||
|
||||
// Sort publication records by publishedAt descending to select top 10 highlights
|
||||
const top10Highlights = [...publications]
|
||||
.sort((a, b) => (b.publishedAt || "").localeCompare(a.publishedAt || ""))
|
||||
.slice(0, 10);
|
||||
|
||||
const getCount = (slug: string) => {
|
||||
if (slug === "patent") return patents.length;
|
||||
return publications.filter((p) => p.category === slug).length;
|
||||
};
|
||||
|
||||
return (
|
||||
<main className="container-content space-y-8 md:space-y-12 pt-10 md:pt-16 pb-16 md:pb-24">
|
||||
{/* Header section */}
|
||||
<section className="space-y-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-px w-8 bg-vermillion" />
|
||||
<span className="font-mono text-xs font-bold uppercase tracking-widest text-vermillion">
|
||||
BIBLIOGRAPHY & INTELLECTUAL PROPERTY
|
||||
</span>
|
||||
</div>
|
||||
<h1 className="headline-ko text-3xl sm:text-4xl text-ink font-bold">
|
||||
연구 실적 개요
|
||||
</h1>
|
||||
<p className="text-sm sm:text-base text-ink-mute max-w-3xl leading-relaxed">
|
||||
AI 에이전트 네트워크 연구실(ANL)의 학술 논문({publications.length}건)과 특허({patents.length}건)를 포함한 총 {totalPubCount}건의 연구 성과 집계 및 대표 실적 목록입니다.
|
||||
</p>
|
||||
</section>
|
||||
|
||||
{/* Category Navigation */}
|
||||
<CategoryNav />
|
||||
|
||||
{/* Category Summary Cards */}
|
||||
<section className="grid gap-6 md:grid-cols-3">
|
||||
{PUB_CATEGORIES.map((cat) => {
|
||||
const count = getCount(cat.slug);
|
||||
return (
|
||||
<div
|
||||
key={cat.slug}
|
||||
className="flex flex-col justify-between border border-line bg-paper p-6 rounded-lg transition-colors hover:border-vermillion group"
|
||||
>
|
||||
<div className="space-y-3">
|
||||
<span className="font-mono text-xs text-ink-mute uppercase tracking-wider">
|
||||
Category
|
||||
</span>
|
||||
<h2 className="headline-ko text-xl text-ink font-bold group-hover:text-vermillion transition-colors">
|
||||
{cat.label}
|
||||
</h2>
|
||||
<p className="text-xs text-ink-mute leading-relaxed">
|
||||
{cat.desc}
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-6 pt-4 border-t border-line flex items-baseline justify-between">
|
||||
<span className="font-display text-3xl font-bold text-ink">
|
||||
{count}
|
||||
<span className="text-xs font-sans text-ink-mute ml-1 font-normal">건</span>
|
||||
</span>
|
||||
<Link
|
||||
href={`/publications/${cat.slug}`}
|
||||
className="inline-flex items-center text-xs font-bold text-vermillion hover:underline"
|
||||
>
|
||||
전체 보기 →
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</section>
|
||||
|
||||
{/* Top 10 Highlights Section */}
|
||||
<section className="space-y-6 pt-6 border-t border-line">
|
||||
<div className="flex items-center justify-between">
|
||||
<h2 className="headline-ko text-2xl font-bold text-ink">
|
||||
최신 대표 실적 (Top 10 Highlights)
|
||||
</h2>
|
||||
<span className="font-mono text-xs text-ink-mute">
|
||||
RECENT HIGHLIGHTS
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="divide-y divide-line border border-line bg-ivory rounded-lg overflow-hidden">
|
||||
{top10Highlights.map((pub, idx) => (
|
||||
<article key={`${pub.category}-${idx}`} className="p-5 hover:bg-paper/50 transition-colors space-y-2">
|
||||
<div className="flex flex-wrap items-center gap-2 text-xs font-mono text-ink-mute">
|
||||
<span className="px-2 py-0.5 rounded bg-paper border border-line text-ink font-bold">
|
||||
{pub.publishedAt?.substring(0, 7) || "Recent"}
|
||||
</span>
|
||||
<span>·</span>
|
||||
<span className="capitalize">{pub.category.replace(/-/g, " ")}</span>
|
||||
{pub.doi && (
|
||||
<>
|
||||
<span>·</span>
|
||||
<span className="text-vermillion">DOI: {pub.doi}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<h3 className="font-semibold text-base text-ink leading-snug">
|
||||
{pub.title}
|
||||
</h3>
|
||||
{pub.authors && (
|
||||
<p className="text-xs text-ink-mute">
|
||||
{pub.authors}
|
||||
</p>
|
||||
)}
|
||||
{pub.venue && (
|
||||
<p className="text-xs italic text-ink-soft">
|
||||
{pub.venue}
|
||||
</p>
|
||||
)}
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import ThemeToggle from "./ThemeToggle";
|
||||
const navItems = [
|
||||
{ href: "/", ko: "홈", en: "Home", no: "01" },
|
||||
{ href: "/members", ko: "구성원", en: "Members", no: "02" },
|
||||
{ href: "/publications/intl-journal-conf", ko: "논문", en: "Publications", no: "03" },
|
||||
{ href: "/publications", ko: "논문", en: "Publications", no: "03" },
|
||||
{ href: "/lectures", ko: "강의", en: "Lectures", no: "04" },
|
||||
{ href: "/standardization", ko: "표준화", en: "Standardization", no: "05" },
|
||||
];
|
||||
@@ -22,13 +22,13 @@ export default function Header() {
|
||||
pathname
|
||||
? href === "/"
|
||||
? pathname === "/"
|
||||
: pathname === href || pathname.startsWith(`${href}/`) || (href.startsWith("/publications") && pathname.startsWith("/publications"))
|
||||
: pathname === href || pathname.startsWith(`${href}/`)
|
||||
: false;
|
||||
|
||||
return (
|
||||
<header className="sticky top-0 z-50 border-b border-ink bg-ivory/85 backdrop-blur py-2 sm:py-3">
|
||||
<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 pb-2 mb-2 desktop: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">KNU/CSE/ANL</span>
|
||||
{/* CUSTOMIZATION HOOK: masthead tagline */}
|
||||
@@ -39,11 +39,11 @@ export default function Header() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="container-content flex min-h-[4.5rem] items-center justify-between gap-8 py-2 sm:py-3">
|
||||
{/* Brand logo — explicit right margin to guarantee clear separation from 01 HOME */}
|
||||
<div className="container-content flex h-[4.25rem] items-center justify-between gap-4">
|
||||
{/* Brand logo */}
|
||||
<Link
|
||||
href="/"
|
||||
className="group flex flex-col leading-none shrink-0 mr-8 xl:mr-16"
|
||||
className="group flex flex-col leading-none shrink-0"
|
||||
onClick={() => setOpen(false)}
|
||||
>
|
||||
<span className="headline-ko text-lg text-ink sm:text-xl">
|
||||
@@ -55,7 +55,7 @@ export default function Header() {
|
||||
</Link>
|
||||
|
||||
{/* Desktop nav — small caps, wide tracking (visible at >= 1240px) */}
|
||||
<div className="hidden items-center gap-6 desktop:flex ml-auto">
|
||||
<div className="hidden items-center gap-4 desktop:flex">
|
||||
<nav className="flex items-center gap-2" aria-label="주 메뉴">
|
||||
{navItems.map((item) => {
|
||||
const active = isActive(item.href);
|
||||
@@ -64,28 +64,20 @@ export default function Header() {
|
||||
key={item.href}
|
||||
href={item.href}
|
||||
aria-current={active ? "page" : undefined}
|
||||
style={
|
||||
className={`group relative flex items-baseline gap-1.5 text-[0.78rem] font-semibold uppercase tracking-[0.18em] transition-all px-3 py-1.5 rounded-md ${
|
||||
active
|
||||
? { backgroundColor: "var(--vermillion)", color: "#ffffff" }
|
||||
: undefined
|
||||
}
|
||||
className={`group relative flex items-baseline gap-1.5 text-[0.78rem] font-bold uppercase tracking-[0.18em] transition-all px-3.5 py-1.5 rounded-md ${
|
||||
active
|
||||
? "shadow-sm font-bold"
|
||||
? "bg-vermillion text-white dark:text-ivory shadow-sm font-bold"
|
||||
: "text-ink hover:text-vermillion hover:bg-paper/60"
|
||||
}`}
|
||||
>
|
||||
<span
|
||||
style={active ? { color: "#ffffff" } : undefined}
|
||||
className={`font-display text-[0.62rem] ${
|
||||
active ? "opacity-90 font-bold" : "text-ink-mute"
|
||||
active ? "text-white dark:text-ivory" : "text-ink-mute"
|
||||
}`}
|
||||
>
|
||||
{item.no}
|
||||
</span>
|
||||
<span style={active ? { color: "#ffffff" } : undefined}>
|
||||
{item.en}
|
||||
</span>
|
||||
<span>{item.en}</span>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@@ -132,37 +124,25 @@ export default function Header() {
|
||||
href={item.href}
|
||||
onClick={() => setOpen(false)}
|
||||
aria-current={active ? "page" : undefined}
|
||||
style={
|
||||
active
|
||||
? { backgroundColor: "var(--vermillion)", color: "#ffffff" }
|
||||
: undefined
|
||||
}
|
||||
className={`flex items-baseline justify-between px-4 py-3.5 transition-colors rounded-md my-1 ${
|
||||
active
|
||||
? "font-bold shadow-sm"
|
||||
? "bg-vermillion text-white dark:text-ivory font-bold shadow-sm"
|
||||
: "text-ink hover:bg-paper/60"
|
||||
}`}
|
||||
>
|
||||
<span className="flex items-baseline gap-3">
|
||||
<span
|
||||
style={active ? { color: "#ffffff" } : undefined}
|
||||
className={`font-display text-xs ${
|
||||
active ? "opacity-90 font-bold" : "text-ink-mute"
|
||||
active ? "text-white dark:text-ivory" : "text-ink-mute"
|
||||
}`}
|
||||
>
|
||||
{item.no}
|
||||
</span>
|
||||
<span
|
||||
style={active ? { color: "#ffffff" } : undefined}
|
||||
className="headline-ko text-lg font-bold"
|
||||
>
|
||||
{item.ko}
|
||||
</span>
|
||||
<span className="headline-ko text-lg">{item.ko}</span>
|
||||
</span>
|
||||
<span
|
||||
style={active ? { color: "#ffffff" } : undefined}
|
||||
className={`text-[0.7rem] font-semibold uppercase tracking-[0.18em] ${
|
||||
active ? "opacity-90 font-bold" : "text-ink-mute"
|
||||
active ? "text-white dark:text-ivory" : "text-ink-mute"
|
||||
}`}
|
||||
>
|
||||
{item.en}
|
||||
|
||||
@@ -389,77 +389,77 @@ export const publications: Publication[] = [
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Mobility Management Requirements and Framework for Systems Beyond IMT-2000\",",
|
||||
"title": "Mobility Management Requirements and Framework for Systems Beyond IMT-2000",
|
||||
"venue": "Journal of Communications and Networks",
|
||||
"volume": "Vol. 7, No. 2, pp. 171~177, June 2005",
|
||||
"publishedAt": "2005-06"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Transport Layer Mobility Support Utilizing Link Signal Strength Information\",",
|
||||
"title": "Transport Layer Mobility Support Utilizing Link Signal Strength Information",
|
||||
"venue": "IEICE Transactions on Communications",
|
||||
"volume": "Vol. E87-B, No. 9, pp. 2548~2556, September 2004",
|
||||
"publishedAt": "2004-09"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Limiting the Length of BET for Tunnel-Based IP Fast Handover\",",
|
||||
"title": "Limiting the Length of BET for Tunnel-Based IP Fast Handover",
|
||||
"venue": "IEICE Transactions on Fundamentals of Electronics Communications and Computer Sciences",
|
||||
"volume": "Vol. E87-A, No. 6, pp. 1527~1530, June 2004",
|
||||
"publishedAt": "2004-06"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "mSCTP for Soft Handover in Transport Layer\",",
|
||||
"title": "mSCTP for Soft Handover in Transport Layer",
|
||||
"venue": "IEEE Communications Letters",
|
||||
"volume": "Vol. 8, No. 3, pp. 189~191, March 2004",
|
||||
"publishedAt": "2004-03"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Configuration of ACK Trees for Multicast Transport Protocols\",",
|
||||
"title": "Configuration of ACK Trees for Multicast Transport Protocols",
|
||||
"venue": "ETRI Journal",
|
||||
"volume": "Vol. 23, No. 3, pp. 111~120, September 2001",
|
||||
"publishedAt": "2001-09"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Assignment of ADM Rings and DCS Mesh in Telecommunication Networks\",",
|
||||
"title": "Assignment of ADM Rings and DCS Mesh in Telecommunication Networks",
|
||||
"venue": "Journal of the O.R. Society",
|
||||
"volume": "Vol. 52, No. 4, pp. 440~448, April 2001",
|
||||
"publishedAt": "2001-04"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Multicast Delivery Based on Unicast and Subnet Multicast\",",
|
||||
"title": "Multicast Delivery Based on Unicast and Subnet Multicast",
|
||||
"venue": "IEEE Communications Letters",
|
||||
"volume": "Vol. 5, No. 4, pp. 181~183, April 2001",
|
||||
"publishedAt": "2001-04"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Dynamic Bandwidth Allocation Scheme for Multiple Real-time VBR Videos over ATM Networks\",",
|
||||
"title": "Dynamic Bandwidth Allocation Scheme for Multiple Real-time VBR Videos over ATM Networks",
|
||||
"venue": "Telecommunications Systems",
|
||||
"volume": "Vol. 15, pp.359~380, December 2000",
|
||||
"publishedAt": "2000-12"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Minimizing Cost and Delay in Shared Multicast Trees\",",
|
||||
"title": "Minimizing Cost and Delay in Shared Multicast Trees",
|
||||
"venue": "ETRI Journal",
|
||||
"volume": "Vol. 22, No. 1, pp.30~37, March 2000",
|
||||
"publishedAt": "2000-03"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "Non-Core Based Shared Tree Architecture for IP Multicasting\",",
|
||||
"title": "Non-Core Based Shared Tree Architecture for IP Multicasting",
|
||||
"venue": "Electronics Letters",
|
||||
"volume": "Vol. 35, No. 11, pp. 872~873, May 1999",
|
||||
"publishedAt": "1999-05"
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "A Design of the Self-Healing ATM Networks Based on the Backup Virtual Path,",
|
||||
"title": "A Design of the Self-Healing ATM Networks Based on the Backup Virtual Path",
|
||||
"venue": "Computers and Operations Research",
|
||||
"volume": "Vol. 25, No. 7/8, pp. 595~609, July 1998",
|
||||
"publishedAt": "1998-07"
|
||||
@@ -473,7 +473,7 @@ export const publications: Publication[] = [
|
||||
},
|
||||
{
|
||||
"category": "intl-journal-conf",
|
||||
"title": "A Tabu Search for the Survivable Fiber Optic Communication Network Design,",
|
||||
"title": "A Tabu Search for the Survivable Fiber Optic Communication Network Design",
|
||||
"venue": "Computers and Industrial Engineering",
|
||||
"volume": "Vol. 28, Issue 4, pp. 689~700, October 1995",
|
||||
"publishedAt": "1995-10"
|
||||
@@ -1096,7 +1096,7 @@ export const publications: Publication[] = [
|
||||
},
|
||||
{
|
||||
"category": "domestic-journal-conf",
|
||||
"title": "멀티캐스트 전송을 위한 오류제어 기법의 분류\",",
|
||||
"title": "멀티캐스트 전송을 위한 오류제어 기법의 분류",
|
||||
"venue": "ETRI 전자통신동향분석",
|
||||
"volume": "99년 6월호, pp. 76 - 84, 1999.",
|
||||
"publishedAt": "1999",
|
||||
@@ -1476,7 +1476,7 @@ export const patents: Patent[] = [
|
||||
},
|
||||
{
|
||||
"category": "patent",
|
||||
"title": "ESL 신호를 이용하여 위치 기반 서비스를 제공하는 스마트 장치,",
|
||||
"title": "ESL 신호를 이용하여 위치 기반 서비스를 제공하는 스마트 장치",
|
||||
"inventors": "고석주, 김지인, 이민형, 김종근, 박지수, 조정근, 이승일, 서대화",
|
||||
"applicationNo": "2014-0180036",
|
||||
"applicationAt": "2014-12-15",
|
||||
|
||||
@@ -117,7 +117,9 @@ def parse_members():
|
||||
clean_item = re.sub(r'<.*?>', '', clean_item)
|
||||
clean_item = clean_item.strip().replace('\n', ' ')
|
||||
clean_item = re.sub(r'[:&\s]+$', '', clean_item)
|
||||
if "Seok-Joo Koh" in clean_item or "고석주" in clean_item or "Professor" in clean_item:
|
||||
if not clean_item:
|
||||
continue
|
||||
if re.search(r'^Seok-Joo Koh\b', clean_item) or "고석주" in clean_item:
|
||||
en = "Seok-Joo Koh"
|
||||
ko = "고석주"
|
||||
degree = "Professor"
|
||||
@@ -209,7 +211,7 @@ def parse_publications():
|
||||
m_title = re.search(r'<U>(.*?)</U>', item, flags=re.S | re.I)
|
||||
if m_title:
|
||||
raw_t = re.sub(r'<br/?>', '', m_title.group(1), flags=re.I).replace('\n', ' ').strip()
|
||||
title = re.sub(r'^(?:"|“|”|\s)+|(?:"|“|”|\s)+$', '', raw_t).strip()
|
||||
title = re.sub(r'^[\s"“”]+|[\s"“”,]+$', '', raw_t).strip()
|
||||
else:
|
||||
title = ""
|
||||
|
||||
@@ -236,7 +238,7 @@ def parse_publications():
|
||||
m_title = re.search(r'<U>(.*?)</U>', item, flags=re.S | re.I)
|
||||
if m_title:
|
||||
raw_t = re.sub(r'<br/?>', '', m_title.group(1), flags=re.I).replace('\n', ' ').strip()
|
||||
title = re.sub(r'^(?:"|“|”|\s)+|(?:"|“|”|\s)+$', '', raw_t).strip()
|
||||
title = re.sub(r'^[\s"“”]+|[\s"“”,]+$', '', raw_t).strip()
|
||||
else:
|
||||
title = ""
|
||||
|
||||
@@ -266,7 +268,7 @@ def parse_publications():
|
||||
lines = [l.strip() for l in clean.split('\n') if l.strip()]
|
||||
if not lines:
|
||||
continue
|
||||
title = lines[0]
|
||||
title = re.sub(r'^[\s"“”]+|[\s"“”,]+$', '', lines[0]).strip()
|
||||
inventors = ""
|
||||
app_no = ""
|
||||
app_date = None
|
||||
|
||||
@@ -54,6 +54,12 @@ def verify():
|
||||
if "63246" not in d["ref"]:
|
||||
errors.append(f"AC-32 Violation: CCIS project deliverable '{d['ref']}' does not belong to CCIS WG (IEC 63246)")
|
||||
|
||||
# N1: Title artifact check (No trailing quotes or commas in publication titles)
|
||||
for p in intl_p + dom_p + pat_p:
|
||||
t = p.get("title", "")
|
||||
if re.search(r'["“”]|,$', t):
|
||||
errors.append(f"N1 Title Artifact Violation in publication '{p.get('id')}': title '{t}' contains quotes or trailing comma")
|
||||
|
||||
# 1. Count checks
|
||||
if len(active_m) != 16:
|
||||
errors.append(f"Active Members count mismatch: got {len(active_m)}, expected 16")
|
||||
|
||||
@@ -159,18 +159,15 @@ def test_unittest_suite():
|
||||
return True
|
||||
|
||||
def test_layout_architecture():
|
||||
print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / R-8.17) ---")
|
||||
print("\n--- Gate 8: Layout Architecture & No-Escape Gate (AC-25 / AC-26 / AC-40 / AC-41 / 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"),
|
||||
]
|
||||
# 1) Check HTML SSG pages for container-content inside main across ALL SSG routes (C2, AC-25)
|
||||
routes_to_check = EXPECTED_SSG_ROUTES
|
||||
|
||||
main_hashes = {}
|
||||
for rel_p in routes_to_check:
|
||||
if not rel_p.endswith(".html"):
|
||||
continue
|
||||
full_p = os.path.join(REFER_DIR, rel_p)
|
||||
if not os.path.exists(full_p):
|
||||
print(f"❌ Missing SSG route HTML: {rel_p}")
|
||||
@@ -178,46 +175,78 @@ def test_layout_architecture():
|
||||
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)
|
||||
# Verify <main> tag exists and does NOT contain max-w- or px-
|
||||
main_match = re.search(r'<main(?:\s+[^>]*)?>(.*?)</main>', html, flags=re.S)
|
||||
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.")
|
||||
|
||||
main_inner = main_match.group(1)
|
||||
|
||||
# C1: Verify child wrapper inside <main> has container-content
|
||||
if "container-content" not in main_inner and "_not-found" not in rel_p and "404" not in rel_p:
|
||||
print(f"❌ container-content missing inside <main> in {rel_p}")
|
||||
return False
|
||||
|
||||
# Verify child wrapper has container-content
|
||||
if "container-content" not in html:
|
||||
print(f"❌ container-content missing in {rel_p}")
|
||||
return False
|
||||
# AC-41: Check for duplicate <main> normalized content hashes (except 404 pages)
|
||||
if "404" not in rel_p and "_not-found" not in rel_p:
|
||||
norm_content = re.sub(r'\s+', '', main_inner)
|
||||
if norm_content in main_hashes:
|
||||
print(f"❌ AC-41 Violation: Duplicate <main> content found between {rel_p} and {main_hashes[norm_content]}")
|
||||
return False
|
||||
main_hashes[norm_content] = rel_p
|
||||
|
||||
# 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_dir in [os.path.join(REFER_DIR, "app"), os.path.join(REFER_DIR, "components")]:
|
||||
for root, _, files in os.walk(root_dir):
|
||||
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, route uniqueness (AC-41), and AC-25/AC-26 no-escape rule verified")
|
||||
return True
|
||||
|
||||
print("✅ Gate 8 PASSED: Layout 3-tier architecture and AC-25/AC-26 no-escape rule verified")
|
||||
def test_token_contracts():
|
||||
print("\n--- Gate 9: Color Token Alpha Contract & Header Style Gate (AC-35 / AC-36 / AC-37) ---")
|
||||
|
||||
# AC-36: Verify zero commas in --*-rgb definitions in globals.css
|
||||
globals_css = os.path.join(REFER_DIR, "app", "globals.css")
|
||||
with open(globals_css, "r", encoding="utf-8") as f:
|
||||
css_text = f.read()
|
||||
|
||||
rgb_lines = re.findall(r'--[a-z-]+-rgb:\s*([^;]+);', css_text)
|
||||
for line in rgb_lines:
|
||||
if "," in line:
|
||||
print(f"❌ AC-36 Violation: Comma found in --*-rgb definition '{line}' in globals.css")
|
||||
return False
|
||||
|
||||
# AC-35: Verify tailwind.config.ts has <alpha-value> for color tokens
|
||||
tw_config = os.path.join(REFER_DIR, "tailwind.config.ts")
|
||||
with open(tw_config, "r", encoding="utf-8") as f:
|
||||
tw_text = f.read()
|
||||
if "<alpha-value>" not in tw_text:
|
||||
print("❌ AC-35 Violation: <alpha-value> placeholder missing in tailwind.config.ts")
|
||||
return False
|
||||
|
||||
# AC-37: Verify Header.tsx has zero inline style={{ and zero opacity- in active badges
|
||||
header_path = os.path.join(REFER_DIR, "components", "Header.tsx")
|
||||
with open(header_path, "r", encoding="utf-8") as f:
|
||||
header_text = f.read()
|
||||
if "style={{" in header_text or "style={" in header_text:
|
||||
print("❌ AC-37 Violation: Inline style attribute found in Header.tsx")
|
||||
return False
|
||||
if "opacity-" in header_text:
|
||||
print("❌ AC-37 Violation: opacity- modifier found in Header.tsx active badge elements")
|
||||
return False
|
||||
|
||||
print("✅ Gate 9 PASSED: Color token alpha contract, space separation (AC-36), and Header clean styling (AC-37) verified")
|
||||
return True
|
||||
|
||||
def main():
|
||||
@@ -235,6 +264,7 @@ def main():
|
||||
success &= test_theme_tokens()
|
||||
success &= test_unittest_suite()
|
||||
success &= test_layout_architecture()
|
||||
success &= test_token_contracts()
|
||||
|
||||
print("\n" + "-" * 70)
|
||||
if success:
|
||||
|
||||
@@ -33,28 +33,28 @@ const config: Config = {
|
||||
},
|
||||
colors: {
|
||||
ink: {
|
||||
DEFAULT: "var(--ink)",
|
||||
soft: "var(--ink-soft)",
|
||||
mute: "var(--ink-mute)",
|
||||
DEFAULT: "rgb(var(--ink-rgb) / <alpha-value>)",
|
||||
soft: "rgb(var(--ink-soft-rgb) / <alpha-value>)",
|
||||
mute: "rgb(var(--ink-mute-rgb) / <alpha-value>)",
|
||||
},
|
||||
ivory: "var(--ivory)",
|
||||
paper: "var(--paper)",
|
||||
line: "var(--line)",
|
||||
ivory: "rgb(var(--ivory-rgb) / <alpha-value>)",
|
||||
paper: "rgb(var(--paper-rgb) / <alpha-value>)",
|
||||
line: "rgb(var(--line-rgb) / <alpha-value>)",
|
||||
vermillion: {
|
||||
DEFAULT: "var(--vermillion)",
|
||||
dark: "var(--vermillion-dark)",
|
||||
DEFAULT: "rgb(var(--vermillion-rgb) / <alpha-value>)",
|
||||
dark: "rgb(var(--vermillion-dark-rgb) / <alpha-value>)",
|
||||
},
|
||||
cobalt: {
|
||||
DEFAULT: "var(--cobalt)",
|
||||
dark: "var(--cobalt-dark)",
|
||||
DEFAULT: "rgb(var(--cobalt-rgb) / <alpha-value>)",
|
||||
dark: "rgb(var(--cobalt-dark-rgb) / <alpha-value>)",
|
||||
},
|
||||
// Back-compat aliases so any stray legacy class still resolves.
|
||||
brand: {
|
||||
DEFAULT: "var(--vermillion)",
|
||||
dark: "var(--vermillion-dark)",
|
||||
light: "var(--cobalt)",
|
||||
DEFAULT: "rgb(var(--vermillion-rgb) / <alpha-value>)",
|
||||
dark: "rgb(var(--vermillion-dark-rgb) / <alpha-value>)",
|
||||
light: "rgb(var(--cobalt-rgb) / <alpha-value>)",
|
||||
},
|
||||
accent: { DEFAULT: "var(--cobalt)" },
|
||||
accent: { DEFAULT: "rgb(var(--cobalt-rgb) / <alpha-value>)" },
|
||||
},
|
||||
fontFamily: {
|
||||
// English display serif — magazine cover scale.
|
||||
|
||||
Reference in New Issue
Block a user