Files
game-fe-agent/html/fe-ai-workflow-ppt.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">검수 &amp;<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">&lt;!-- Figma MCP로 디자인 파싱 후 자동 생성 --&gt;</span></span>
<span class="line"><span class="op">&lt;</span><span class="tag">script</span> <span class="attr">setup</span> <span class="attr">lang</span>=<span class="str">"ts"</span><span class="op">&gt;</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>&lt;{ <span class="var">title</span>: <span class="kw">string</span> }&gt;();</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">&lt;/</span><span class="tag">script</span><span class="op">&gt;</span></span>
<span class="line"><span class="op">&lt;</span><span class="tag">template</span><span class="op">&gt;</span></span>
<span class="line"> <span class="op">&lt;</span><span class="tag">div</span> <span class="attr">class</span>=<span class="str">"component-root"</span><span class="op">&gt;</span>...</span>
<span class="line"> <span class="op">&lt;/</span><span class="tag">div</span><span class="op">&gt;</span></span>
<span class="line"><span class="op">&lt;/</span><span class="tag">template</span><span class="op">&gt;</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">번역 키 &amp; 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">코드 검수 &amp; 커밋</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="이전 슬라이드">&#8249;</button>
<button class="nav-btn" id="btnNext" aria-label="다음 슬라이드">&#8250;</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>