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
332
apps/web/src/components/ui/ui.css
Normal file
332
apps/web/src/components/ui/ui.css
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/* =====================================================================
|
||||
UI 프리미티브 스타일 — 토큰 변수만 참조(tokens.css).
|
||||
철칙: border-left 강조선 0 · 카드 상단 강조선 0 · 알약 radius 금지(원형은 dot/오브/아바타만).
|
||||
강조 = weight + accent 텍스트 + accent-tint 배경 + kicker(+dot).
|
||||
===================================================================== */
|
||||
|
||||
/* ── Kicker (상단 소제목, 양산형 좌측바 대체 위계 신호) §3.6 ── */
|
||||
.vg-kicker {
|
||||
font-family: var(--font-num);
|
||||
font-size: var(--fs-kicker);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--accent);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-transform: none;
|
||||
}
|
||||
.vg-kicker__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--accent-bright);
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* ── SectionHead ── */
|
||||
.vg-sechead {
|
||||
margin-bottom: var(--sp-5);
|
||||
}
|
||||
.vg-sechead__kicker {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.vg-sechead__title {
|
||||
font-size: var(--fs-h2);
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.015em;
|
||||
color: var(--text-strong);
|
||||
line-height: 1.35;
|
||||
}
|
||||
.vg-sechead__desc {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* ── Button (위계 3단 + danger). radius 8px, 알약 금지 §7.1 ── */
|
||||
.vg-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
font: 600 var(--fs-sm) / 1 var(--font-sans);
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid transparent;
|
||||
transition:
|
||||
background var(--dur-base) var(--ease-out),
|
||||
border-color var(--dur-base) var(--ease-out),
|
||||
transform var(--dur-fast) var(--ease-out);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
.vg-btn:active {
|
||||
transform: translateY(0.5px);
|
||||
}
|
||||
.vg-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
transform: none;
|
||||
}
|
||||
.vg-btn--block {
|
||||
width: 100%;
|
||||
}
|
||||
.vg-btn--sm {
|
||||
padding: 7px 13px;
|
||||
font-size: var(--fs-xs);
|
||||
}
|
||||
.vg-btn--lg {
|
||||
padding: 13px 22px;
|
||||
font-size: var(--fs-body);
|
||||
}
|
||||
|
||||
.vg-btn--primary {
|
||||
background: var(--accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
.vg-btn--primary:hover:not(:disabled) {
|
||||
background: var(--accent-deep);
|
||||
}
|
||||
.vg-btn--secondary {
|
||||
background: var(--bg-surface);
|
||||
color: var(--accent);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.vg-btn--secondary:hover:not(:disabled) {
|
||||
background: var(--accent-tint);
|
||||
}
|
||||
.vg-btn--ghost {
|
||||
background: transparent;
|
||||
color: var(--text-body);
|
||||
}
|
||||
.vg-btn--ghost:hover:not(:disabled) {
|
||||
background: var(--bg-surface-2);
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.vg-btn--danger {
|
||||
background: var(--crit-solid);
|
||||
color: #fff;
|
||||
}
|
||||
.vg-btn--danger:hover:not(:disabled) {
|
||||
background: var(--crit-text);
|
||||
}
|
||||
|
||||
/* ── Card / Panel §7.3 (헤어라인 + 미세 그림자, 상단 강조선 금지) ── */
|
||||
.vg-card {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius);
|
||||
padding: var(--sp-5);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.vg-card--flat {
|
||||
box-shadow: none;
|
||||
}
|
||||
.vg-card--tint {
|
||||
background: var(--bg-tint);
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
.vg-panel {
|
||||
background: var(--bg-surface);
|
||||
border: 1px solid var(--border-subtle);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--sp-6);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
.vg-panel--flat {
|
||||
box-shadow: none;
|
||||
}
|
||||
.vg-panel--tint {
|
||||
background: var(--bg-tint);
|
||||
border-color: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
/* ── Badge (배경 틴트 + 텍스트, 좌측바 없음) ── */
|
||||
.vg-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-family: var(--font-num);
|
||||
font-size: var(--fs-xs);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.01em;
|
||||
padding: 2px 9px;
|
||||
border-radius: 100px; /* 칩/배지만 100px 관용 (DESIGN_CONCEPT 데모 일치) */
|
||||
line-height: 1.5;
|
||||
}
|
||||
.vg-badge--neutral {
|
||||
color: var(--ink-2);
|
||||
background: var(--paper-2);
|
||||
}
|
||||
.vg-badge--accent {
|
||||
color: var(--accent-deep);
|
||||
background: var(--accent-tint);
|
||||
}
|
||||
.vg-badge--pos {
|
||||
color: var(--pos-text);
|
||||
background: var(--pos-tint);
|
||||
}
|
||||
.vg-badge--warn {
|
||||
color: var(--warn-text);
|
||||
background: var(--warn-tint);
|
||||
}
|
||||
.vg-badge--crit {
|
||||
color: var(--crit-text);
|
||||
background: var(--crit-tint);
|
||||
}
|
||||
.vg-badge--info {
|
||||
color: var(--info-text);
|
||||
background: var(--info-tint);
|
||||
}
|
||||
|
||||
/* ── Dot (상태 점). 원형 예외 허용 대상 ── */
|
||||
.vg-dot {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
flex: none;
|
||||
background: var(--neutral-200);
|
||||
}
|
||||
.vg-dot--accent {
|
||||
background: var(--accent);
|
||||
}
|
||||
.vg-dot--pos {
|
||||
background: var(--pos-solid);
|
||||
}
|
||||
.vg-dot--warn {
|
||||
background: var(--warn-solid);
|
||||
}
|
||||
.vg-dot--crit {
|
||||
background: var(--crit-solid);
|
||||
}
|
||||
.vg-dot--info {
|
||||
background: var(--info-solid);
|
||||
}
|
||||
.vg-dot--muted {
|
||||
background: var(--ink-3);
|
||||
}
|
||||
|
||||
/* ── StatLine (요약 스탯, tabular) ── */
|
||||
.vg-statline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-5);
|
||||
}
|
||||
.vg-statline__item {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 7px;
|
||||
}
|
||||
.vg-statline__num {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--text-strong);
|
||||
font-variant-numeric: tabular-nums;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.vg-statline__lab {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.vg-statline__sep {
|
||||
width: 1px;
|
||||
height: 18px;
|
||||
background: var(--hair);
|
||||
}
|
||||
|
||||
/* ── ProgressBar (가로 게이지, 라운드 없는 사각 트랙) §5.5/§6.4 ── */
|
||||
.vg-progress {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--paper-2);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.vg-progress--slim {
|
||||
height: 4px;
|
||||
}
|
||||
.vg-progress__fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
background: var(--accent);
|
||||
transition: width var(--dur-slow) var(--ease-out);
|
||||
}
|
||||
.vg-progress__fill--muted {
|
||||
background: var(--neutral-200);
|
||||
}
|
||||
.vg-progress__fill--warn {
|
||||
background: var(--warn-solid);
|
||||
}
|
||||
.vg-progress__fill--clay {
|
||||
background: var(--clay);
|
||||
}
|
||||
|
||||
/* ── Field / Input §7.2 (radius 6px, 포커스 보더색+ring, 좌측바 X) ── */
|
||||
.vg-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
.vg-field__label {
|
||||
font-size: var(--fs-sm);
|
||||
font-weight: 600;
|
||||
color: var(--text-strong);
|
||||
}
|
||||
.vg-field__hint {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.vg-field__error {
|
||||
font-size: var(--fs-xs);
|
||||
color: var(--crit-text);
|
||||
}
|
||||
.vg-input {
|
||||
width: 100%;
|
||||
background: var(--bg-surface-2);
|
||||
color: var(--text-strong);
|
||||
border: 1px solid var(--border-strong);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 10px 12px;
|
||||
font: var(--fs-body) / 1.5 var(--font-sans);
|
||||
transition:
|
||||
border-color var(--dur-fast) var(--ease-out),
|
||||
box-shadow var(--dur-fast) var(--ease-out);
|
||||
}
|
||||
.vg-input::placeholder {
|
||||
color: var(--neutral-400);
|
||||
}
|
||||
.vg-input:focus {
|
||||
border-color: var(--border-focus);
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 3px var(--focus-ring);
|
||||
}
|
||||
.vg-input[aria-invalid="true"] {
|
||||
border-color: var(--crit-solid);
|
||||
}
|
||||
|
||||
/* ── 빈/준비중 상태 (스텁 페이지 공통) ── */
|
||||
.vg-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: var(--sp-3);
|
||||
max-width: var(--maxw-read);
|
||||
}
|
||||
.vg-empty__title {
|
||||
font-size: var(--fs-h1);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--text-strong);
|
||||
line-height: 1.3;
|
||||
}
|
||||
.vg-empty__desc {
|
||||
font-size: var(--fs-lead);
|
||||
color: var(--text-body);
|
||||
line-height: 1.6;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue