1026 lines
35 KiB
HTML
1026 lines
35 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>FE AI 워크플로우</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@400;700&family=Inter:wght@400;700;900&family=JetBrains+Mono:wght@400;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* ===== CSS Custom Properties ===== */
|
|
:root {
|
|
--bg: #0a0a0f;
|
|
--bg-subtle: #111118;
|
|
--accent: #C6A55C;
|
|
--accent-dim: rgba(198, 165, 92, 0.15);
|
|
--accent-glow: rgba(198, 165, 92, 0.3);
|
|
--text-primary: #F0EBE0;
|
|
--text-secondary: #B8B0A0;
|
|
--text-dim: #706858;
|
|
--border: #1e1c18;
|
|
|
|
--font-kr: 'Noto Sans KR', sans-serif;
|
|
--font-en: 'Inter', sans-serif;
|
|
--font-mono: 'JetBrains Mono', monospace;
|
|
|
|
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
|
}
|
|
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
font-family: var(--font-kr), var(--font-en), sans-serif;
|
|
overflow: hidden;
|
|
background: var(--bg);
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.slide-viewport {
|
|
position: fixed;
|
|
inset: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
background: var(--bg);
|
|
overflow: hidden;
|
|
}
|
|
|
|
.slide-viewport.animating { pointer-events: none; }
|
|
|
|
.slide {
|
|
position: absolute;
|
|
inset: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: clamp(2rem, 5vw, 5rem);
|
|
|
|
opacity: 0;
|
|
transform: translateX(100%);
|
|
pointer-events: none;
|
|
transition:
|
|
opacity 400ms ease-in-out,
|
|
transform 450ms cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
}
|
|
|
|
.slide.active {
|
|
opacity: 1;
|
|
transform: translateX(0);
|
|
pointer-events: auto;
|
|
z-index: 2;
|
|
}
|
|
|
|
.slide.stand-left { transform: translateX(-100%); }
|
|
|
|
.slide::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: repeating-linear-gradient(0deg,
|
|
transparent, transparent 2px,
|
|
rgba(255, 255, 255, 0.005) 2px,
|
|
rgba(255, 255, 255, 0.005) 4px);
|
|
pointer-events: none;
|
|
z-index: 1;
|
|
}
|
|
|
|
.slide::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background-image:
|
|
linear-gradient(rgba(198, 165, 92, 0.015) 1px, transparent 1px),
|
|
linear-gradient(90deg, rgba(198, 165, 92, 0.015) 1px, transparent 1px);
|
|
background-size: 40px 40px;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.slide > * { position: relative; z-index: 2; }
|
|
|
|
.slide__inner {
|
|
width: 100%;
|
|
max-width: 680px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
flex: 1;
|
|
gap: clamp(0.8rem, 2vw, 1.5rem);
|
|
}
|
|
|
|
.slide__header {}
|
|
.slide__body { display: flex; flex-direction: column; justify-content: center; gap: 1rem; }
|
|
.slide__footer { font-size: clamp(0.6rem, 1vw, 0.8rem); color: var(--text-dim); text-align: right; margin-top: auto; }
|
|
|
|
h1 {
|
|
font-family: var(--font-kr), var(--font-en), sans-serif;
|
|
font-size: clamp(2rem, 5vw, 4rem);
|
|
font-weight: 700;
|
|
line-height: 1.15;
|
|
color: var(--text-primary);
|
|
}
|
|
h2 {
|
|
font-family: var(--font-kr), var(--font-en), sans-serif;
|
|
font-size: clamp(1.4rem, 3vw, 2.4rem);
|
|
font-weight: 700;
|
|
line-height: 1.2;
|
|
margin-bottom: 0.8rem;
|
|
color: var(--text-primary);
|
|
}
|
|
h3 {
|
|
font-family: var(--font-kr), var(--font-en), sans-serif;
|
|
font-size: clamp(1.1rem, 2vw, 1.6rem);
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
color: var(--text-primary);
|
|
}
|
|
p, li {
|
|
font-family: var(--font-kr), var(--font-en), sans-serif;
|
|
font-size: clamp(0.85rem, 1.5vw, 1.2rem);
|
|
line-height: 1.7;
|
|
color: var(--text-secondary);
|
|
}
|
|
ul { list-style: none; padding: 0; }
|
|
ul li {
|
|
padding: clamp(0.5rem, 0.8vw, 0.7rem) clamp(0.8rem, 1.2vw, 1rem);
|
|
background: rgba(255,255,255,0.02);
|
|
border: 1px solid var(--border);
|
|
border-radius: 4px;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.6rem;
|
|
}
|
|
ul li::before { content: '→ '; color: var(--accent); font-weight: bold; font-family: var(--font-mono); flex-shrink: 0; }
|
|
|
|
.slide--intro { background: radial-gradient(ellipse at 30% 50%, rgba(198,165,92,0.06) 0%, var(--bg) 70%); }
|
|
.slide--content { background: radial-gradient(ellipse at 70% 40%, rgba(198,165,92,0.04) 0%, var(--bg) 70%); }
|
|
.slide--content-alt { background: radial-gradient(ellipse at 30% 60%, rgba(198,165,92,0.04) 0%, var(--bg) 70%); }
|
|
.slide--code { background: var(--bg); }
|
|
.slide--diagram { background: radial-gradient(ellipse at 50% 30%, rgba(198,165,92,0.05) 0%, var(--bg) 70%); }
|
|
.slide--end { background: radial-gradient(ellipse at 50% 50%, rgba(198,165,92,0.08) 0%, var(--bg) 60%); }
|
|
|
|
.reveal {
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
transition: opacity 0.6s var(--ease-out-expo), transform 0.6s var(--ease-out-expo);
|
|
}
|
|
|
|
.slide.active .reveal {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.reveal:nth-child(1) { transition-delay: 0.08s; }
|
|
.reveal:nth-child(2) { transition-delay: 0.16s; }
|
|
.reveal:nth-child(3) { transition-delay: 0.24s; }
|
|
.reveal:nth-child(4) { transition-delay: 0.32s; }
|
|
.reveal:nth-child(5) { transition-delay: 0.40s; }
|
|
.reveal:nth-child(6) { transition-delay: 0.48s; }
|
|
.reveal:nth-child(7) { transition-delay: 0.56s; }
|
|
.reveal:nth-child(8) { transition-delay: 0.64s; }
|
|
|
|
.slide.active .slide__body > .reveal:nth-child(1) { transition-delay: 0.15s; }
|
|
.slide.active .slide__body > .reveal:nth-child(2) { transition-delay: 0.25s; }
|
|
.slide.active .slide__body > .reveal:nth-child(3) { transition-delay: 0.35s; }
|
|
.slide.active .slide__body > .reveal:nth-child(4) { transition-delay: 0.45s; }
|
|
.slide.active .slide__body > .reveal:nth-child(5) { transition-delay: 0.55s; }
|
|
|
|
.slide.active ul li.reveal:nth-child(1) { transition-delay: 0.15s; }
|
|
.slide.active ul li.reveal:nth-child(2) { transition-delay: 0.25s; }
|
|
.slide.active ul li.reveal:nth-child(3) { transition-delay: 0.35s; }
|
|
.slide.active ul li.reveal:nth-child(4) { transition-delay: 0.45s; }
|
|
.slide.active ul li.reveal:nth-child(5) { transition-delay: 0.55s; }
|
|
|
|
.code-block {
|
|
background: #111118;
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
overflow: hidden;
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.65rem, 1.1vw, 0.9rem);
|
|
line-height: 1.7;
|
|
counter-reset: line-number;
|
|
width: 100%;
|
|
}
|
|
.code-block::before {
|
|
content: attr(data-language);
|
|
display: block;
|
|
background: rgba(198,165,92,0.06);
|
|
color: var(--text-dim);
|
|
font-size: 0.75em;
|
|
padding: 0.4em 1em;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.1em;
|
|
font-family: var(--font-mono);
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.code-block code { display: block; padding: 1rem 1.2rem; overflow-x: auto; }
|
|
.code-block .line {
|
|
display: block;
|
|
padding: 2px 0 2px 3em;
|
|
position: relative;
|
|
counter-increment: line-number;
|
|
transition: background-color 0.3s ease, opacity 0.3s ease;
|
|
color: var(--text-secondary);
|
|
}
|
|
.code-block .line::before {
|
|
content: counter(line-number);
|
|
position: absolute;
|
|
left: 0;
|
|
width: 2.5em;
|
|
text-align: right;
|
|
color: #3a3830;
|
|
font-size: 0.85em;
|
|
user-select: none;
|
|
}
|
|
.code-block .line--highlight {
|
|
background: rgba(198, 165, 92, 0.08);
|
|
border-left: 3px solid var(--accent);
|
|
padding-left: calc(3em - 3px);
|
|
}
|
|
.code-block .line--highlight::before { color: var(--accent); }
|
|
.code-block.dim-others .line:not(.line--highlight) { opacity: 0.3; }
|
|
|
|
.kw { color: #C6A55C; }
|
|
.fn { color: #D4B87A; }
|
|
.str { color: #A89070; }
|
|
.cmt { color: #555040; font-style: italic; }
|
|
.var { color: #E0D0B0; }
|
|
.op { color: #8A8070; }
|
|
.tag { color: #C6A55C; }
|
|
.attr { color: #D4B87A; }
|
|
.val { color: #A89070; }
|
|
|
|
.split { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; align-items: start; }
|
|
|
|
.split-card {
|
|
padding: clamp(0.8rem, 1.5vw, 1.2rem);
|
|
background: rgba(255,255,255,0.02);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
}
|
|
|
|
.info-card {
|
|
padding: clamp(1rem, 2vw, 1.5rem);
|
|
background: rgba(255,255,255,0.02);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
width: 100%;
|
|
}
|
|
|
|
.info-card .card-title {
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.7rem, 1vw, 0.85rem);
|
|
color: var(--text-dim);
|
|
margin-bottom: 0.8rem;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.badge {
|
|
display: inline-block;
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.65rem, 0.9vw, 0.8rem);
|
|
color: var(--accent);
|
|
padding: 0.2em 0.6em;
|
|
border: 1px solid var(--accent-dim);
|
|
border-radius: 100px;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.terminal-badge {
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
|
color: var(--accent);
|
|
letter-spacing: 0.1em;
|
|
text-transform: uppercase;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.terminal-badge::before {
|
|
content: '> ';
|
|
animation: blink 1s step-end infinite;
|
|
}
|
|
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
|
|
.accent-line {
|
|
width: 50px;
|
|
height: 2px;
|
|
background: var(--accent);
|
|
margin: 0.5rem 0;
|
|
}
|
|
|
|
/* ===== Step 플로우 컴포넌트 ===== */
|
|
.step-flow {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 0.6rem;
|
|
align-items: center;
|
|
}
|
|
|
|
.step-item {
|
|
padding: clamp(0.7rem, 1.2vw, 1rem);
|
|
background: rgba(255,255,255,0.02);
|
|
border: 1px solid var(--border);
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
}
|
|
|
|
.step-item .step-num {
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.6rem, 0.8vw, 0.75rem);
|
|
color: var(--accent);
|
|
margin-bottom: 0.3rem;
|
|
}
|
|
|
|
.step-item .step-label {
|
|
font-size: clamp(0.75rem, 1.1vw, 0.95rem);
|
|
color: var(--text-primary);
|
|
font-weight: 700;
|
|
line-height: 1.3;
|
|
}
|
|
|
|
.step-item .step-sub {
|
|
font-family: var(--font-mono);
|
|
font-size: clamp(0.6rem, 0.8vw, 0.7rem);
|
|
color: var(--text-dim);
|
|
margin-top: 0.25rem;
|
|
}
|
|
|
|
.step-arrow {
|
|
text-align: center;
|
|
color: var(--accent);
|
|
font-size: clamp(0.8rem, 1.2vw, 1rem);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* ===== 네비게이션 UI ===== */
|
|
.progress-bar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 2px;
|
|
background: rgba(198,165,92,0.1);
|
|
z-index: 101;
|
|
}
|
|
.progress-fill {
|
|
height: 100%;
|
|
width: 0%;
|
|
background: var(--accent);
|
|
transition: width 0.35s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.slide-counter {
|
|
position: fixed;
|
|
bottom: 16px;
|
|
right: 24px;
|
|
font-size: 12px;
|
|
color: var(--text-dim);
|
|
font-family: var(--font-mono);
|
|
z-index: 102;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
|
|
.nav-buttons {
|
|
position: fixed;
|
|
bottom: 16px;
|
|
left: 24px;
|
|
display: flex;
|
|
gap: 6px;
|
|
z-index: 102;
|
|
}
|
|
.nav-btn {
|
|
width: 32px;
|
|
height: 32px;
|
|
border: 1px solid rgba(198,165,92,0.2);
|
|
background: rgba(198,165,92,0.04);
|
|
color: var(--text-dim);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
|
}
|
|
.nav-btn:hover { background: rgba(198,165,92,0.12); color: var(--accent); border-color: rgba(198,165,92,0.4); }
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.reveal, .slide { transition-duration: 0.1s !important; }
|
|
.reveal { transform: none !important; }
|
|
}
|
|
|
|
@media (hover: none) and (pointer: coarse) {
|
|
.nav-btn { width: 44px; height: 44px; }
|
|
}
|
|
|
|
@media (max-height: 600px) {
|
|
.slide { padding: clamp(1rem, 3vw, 2rem); }
|
|
h1 { font-size: clamp(1.5rem, 4vw, 2.5rem); }
|
|
h2 { font-size: clamp(1.2rem, 2.5vw, 1.8rem); }
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="slide-viewport" id="viewport">
|
|
|
|
<!-- ============ 슬라이드 01: 타이틀 ============ -->
|
|
<section class="slide slide--intro active" id="slide-1"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 1" aria-hidden="false">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">FE AI Workflow</div>
|
|
<h1 class="reveal">프론트엔드<br>AI 워크플로우</h1>
|
|
</div>
|
|
<div class="slide__body">
|
|
<p class="reveal" style="font-size: clamp(0.95rem, 1.8vw, 1.3rem);">
|
|
Claude Code로 완성하는<br>팀 공통 AI 개발 프로세스
|
|
</p>
|
|
<div class="accent-line reveal"></div>
|
|
<p class="reveal" style="font-size: clamp(0.7rem, 1vw, 0.85rem); color: var(--text-dim);">
|
|
← → 키 또는 스와이프로 이동
|
|
</p>
|
|
</div>
|
|
<div class="slide__footer">gameservice-fe-agent · 2026.05</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 02: WHY AI ============ -->
|
|
<section class="slide slide--content" id="slide-2"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 2" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Why AI?</div>
|
|
<h2 class="reveal">개발 패러다임의 변화</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<ul>
|
|
<li class="reveal">반복 작업을 AI에게 위임 — 개발자는 설계에 집중</li>
|
|
<li class="reveal">Figma → 코드, 기획서 → 스펙, 코드 → 번역 키 자동화</li>
|
|
<li class="reveal">팀 공통 규칙을 AI에 주입해 일관성 있는 산출물 보장</li>
|
|
<li class="reveal">리뷰·검수 자동화로 휴먼 에러 사전 차단</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 03: 도구 스택 ============ -->
|
|
<section class="slide slide--diagram" id="slide-3"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 3" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Tool Stack</div>
|
|
<h2 class="reveal">우리 팀 AI 도구 스택</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<div class="split">
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.6rem;">
|
|
<strong style="color: var(--accent);">Claude Code</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
메인 AI 에이전트<br>
|
|
슬래시 커맨드 + 스킬로<br>
|
|
팀 워크플로우 자동화
|
|
</p>
|
|
</div>
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.6rem;">
|
|
<strong style="color: #D4B87A;">Figma MCP</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
디자인 파일 직접 파싱<br>
|
|
컴포넌트 구조 분석<br>
|
|
HTML/CSS 자동 생성
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="split reveal" style="margin-top: 0;">
|
|
<div class="split-card">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.6rem;">
|
|
<strong style="color: #B89860;">Confluence MCP</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
업무일지 자동 작성<br>
|
|
기획 문서 파싱
|
|
</p>
|
|
</div>
|
|
<div class="split-card">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.6rem;">
|
|
<strong style="color: #A08848;">CLAUDE.md</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
팀 규칙 영구 주입<br>
|
|
프로젝트별 컨텍스트
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 04: 전체 워크플로우 ============ -->
|
|
<section class="slide slide--content-alt" id="slide-4"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 4" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Overview</div>
|
|
<h2 class="reveal">4단계 AI 워크플로우</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<div class="step-flow reveal">
|
|
<div class="step-item">
|
|
<div class="step-num">STEP 01</div>
|
|
<div class="step-label">기획<br>분석</div>
|
|
<div class="step-sub">/plan-analyzer</div>
|
|
</div>
|
|
<div class="step-item">
|
|
<div class="step-num">STEP 02</div>
|
|
<div class="step-label">컴포넌트<br>구현</div>
|
|
<div class="step-sub">Figma → Vue</div>
|
|
</div>
|
|
<div class="step-item">
|
|
<div class="step-num">STEP 03</div>
|
|
<div class="step-label">번역·EDM<br>작업</div>
|
|
<div class="step-sub">/markup-edm</div>
|
|
</div>
|
|
<div class="step-item">
|
|
<div class="step-num">STEP 04</div>
|
|
<div class="step-label">검수 &<br>커밋</div>
|
|
<div class="step-sub">/verify</div>
|
|
</div>
|
|
</div>
|
|
<div class="info-card reveal">
|
|
<div class="card-title">// 공통 원칙</div>
|
|
<p style="font-size: clamp(0.8rem, 1.2vw, 1rem);">
|
|
각 단계마다 Claude Code 스킬이 연결되어 있습니다.<br>
|
|
팀 규칙(CLAUDE.md)이 모든 단계에 자동 적용됩니다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 05: STEP 1 기획 분석 ============ -->
|
|
<section class="slide slide--content" id="slide-5"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 5" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Step 01</div>
|
|
<h2 class="reveal">기획 분석 — plan-analyzer</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<ul>
|
|
<li class="reveal">PPT / 기획서를 넣으면 개발 스펙으로 변환</li>
|
|
<li class="reveal">컴포넌트 목록, API 목록, 번역 키 후보 자동 추출</li>
|
|
<li class="reveal">Confluence 기획 페이지 직접 파싱 가능</li>
|
|
<li class="reveal">산출물을 팀 템플릿에 맞게 문서화</li>
|
|
</ul>
|
|
<div class="info-card reveal">
|
|
<div class="card-title">// 실행 방법</div>
|
|
<p style="font-size: clamp(0.78rem, 1.15vw, 0.98rem); font-family: var(--font-mono); color: var(--accent);">
|
|
/plan-analyzer [기획서 파일 또는 URL]
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 06: STEP 2 컴포넌트 구현 ============ -->
|
|
<section class="slide slide--code" id="slide-6"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 6" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal" style="margin-bottom: 0.4rem;">Step 02</div>
|
|
<h2 class="reveal" style="color: var(--accent);">Figma → Vue 컴포넌트</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<pre class="code-block reveal" data-language="vue" data-highlight-lines="1,4-6"><code><span class="line"><span class="cmt"><!-- Figma MCP로 디자인 파싱 후 자동 생성 --></span></span>
|
|
<span class="line"><span class="op"><</span><span class="tag">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="str">"ts"</span><span class="op">></span></span>
|
|
<span class="line"><span class="kw">import</span> { <span class="var">ref</span> } <span class="kw">from</span> <span class="str">'vue'</span>;</span>
|
|
<span class="line"><span class="kw">const</span> <span class="var">props</span> = <span class="fn">defineProps</span><{ <span class="var">title</span>: <span class="kw">string</span> }>();</span>
|
|
<span class="line"><span class="kw">const</span> <span class="var">isOpen</span> = <span class="fn">ref</span>(<span class="str">false</span>);</span>
|
|
<span class="line"><span class="op"></</span><span class="tag">script</span><span class="op">></span></span>
|
|
<span class="line"><span class="op"><</span><span class="tag">template</span><span class="op">></span></span>
|
|
<span class="line"> <span class="op"><</span><span class="tag">div</span> <span class="attr">class</span>=<span class="str">"component-root"</span><span class="op">></span>...</span>
|
|
<span class="line"> <span class="op"></</span><span class="tag">div</span><span class="op">></span></span>
|
|
<span class="line"><span class="op"></</span><span class="tag">template</span><span class="op">></span></span></code></pre>
|
|
<p class="reveal" style="font-size: clamp(0.7rem, 1.1vw, 0.95rem); color: var(--text-dim); margin-top: 0.5rem;">
|
|
script setup · defineProps 제네릭 · Tailwind 클래스 — 팀 컨벤션 자동 적용
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 07: STEP 3 번역 키 & EDM ============ -->
|
|
<section class="slide slide--diagram" id="slide-7"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 7" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Step 03</div>
|
|
<h2 class="reveal">번역 키 & EDM 마크업</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<div class="split">
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: var(--accent);">plan-translation-generator</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
기획서에서 번역 키 후보 추출<br>
|
|
i18n JSON 자동 생성<br>
|
|
네이밍 컨벤션 자동 적용
|
|
</p>
|
|
</div>
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: #D4B87A;">markup-edm</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
Figma → HTML Table 마크업<br>
|
|
아웃룩/다크모드 호환 보장<br>
|
|
단계별 검수 가이드 포함
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="info-card reveal" style="margin-top: 0;">
|
|
<div class="card-title">// 공통 특징</div>
|
|
<p style="font-size: clamp(0.78rem, 1.15vw, 0.98rem);">
|
|
두 스킬 모두 Figma MCP 연동으로 디자인을 직접 읽어 산출물을 생성합니다.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 08: STEP 4 검수 & 커밋 ============ -->
|
|
<section class="slide slide--content-alt" id="slide-8"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 8" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Step 04</div>
|
|
<h2 class="reveal">코드 검수 & 커밋</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<ul>
|
|
<li class="reveal"><strong style="color: var(--accent);">/verify-component-review</strong> — 컴포넌트가 Figma 디자인과 일치하는지 자동 검수</li>
|
|
<li class="reveal"><strong style="color: var(--accent);">/code-review</strong> — PR 코드 품질, 컨벤션, 타입 안전성 검토</li>
|
|
<li class="reveal"><strong style="color: var(--accent);">/conventional-commit</strong> — 변경사항 분석 후 팀 규칙에 맞는 커밋 메시지 생성</li>
|
|
<li class="reveal"><strong style="color: var(--accent);">/work-log</strong> — Confluence에 업무일지 자동 작성</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 09: 스킬 시스템 ============ -->
|
|
<section class="slide slide--content" id="slide-9"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 9" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">Skill System</div>
|
|
<h2 class="reveal">Claude Code 스킬 시스템</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<div class="info-card reveal">
|
|
<div class="card-title">// 구조</div>
|
|
<p style="font-size: clamp(0.78rem, 1.15vw, 0.98rem);">
|
|
<span style="color: var(--accent); font-family: var(--font-mono);">gameservice-fe-agent/skills/</span>에서 팀 공용 스킬 관리<br>
|
|
각 프로젝트는 심볼릭 링크로 연결 (<span style="color: var(--accent); font-family: var(--font-mono);">scripts/link-skills.sh</span>)
|
|
</p>
|
|
</div>
|
|
<div class="info-card reveal">
|
|
<div class="card-title">// 업데이트</div>
|
|
<p style="font-size: clamp(0.78rem, 1.15vw, 0.98rem);">
|
|
공통 저장소에 PR → 머지 후<br>
|
|
각 프로젝트에서 <span style="color: var(--accent); font-family: var(--font-mono);">scripts/update.sh</span> 실행으로 최신화
|
|
</p>
|
|
</div>
|
|
<div class="info-card reveal">
|
|
<div class="card-title">// 호출</div>
|
|
<p style="font-size: clamp(0.78rem, 1.15vw, 0.98rem); font-family: var(--font-mono); color: var(--accent);">
|
|
/스킬명 [선택적 인자]
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 10: 팀 규칙 + AI ============ -->
|
|
<section class="slide slide--diagram" id="slide-10"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 10" aria-hidden="true">
|
|
<div class="slide__inner">
|
|
<div class="slide__header">
|
|
<div class="terminal-badge reveal">CLAUDE.md</div>
|
|
<h2 class="reveal">팀 규칙 + AI 연동</h2>
|
|
</div>
|
|
<div class="slide__body">
|
|
<div class="split">
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: var(--accent);">coding-conventions</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
네이밍 규칙, 포맷팅,<br>
|
|
import 순서 등 자동 준수
|
|
</p>
|
|
</div>
|
|
<div class="split-card reveal">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: #D4B87A;">framework-rules</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
Vue 3 / Nuxt / Tailwind<br>
|
|
사용 패턴 일관성 보장
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="split reveal" style="margin-top: 0;">
|
|
<div class="split-card">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: #B89860;">commit-pr</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
Conventional Commits<br>
|
|
PR 템플릿 자동 적용
|
|
</p>
|
|
</div>
|
|
<div class="split-card">
|
|
<p style="font-size: clamp(0.8rem, 1.3vw, 1.05rem); margin-bottom: 0.5rem;">
|
|
<strong style="color: #A08848;">claude-workflow</strong>
|
|
</p>
|
|
<p style="font-size: clamp(0.75rem, 1.1vw, 0.95rem);">
|
|
탐색 → 계획 → 구현 →<br>
|
|
검증 순서 강제 준수
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ============ 슬라이드 11: 마무리 ============ -->
|
|
<section class="slide slide--end" id="slide-11"
|
|
role="region" aria-roledescription="슬라이드" aria-label="슬라이드 11" aria-hidden="true">
|
|
<div class="slide__inner" style="text-align: center; align-items: center;">
|
|
<div class="slide__body" style="align-items: center;">
|
|
<h1 class="reveal">AI와 함께하는<br>FE 개발</h1>
|
|
<div class="accent-line reveal" style="margin: 0.8rem auto;"></div>
|
|
<p class="reveal" style="font-size: clamp(0.85rem, 1.3vw, 1.1rem);">
|
|
반복은 AI에게, 창의는 개발자에게
|
|
</p>
|
|
<div class="reveal" style="display: flex; gap: 0.5rem; margin-top: 1rem; flex-wrap: wrap; justify-content: center;">
|
|
<span class="badge">Claude Code</span>
|
|
<span class="badge">Figma MCP</span>
|
|
<span class="badge">CLAUDE.md</span>
|
|
<span class="badge">Skills</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<!-- 네비게이션 UI -->
|
|
<div class="progress-bar"><div class="progress-fill" id="progressFill"></div></div>
|
|
<div class="slide-counter" id="slideCounter">01 / 11</div>
|
|
<div class="nav-buttons">
|
|
<button class="nav-btn" id="btnPrev" aria-label="이전 슬라이드">‹</button>
|
|
<button class="nav-btn" id="btnNext" aria-label="다음 슬라이드">›</button>
|
|
</div>
|
|
|
|
<script>
|
|
class Presentation {
|
|
constructor() {
|
|
this.viewport = document.getElementById('viewport');
|
|
this.slides = Array.from(this.viewport.querySelectorAll('.slide'));
|
|
this.total = this.slides.length;
|
|
this.current = 0;
|
|
this.isAnimating = false;
|
|
this.DURATION = 450;
|
|
|
|
this.slides.forEach(s => this._applyCodeHighlights(s));
|
|
this._restoreFromHash();
|
|
this._bindKeyboard();
|
|
this._bindTouch();
|
|
this._bindButtons();
|
|
this._bindHash();
|
|
this._updateUI();
|
|
}
|
|
|
|
goNext() {
|
|
if (this.isAnimating || this.current >= this.total - 1) return;
|
|
this._goSlide(this.current + 1, 'next');
|
|
}
|
|
|
|
goPrev() {
|
|
if (this.isAnimating || this.current <= 0) return;
|
|
this._goSlide(this.current - 1, 'prev');
|
|
}
|
|
|
|
goTo(index) {
|
|
if (this.isAnimating || index === this.current) return;
|
|
if (index < 0 || index >= this.total) return;
|
|
this._goSlide(index, index > this.current ? 'next' : 'prev');
|
|
}
|
|
|
|
_goSlide(index, direction) {
|
|
const isNext = direction === 'next';
|
|
const from = this.slides[this.current];
|
|
const to = this.slides[index];
|
|
|
|
this.isAnimating = true;
|
|
this.viewport.classList.add('animating');
|
|
|
|
from.style.willChange = 'transform, opacity';
|
|
to.style.willChange = 'transform, opacity';
|
|
|
|
to.style.transition = 'none';
|
|
to.classList.remove('stand-left');
|
|
to.style.transform = isNext ? 'translateX(100%)' : 'translateX(-100%)';
|
|
to.style.opacity = '0';
|
|
void to.offsetWidth;
|
|
|
|
to.style.transition = '';
|
|
to.style.transform = '';
|
|
to.style.opacity = '';
|
|
from.classList.remove('active');
|
|
to.classList.add('active');
|
|
|
|
from.style.transform = isNext ? 'translateX(-100%)' : 'translateX(100%)';
|
|
from.style.opacity = '0';
|
|
|
|
from.setAttribute('aria-hidden', 'true');
|
|
to.setAttribute('aria-hidden', 'false');
|
|
|
|
this.current = index;
|
|
this._syncHash();
|
|
this._updateUI();
|
|
|
|
let done = false;
|
|
const cleanup = () => {
|
|
if (done) return;
|
|
done = true;
|
|
clearTimeout(fallback);
|
|
to.removeEventListener('transitionend', onEnd);
|
|
|
|
from.style.transform = '';
|
|
from.style.opacity = '';
|
|
from.style.transition = 'none';
|
|
from.classList.add('stand-left');
|
|
void from.offsetWidth;
|
|
from.style.transition = '';
|
|
|
|
from.style.willChange = 'auto';
|
|
to.style.willChange = 'auto';
|
|
|
|
this.isAnimating = false;
|
|
this.viewport.classList.remove('animating');
|
|
};
|
|
|
|
const onEnd = (e) => {
|
|
if (e.propertyName !== 'transform' || e.target !== to) return;
|
|
cleanup();
|
|
};
|
|
to.addEventListener('transitionend', onEnd);
|
|
const fallback = setTimeout(cleanup, this.DURATION + 150);
|
|
}
|
|
|
|
_applyCodeHighlights(slide) {
|
|
slide.querySelectorAll('.code-block[data-highlight-lines]').forEach(pre => {
|
|
const ranges = this._parseRanges(pre.dataset.highlightLines);
|
|
const lines = pre.querySelectorAll('.line');
|
|
lines.forEach((line, i) => {
|
|
if (ranges.has(i + 1)) line.classList.add('line--highlight');
|
|
});
|
|
if (ranges.size > 0) pre.classList.add('dim-others');
|
|
});
|
|
}
|
|
|
|
_parseRanges(str) {
|
|
const result = new Set();
|
|
str.split(',').forEach(part => {
|
|
part = part.trim();
|
|
if (part.includes('-')) {
|
|
const [a, b] = part.split('-').map(Number);
|
|
for (let i = a; i <= b; i++) result.add(i);
|
|
} else {
|
|
result.add(Number(part));
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
_updateUI() {
|
|
const progress = this.total <= 1 ? 100 : (this.current / (this.total - 1)) * 100;
|
|
document.getElementById('progressFill').style.width = `${progress}%`;
|
|
const num = String(this.current + 1).padStart(2, '0');
|
|
const tot = String(this.total).padStart(2, '0');
|
|
document.getElementById('slideCounter').textContent = `${num} / ${tot}`;
|
|
}
|
|
|
|
_syncHash() {
|
|
const hash = `#slide-${this.current + 1}`;
|
|
if (location.hash !== hash) history.pushState(null, '', hash);
|
|
}
|
|
|
|
_restoreFromHash() {
|
|
const match = location.hash.match(/^#slide-(\d+)$/);
|
|
if (match) {
|
|
const idx = parseInt(match[1], 10) - 1;
|
|
if (idx > 0 && idx < this.total) {
|
|
this.slides[0].classList.remove('active');
|
|
this.slides[0].setAttribute('aria-hidden', 'true');
|
|
this.slides[0].classList.add('stand-left');
|
|
this.slides[idx].classList.add('active');
|
|
this.slides[idx].setAttribute('aria-hidden', 'false');
|
|
this.current = idx;
|
|
}
|
|
}
|
|
}
|
|
|
|
_bindKeyboard() {
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.repeat) return;
|
|
const tag = document.activeElement.tagName;
|
|
if (tag === 'INPUT' || tag === 'TEXTAREA') return;
|
|
|
|
switch (e.key) {
|
|
case 'ArrowRight': case 'ArrowDown': case ' ': case 'PageDown':
|
|
e.preventDefault(); this.goNext(); break;
|
|
case 'ArrowLeft': case 'ArrowUp': case 'PageUp':
|
|
e.preventDefault(); this.goPrev(); break;
|
|
case 'Home':
|
|
e.preventDefault(); this.goTo(0); break;
|
|
case 'End':
|
|
e.preventDefault(); this.goTo(this.total - 1); break;
|
|
case 'f': case 'F':
|
|
this._toggleFullscreen(); break;
|
|
}
|
|
});
|
|
}
|
|
|
|
_bindTouch() {
|
|
let startX = 0, startY = 0, startTime = 0, swiping = false;
|
|
const el = this.viewport;
|
|
|
|
el.addEventListener('touchstart', (e) => {
|
|
const t = e.changedTouches[0];
|
|
startX = t.clientX; startY = t.clientY;
|
|
startTime = Date.now(); swiping = false;
|
|
}, { passive: true });
|
|
|
|
el.addEventListener('touchmove', (e) => {
|
|
const t = e.changedTouches[0];
|
|
const dx = Math.abs(t.clientX - startX);
|
|
const dy = Math.abs(t.clientY - startY);
|
|
if (dx > dy && dx > 10) { swiping = true; e.preventDefault(); }
|
|
}, { passive: false });
|
|
|
|
el.addEventListener('touchend', (e) => {
|
|
const t = e.changedTouches[0];
|
|
const dx = t.clientX - startX;
|
|
const dy = Math.abs(t.clientY - startY);
|
|
const elapsed = Date.now() - startTime;
|
|
if (swiping && Math.abs(dx) >= 50 && dy <= 75 && elapsed <= 400) {
|
|
dx < 0 ? this.goNext() : this.goPrev();
|
|
}
|
|
swiping = false;
|
|
}, { passive: true });
|
|
|
|
el.style.touchAction = 'pan-y';
|
|
el.style.userSelect = 'none';
|
|
}
|
|
|
|
_bindButtons() {
|
|
document.getElementById('btnPrev').addEventListener('click', () => this.goPrev());
|
|
document.getElementById('btnNext').addEventListener('click', () => this.goNext());
|
|
}
|
|
|
|
_bindHash() {
|
|
window.addEventListener('hashchange', () => {
|
|
const match = location.hash.match(/^#slide-(\d+)$/);
|
|
if (match) {
|
|
const idx = parseInt(match[1], 10) - 1;
|
|
if (idx !== this.current) this.goTo(idx);
|
|
}
|
|
});
|
|
}
|
|
|
|
_toggleFullscreen() {
|
|
if (!document.fullscreenElement) {
|
|
document.documentElement.requestFullscreen().catch(() => {});
|
|
} else {
|
|
document.exitFullscreen();
|
|
}
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => new Presentation());
|
|
</script>
|
|
</body>
|
|
</html>
|