feat: P1 풀빌드 — React 프론트 7화면 + 백엔드 상담루프·평가·음성·RAG
web (Vite+React19+TS, Cloudflare Pages 배포): - 디자인토큰(세이지틸/테라코타 SSOT), 앱셸, 공통 UI 프리미티브 - 7화면: 로그인/학습자홈/상담세션/회기리뷰/교수자/관리자/설정 - ClientAvatar: SVG 반구상 흉상 4상태 + RMS 립싱크 + 6파라미터 정서 - 회기리뷰는 외부 레퍼런스 디자인을 Vignette 토큰으로 리스킨 api (FastAPI): - 게이트웨이 /v1/generate·/v1/stream 어댑터(상주풀/EngineSession 보존) - services: 페르소나 L0~L6 빌더 / 결정론 상태머신 / 가드레일 / 턴 오케스트레이터 / 회기간 메모리 / 평가AI / 음성 / RAG - store: DB off 폴백(in-memory), sessions 실구현 검증: - web: node22 tsc+vite build 통과(node23 segfault 회피), Pages 배포 200 - api: app.main import 통과 - 핫픽스: Topbar initials undefined-safe (undefined.trim 크래시) - E2E: 서연(P1) 상담 1턴 — 좋은/나쁜 상담에 차등 반응 실증
This commit is contained in:
parent
859ab26314
commit
24b1b7a6e1
84 changed files with 19645 additions and 107 deletions
118
apps/web/src/styles/global.css
Normal file
118
apps/web/src/styles/global.css
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* =====================================================================
|
||||
Vignette — global reset + base + 공통 유틸
|
||||
토큰만 참조(tokens.css). 순흑/순백·border-left 강조선 금지.
|
||||
===================================================================== */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html,
|
||||
body,
|
||||
#root {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-app);
|
||||
color: var(--text-strong);
|
||||
font-size: var(--fs-body);
|
||||
line-height: 1.6;
|
||||
letter-spacing: -0.01em;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
/* 링크 기본: accent 텍스트, 밑줄은 hover에서만 (양산형 회피) */
|
||||
a {
|
||||
color: var(--accent-deep);
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
input,
|
||||
textarea,
|
||||
select {
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/* 이미지/svg 블록 정렬 */
|
||||
img,
|
||||
svg {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* 포커스 — 키보드 사용자만 ring (마우스 클릭엔 없음) */
|
||||
:focus {
|
||||
outline: none;
|
||||
}
|
||||
:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--focus-ring);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
/* ── 스크롤바 (차분하게, 무채) ── */
|
||||
* {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--neutral-200) transparent;
|
||||
}
|
||||
*::-webkit-scrollbar {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
*::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
*::-webkit-scrollbar-thumb {
|
||||
background: var(--neutral-200);
|
||||
border-radius: 100px;
|
||||
border: 2px solid var(--bg-app);
|
||||
}
|
||||
*::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--neutral-400);
|
||||
}
|
||||
|
||||
/* ── 공통 유틸 ── */
|
||||
.tabular {
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.visually-hidden {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
height: 1px;
|
||||
padding: 0;
|
||||
margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* 접근성: 모션 줄이기 */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
}
|
||||
196
apps/web/src/styles/tokens.css
Normal file
196
apps/web/src/styles/tokens.css
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/* =====================================================================
|
||||
Vignette — 디자인 토큰 (단일 진실원본)
|
||||
출처: docs/dev_dashboard.html :root + docs/DESIGN_CONCEPT.md §3
|
||||
primitive(원시값) → semantic(의미 별칭) 2계층. 컴포넌트는 semantic만 참조.
|
||||
다크모드/역할별 accent는 이 파일 하단 1곳에서만 스왑.
|
||||
철칙: 순흑/순백 금지(paper/ink 토큰만), border-left 강조선 금지,
|
||||
강조는 weight+accent텍스트+tint배경+kicker로만.
|
||||
===================================================================== */
|
||||
|
||||
:root {
|
||||
/* ── Primitive: Neutral (따뜻한 종이 + 쿨 슬레이트) §3.1 ── */
|
||||
--paper: #fbfaf8; /* 본문 배경: 따뜻한 종이색 (순백 아님) */
|
||||
--paper-2: #f4f2ee; /* 카드/패널 미세 단차 (테두리 대신 톤차) */
|
||||
--surface: #ffffff; /* 카드 표면 */
|
||||
--neutral-100: #eceff1; /* 표면 hover / subtle fill */
|
||||
--neutral-150: #e5e1da; /* 카드 보더 baseline = hairline */
|
||||
--neutral-200: #d5dbde; /* divider 진함 */
|
||||
--neutral-400: #98a4a9; /* placeholder / disabled */
|
||||
|
||||
--ink: #1c2a2a; /* 본문 텍스트: 잉크 네이비-그린 (순흑 아님) */
|
||||
--ink-2: #5a6663; /* 보조 텍스트: 슬레이트 */
|
||||
--ink-3: #93a09c; /* 캡션/메타 */
|
||||
--hair: #e5e1da; /* 1px 헤어라인 (구역 분리는 이걸로) */
|
||||
|
||||
/* ── Sage-Teal (주 악센트 = 상담/신뢰), 화면당 1곳 배급 §3.2 ── */
|
||||
--accent: #3e7a6e; /* ★ 메인 악센트. 흰 배경 대비 5.0:1 AA */
|
||||
--accent-deep: #316257; /* hover / 진한 강조 텍스트 */
|
||||
--accent-tint: #eef4f2; /* 악센트 배경 틴트 (콜아웃·선택·active) */
|
||||
--accent-bright: #5f968b; /* dot·진행바 fill */
|
||||
|
||||
/* ── Terracotta (보조 악센트 = 학습/온기), 저채도, 화면당 1곳 ── */
|
||||
--clay: #b0735c; /* ★ 보조 악센트 (학습자 온기, 내담자 측 신호) */
|
||||
--clay-deep: #94604c;
|
||||
--clay-tint: #f6f0ec;
|
||||
|
||||
/* ── 의미색 (Semantic feedback) — solid + tint 페어 §3.3 ── */
|
||||
/* 긍정 = 녹색 (상담 진전·라포·가드레일 통과·잘함) */
|
||||
--pos-text: #2c6e49;
|
||||
--pos-solid: #3b8c5e;
|
||||
--pos-tint: #e8f3ec;
|
||||
/* 주의 = 앰버 (라포 약화·과도한 침묵·페이싱) */
|
||||
--warn-text: #8a5a00;
|
||||
--warn-solid: #c28a2c;
|
||||
--warn-tint: #faf1e0;
|
||||
/* 위험 = 적색 (가드레일 위반·세션 강제중단·임상적 위급) — 오직 진짜 위급만 */
|
||||
--crit-text: #a8352c;
|
||||
--crit-solid: #c4453a;
|
||||
--crit-tint: #faeae8;
|
||||
/* 정보 = 슬레이트블루 (평가 AI 피드백·시스템 안내, 중립) */
|
||||
--info-text: #2e5a7a;
|
||||
--info-solid: #3c7299;
|
||||
--info-tint: #eaf1f6;
|
||||
--neutral-sig: #8895a2; /* 데이터 없음·미평가 */
|
||||
|
||||
/* ── 시맨틱 별칭 (라이트 모드) §3.4 ── */
|
||||
--bg-app: var(--paper); /* 앱 캔버스 */
|
||||
--bg-surface: var(--surface); /* 카드/패널 */
|
||||
--bg-surface-2: var(--paper-2); /* 중첩 표면/입력 */
|
||||
--bg-tint: var(--accent-tint); /* 선택·강조 배경 */
|
||||
--bg-stage: #1e2724; /* 내담자 아바타 영역 (깊은 차콜그린) */
|
||||
|
||||
--text-strong: var(--ink); /* 제목 */
|
||||
--text-body: var(--ink-2); /* 본문 보조 */
|
||||
--text-muted: var(--ink-3); /* 캡션 */
|
||||
--text-on-accent: #fbfaf8; /* accent 버튼 위 텍스트 */
|
||||
|
||||
--border-subtle: var(--neutral-150);
|
||||
--border-strong: var(--neutral-200);
|
||||
--border-focus: var(--accent);
|
||||
--focus-ring: rgba(62, 122, 110, 0.45);
|
||||
|
||||
/* ── 타이포그래피 §3.6 ── */
|
||||
--font-sans: "Pretendard Variable", Pretendard, -apple-system,
|
||||
"Apple SD Gothic Neo", "Noto Sans KR", system-ui, sans-serif;
|
||||
--font-serif: "Noto Serif KR", "Source Serif 4", serif; /* 랜딩 헤드만 */
|
||||
--font-num: "Pretendard Variable", "JetBrains Mono", ui-monospace, monospace;
|
||||
|
||||
/* 타입 스케일 (기준 16px / 1.6, 위계는 크기보다 weight) */
|
||||
--fs-display: 2.5rem; /* 40 / 표지·랜딩 대제목 */
|
||||
--fs-h1: 1.875rem; /* 30 / 페이지 제목 */
|
||||
--fs-h2: 1.5rem; /* 24 / 섹션 제목 */
|
||||
--fs-h3: 1.25rem; /* 20 / 카드·패널 제목 */
|
||||
--fs-lead: 1.125rem; /* 18 / 리드 문장 */
|
||||
--fs-body: 1rem; /* 16 / 본문 기본 */
|
||||
--fs-sm: 0.875rem; /* 14 / 보조·폼 라벨 */
|
||||
--fs-xs: 0.8125rem; /* 13 / 캡션·메타·배지 */
|
||||
--fs-kicker: 0.75rem; /* 12 / 상단 kicker 소제목 */
|
||||
|
||||
/* ── 스페이싱 (8pt 기반) §3.7 ── */
|
||||
--sp-1: 4px;
|
||||
--sp-2: 8px;
|
||||
--sp-3: 12px;
|
||||
--sp-4: 16px;
|
||||
--sp-5: 24px;
|
||||
--sp-6: 32px;
|
||||
--sp-7: 48px;
|
||||
--sp-8: 64px;
|
||||
--sp-9: 96px;
|
||||
|
||||
/* ── 그리드·형태 §3.8 ── */
|
||||
--maxw: 1200px; /* 대시보드 콘텐츠 최대폭 */
|
||||
--maxw-read: 680px; /* 읽기 영역 */
|
||||
--nav-w: 240px; /* 좌측 네비 펼침 */
|
||||
--nav-w-collapsed: 72px; /* 좌측 네비 아이콘 only */
|
||||
--topbar-h: 56px;
|
||||
--radius: 8px; /* 카드/버튼 (거의 직각) */
|
||||
--radius-sm: 6px; /* 입력·칩 */
|
||||
--radius-lg: 12px; /* 패널 */
|
||||
--gutter: 24px;
|
||||
|
||||
--shadow-sm: 0 1px 2px rgba(28, 42, 42, 0.05);
|
||||
--shadow-md: 0 4px 14px rgba(28, 42, 42, 0.07); /* 모달·팝오버만 */
|
||||
|
||||
/* ── 모션 §3.9 ── */
|
||||
--ease-out: cubic-bezier(0.22, 0.61, 0.36, 1);
|
||||
--ease-in-out: cubic-bezier(0.45, 0.05, 0.55, 0.95);
|
||||
--dur-fast: 120ms; /* hover·포커스 */
|
||||
--dur-base: 200ms; /* 버튼·토글·드롭다운 */
|
||||
--dur-slow: 320ms; /* 패널 전환·모달 */
|
||||
--dur-breathe: 3200ms; /* 음성 호흡 펄스 */
|
||||
}
|
||||
|
||||
/* ── 다크 모드 (gloom 회피 — 충분한 밝은 면 + 절제된 악센트) §3.5 ── */
|
||||
[data-theme="dark"] {
|
||||
--bg-app: #131a1e;
|
||||
--bg-surface: #1a2429;
|
||||
--bg-surface-2: #222e34;
|
||||
--bg-tint: #1e2e2a;
|
||||
--bg-stage: #0e1614;
|
||||
--text-strong: #eaf0f1;
|
||||
--text-body: #c4cfd3;
|
||||
--text-muted: #88969c;
|
||||
--text-on-accent: #0f1a17;
|
||||
--border-subtle: #2a363c;
|
||||
--border-strong: #38454c;
|
||||
--border-focus: #6fb3a4;
|
||||
--accent: #6fb3a4;
|
||||
--accent-deep: #84c2b4;
|
||||
--accent-tint: #1e2e2a;
|
||||
--accent-bright: #6fb3a4;
|
||||
--clay: #cc8f77;
|
||||
--focus-ring: rgba(111, 179, 164, 0.45);
|
||||
|
||||
--pos-text: #7fd0a0;
|
||||
--pos-solid: #5fb682;
|
||||
--pos-tint: #16271d;
|
||||
--warn-text: #e2b566;
|
||||
--warn-solid: #d4a24a;
|
||||
--warn-tint: #2a2415;
|
||||
--crit-text: #e89086;
|
||||
--crit-solid: #d86459;
|
||||
--crit-tint: #2a1715;
|
||||
--info-text: #79b0d4;
|
||||
--info-solid: #5c97be;
|
||||
--info-tint: #15222c;
|
||||
}
|
||||
|
||||
/* ── 역할별 accent 오버라이드 §6.2 ──
|
||||
같은 골격, accent 한 토큰만 교체. 모두 저채도.
|
||||
API role(learner/teacher/admin) → data-role 매핑:
|
||||
teacher 화면은 data-role="instructor"(인디고-블루), admin은 슬레이트. */
|
||||
[data-role="learner"] {
|
||||
--accent: #3e7a6e;
|
||||
--accent-deep: #316257;
|
||||
--accent-bright: #5f968b;
|
||||
--accent-tint: #eef4f2;
|
||||
--focus-ring: rgba(62, 122, 110, 0.45);
|
||||
}
|
||||
[data-role="instructor"] {
|
||||
--accent: #3a5ba0; /* 인디고-블루: 감독 */
|
||||
--accent-deep: #2f4a85;
|
||||
--accent-bright: #5478c4;
|
||||
--accent-tint: #eaeff7;
|
||||
--focus-ring: rgba(58, 91, 160, 0.4);
|
||||
}
|
||||
[data-role="admin"] {
|
||||
--accent: #5b5f6b; /* 슬레이트: 운영 */
|
||||
--accent-deep: #494d57;
|
||||
--accent-bright: #7d818e;
|
||||
--accent-tint: #eeeff1;
|
||||
--focus-ring: rgba(91, 95, 107, 0.4);
|
||||
}
|
||||
|
||||
/* 다크 + 역할 동시 적용 시 accent 명도 보정 (가독) */
|
||||
[data-theme="dark"][data-role="instructor"] {
|
||||
--accent: #7d9fde;
|
||||
--accent-deep: #94b1e6;
|
||||
--accent-bright: #7d9fde;
|
||||
--accent-tint: #19222f;
|
||||
}
|
||||
[data-theme="dark"][data-role="admin"] {
|
||||
--accent: #9aa0ad;
|
||||
--accent-deep: #b0b5c0;
|
||||
--accent-bright: #9aa0ad;
|
||||
--accent-tint: #1d2026;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue