- Enhance Reveal component with HTMLAttributes passthrough and inline style merging - Integrate Reveal into Home, Members, Publications, Lectures, and Standardization sections - Apply lightweight stagger delay (max 360ms cap) without breaking grid stretch (h-full) - Preserve ProjectPageView CSS Scroll Snap direct-child contract via as='article' in-place replacement - Integrate Counter component for metrics and category badges - Support prefers-reduced-motion media query and noscript fallback - All 12 quality gates passed clean with unanimous PASS from all reviewers
144 lines
6.0 KiB
TypeScript
144 lines
6.0 KiB
TypeScript
import React from "react";
|
|
import Reveal from "@/components/Reveal";
|
|
import type { ResearchProject } from "../../content/types";
|
|
import ProjectPageViewControls from "./ProjectPageViewControls";
|
|
|
|
interface ProjectPageViewProps {
|
|
projects?: ResearchProject[];
|
|
}
|
|
|
|
export function ProjectPageView({ projects: projectsProp }: ProjectPageViewProps = {}) {
|
|
const projects = projectsProp ?? [];
|
|
const slugs = projects.map((p) => p.slug);
|
|
const trackId = "research-projects-track";
|
|
|
|
return (
|
|
<section className="space-y-6">
|
|
<Reveal variant="up">
|
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-4 border-b border-line pb-4">
|
|
<div>
|
|
<div className="flex items-center gap-3 mb-1">
|
|
<span className="h-px w-8 bg-cobalt-dark"></span>
|
|
<span className="font-mono text-xs tracking-widest uppercase text-cobalt-dark font-bold">
|
|
Key Initiatives
|
|
</span>
|
|
</div>
|
|
<h2 className="font-serif text-2xl sm:text-3xl font-normal text-ink">
|
|
Research Projects
|
|
</h2>
|
|
</div>
|
|
<p className="text-xs text-ink-mute font-mono">
|
|
{projects.length} core international standardization research projects
|
|
</p>
|
|
</div>
|
|
</Reveal>
|
|
|
|
{/* Scroll-Snap Track: Mobile 1up, Tablet 2up, Desktop Static 3-col Grid */}
|
|
<div
|
|
id={trackId}
|
|
tabIndex={0}
|
|
role="group"
|
|
aria-roledescription="carousel"
|
|
aria-label="연구 프로젝트 목록"
|
|
className="flex gap-6 overflow-x-auto snap-x snap-mandatory scroll-smooth [scrollbar-width:none] [&::-webkit-scrollbar]:hidden desktop:grid desktop:grid-cols-3 desktop:overflow-visible focus:outline-none focus:ring-1 focus:ring-cobalt/50 rounded"
|
|
>
|
|
{projects.map((proj, idx) => (
|
|
<Reveal
|
|
key={proj.slug}
|
|
as="article"
|
|
id={`project-${proj.slug}`}
|
|
role="group"
|
|
aria-roledescription="slide"
|
|
aria-label={`${projects.length}개 중 ${idx + 1}번째 프로젝트`}
|
|
variant="up"
|
|
delay={Math.min(idx, 6) * 60}
|
|
className="snap-start shrink-0 min-w-0 w-full md:w-[calc(50%-12px)] desktop:w-full flex flex-col justify-between bg-paper p-6 rounded-lg border border-line hover:border-cobalt/60 transition-colors space-y-5 break-words [word-break:keep-all] h-full"
|
|
>
|
|
<div className="space-y-4">
|
|
{/* Header Info */}
|
|
<div className="flex items-start justify-between gap-3 border-b border-line/60 pb-3">
|
|
<h3 className="font-serif text-xl font-normal text-ink leading-snug">
|
|
{proj.title}
|
|
</h3>
|
|
{proj.period && (
|
|
<span className="font-mono text-xs text-cobalt-dark bg-ivory border border-line px-2 py-0.5 rounded shrink-0">
|
|
{proj.period}
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* Abstract */}
|
|
<p className="text-sm leading-relaxed text-ink/80">
|
|
{proj.abstract}
|
|
</p>
|
|
|
|
{/* Keywords */}
|
|
<div className="flex flex-wrap gap-1.5 pt-1">
|
|
{proj.keywords.map((kw) => (
|
|
<span
|
|
key={kw}
|
|
className="font-mono text-[0.72rem] text-ink-mute bg-ivory border border-line px-2 py-0.5 rounded"
|
|
>
|
|
#{kw}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Metadata Footer: Standards Track & Deliverables */}
|
|
<div className="space-y-3 pt-3 border-t border-line/60 text-xs font-mono">
|
|
{/* Standards Track / Organization */}
|
|
{proj.funder ? (
|
|
<div className="flex items-baseline justify-between gap-2">
|
|
<span className="text-ink-mute">Funder:</span>
|
|
<span className="text-ink font-semibold">{proj.funder}</span>
|
|
</div>
|
|
) : proj.standardsTrack ? (
|
|
<div className="flex items-baseline justify-between gap-2">
|
|
<span className="text-ink-mute">Organization:</span>
|
|
<span className="text-cobalt-dark font-bold">{proj.standardsTrack}</span>
|
|
</div>
|
|
) : proj.standardsOrg ? (
|
|
<div className="flex items-baseline justify-between gap-2">
|
|
<span className="text-ink-mute">Organization:</span>
|
|
<span className="text-cobalt-dark font-bold">{proj.standardsOrg}</span>
|
|
</div>
|
|
) : null}
|
|
|
|
{/* Key Deliverables */}
|
|
{proj.deliverables.length > 0 && (
|
|
<div className="space-y-1.5 pt-1">
|
|
<span className="text-ink-mute block">Key Deliverables:</span>
|
|
<ul className="space-y-1">
|
|
{proj.deliverables.slice(0, 3).map((item, dIdx) => (
|
|
<li
|
|
key={dIdx}
|
|
className="flex items-start gap-1.5 text-[0.75rem] text-ink/90"
|
|
>
|
|
<span className="text-vermillion font-bold">•</span>
|
|
<span className="line-clamp-2 leading-tight">
|
|
<strong className="font-semibold text-ink">{item.ref}</strong> — {item.title}
|
|
</span>
|
|
</li>
|
|
))}
|
|
{proj.deliverables.length > 3 && (
|
|
<li className="text-[0.7rem] text-ink-mute italic">
|
|
+외 {proj.deliverables.length - 3}건의 규격 문서
|
|
</li>
|
|
)}
|
|
</ul>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</Reveal>
|
|
))}
|
|
</div>
|
|
|
|
{/* Interactive Controls (Hidden on desktop) */}
|
|
<ProjectPageViewControls slugs={slugs} trackId={trackId} />
|
|
</section>
|
|
);
|
|
}
|
|
|
|
export default ProjectPageView;
|