Files
web-page/src/pages/effect-lab/EffectLab.jsx
gahusb 25715a2198 feat: Agent Office — AI 에이전트 가상 오피스 (#2)
## Summary
- Canvas 2D 픽셀아트 오피스 렌더링 (SpriteSheet + TileMap + AgentSprite)
- WebSocket 실시간 에이전트 상태 동기화 (useAgentManager)
- ChatPanel (명령/승인) + TaskHistory (작업 이력) UI
- 다크 테마 + glassmorphism 패널

## Changes (7 commits)
- API helpers + route + Lab entry
- Canvas engine: SpriteSheet, TileMap, AgentSprite, OfficeRenderer
- React hooks: useAgentManager, useOfficeCanvas
- Components: ChatPanel, TaskHistory
- Main page + CSS
- Code review fixes: claude agent 참조 제거, rejected 배지 추가

Reviewed-on: #2
2026-04-11 13:35:35 +09:00

93 lines
3.1 KiB
JavaScript

import React from 'react';
import { Link } from 'react-router-dom';
import './EffectLab.css';
const LAB_ITEMS = [
{
id: 'sword-stream',
path: '/lab/sword-stream',
title: 'Sword Stream',
category: '3D · 인터랙티브',
desc: '1,500개의 검 파티클이 마우스를 따라 흐릅니다. 클릭하면 나선형 궤도로 전환됩니다.',
tags: ['Three.js', '파티클', '인터랙티브'],
accent: '#44aadd',
icon: '⚔️',
status: 'live',
},
{
id: 'day-calc',
path: '/lab/day-calc',
title: '일수 계산기',
category: '유틸리티 · 날짜',
desc: '두 날짜 사이의 기간을 일, 주, 월, 연 단위로 계산하고 기념일 날짜를 확인합니다.',
tags: ['날짜', '계산기', '기념일'],
accent: '#fbbf24',
icon: '📅',
status: 'live',
},
{
id: 'agent-office',
path: '/agent-office',
title: 'Agent Office',
category: 'AI · 자동화',
desc: 'AI 에이전트들이 사무실에서 자동으로 작업하는 가상 오피스',
tags: ['Canvas 2D', 'WebSocket', 'AI Agent', 'Telegram'],
accent: '#8b5cf6',
icon: '🏢',
status: 'wip',
},
];
const STATUS_LABEL = {
live: { label: 'LIVE', color: '#34d399' },
wip: { label: 'WIP', color: '#fbbf24' },
planned: { label: 'PLANNED', color: '#94a3b8' },
};
const LabCard = ({ item }) => {
const st = STATUS_LABEL[item.status] || STATUS_LABEL.planned;
return (
<Link to={item.path} className="lab-card" style={{ '--card-accent': item.accent }}>
<div className="lab-card__top">
<span className="lab-card__icon">{item.icon}</span>
<span className="lab-card__status" style={{ color: st.color, borderColor: `${st.color}40` }}>
{st.label}
</span>
</div>
<div className="lab-card__body">
<p className="lab-card__category">{item.category}</p>
<h2 className="lab-card__title">{item.title}</h2>
<p className="lab-card__desc">{item.desc}</p>
</div>
<div className="lab-card__footer">
<div className="lab-card__tags">
{item.tags.map((t) => (
<span key={t} className="lab-card__tag">{t}</span>
))}
</div>
<span className="lab-card__arrow"></span>
</div>
</Link>
);
};
const EffectLab = () => (
<div className="lab">
<header className="lab__header">
<p className="lab__kicker">STREAM</p>
<h1>Lab</h1>
<p className="lab__desc">
실험적인 UI, 인터랙티브 효과, 유틸리티 도구를 테스트하고 탐구하는 공간입니다.
</p>
</header>
<div className="lab__grid">
{LAB_ITEMS.map((item) => (
<LabCard key={item.id} item={item} />
))}
</div>
</div>
);
export default EffectLab;