- apps/web(React) · apps/api(FastAPI) · infra(Docker) · docs(설계 SoT) - 마스터플랜·적대검증·메모리설계·디자인컨셉 docs 이관 - 미성년 사례데이터는 .gitignore로 제외(개인정보 보호)
63 KiB
| date | type | project | tags | status | |||||
|---|---|---|---|---|---|---|---|---|---|
| 2026-06-25 | design-concept | 한신대학교 AI 심리상담 시뮬레이션 훈련 플랫폼 |
|
통합본 v1 |
한신대 AI 심리상담 시뮬레이션 플랫폼 — 통합 디자인 컨셉 문서
이 문서 하나로 사이트를 만들 수 있다. 디자인 철학 → 디자인 시스템 토큰(실측값) → 가상 내담자 아바타(상세) → 상담 세션 화면 → 3역할 대시보드 → 컴포넌트·모션 → React 구현 노트 → 안티패턴 체크리스트까지, 추상적 형용사 없이 실제 hex·px·컴포넌트·인터랙션으로 적었다.
5개 영역 설계([design-language] / [design-system] / [avatar] / [session-ux] / [role-dashboards])를 단일 기준 문서로 종합한 것이다. 토큰 충돌 지점은 본 문서가 **단일 진실원본(SSOT)**으로 일원화했다.
재사용 자산 연계: 윤찬 기존 디자인 시스템(
Resources/50_Tech_Dev/디자인/slide-base.css)과 동일한 토큰 철학(무채 골격 + accent 교체). slide-base 표의 "한신대/의료" 빈 행을 본 문서의--accent로 채운다.
0. 한 문장 컨셉과 SSOT 결정
0.1 컨셉
"불안한 사람을 다루는 사람을 위한, 차분한 임상 도구."
상담 수련생(학습자)이 가상 내담자 AI와 음성으로 대면하고, 뒤에서 평가 AI가 조용히 관찰한다. 이 화면은 두 종류의 신뢰를 동시에 줘야 한다.
- 임상적 신뢰감 — 학습자가 "이건 진지한 임상 훈련 장비다"라고 느낀다.
- 교육적 따뜻함 — 연습 중 실수해도 안전하다고 느낀다.
자극적·게임적이면 안 되고, 차갑고 행정적이어도 안 된다. 정답은 그 사이의 "조용한 따뜻함(quiet warmth)" — 스펙트럼 위 65% 임상 : 35% 따뜻함 지점이다.
화면은 "상담실"이지 "대시보드"가 아니다. 평가 정보는 상담실 벽에 걸린 은은한 시계처럼 곁에 있되, 내담자의 얼굴에서 시선을 빼앗지 않는다.
0.2 토큰 SSOT 결정 (5개 설계 영역의 색 통일)
5개 영역이 각각 미세하게 다른 그린(#5B7A6B / #3E7A6E / #4F6B5D / #0E7C7B / #0F766E)을 제안했다. 본 문서는 아래로 확정 일원화한다.
| 용도 | 확정값 | 근거 |
|---|---|---|
| 메인 accent (전역 기본) | --accent: #3E7A6E (세이지-틸) |
저채도·차분, 흰 배경 대비 약 5.0:1 (WCAG AA 통과). 흔한 메디컬 블루/형광 틸 회피. slide-base의 #0F766E보다 한 톤 더 낮춤. |
| 역할별 보조 accent | 학습자 #3E7A6E / 교수자 #3A5BA0 / 관리자 #5B5F6B |
같은 골격, accent 한 토큰만 교체. 모두 저채도. |
| 보조 온기 accent | --clay: #B0735C (테라코타) |
양산형 메디컬 블루를 깨는 "사람이 만든 교육 도구" 온기. 저채도로만, 화면당 1곳. |
이유: 세이지-틸은 임상적 차분함(자연·회복·안정)과 따뜻함(흙·식물)을 동시에 품으면서, 한국 상담/심리 분야에서 과용되지 않아 차별화된다. 의료 블루는 흔하고 차갑다.
1. 핵심 디자인 원칙 (신성불가침 5개)
원칙 1 — One Screen, One Intent (한 화면 한 의도)
상담 시뮬레이션은 본질적으로 집중을 요구한다. 학습자가 가상 내담자의 말에 귀 기울이는 순간 화면에 버튼 12개·카드 9개가 깔려 있으면 안 된다. 모든 화면은 그 순간의 단 하나의 주된 행동(말하기 / 듣기 / 피드백 읽기 / 세션 시작)을 중심으로 설계한다. 부차 정보는 위계 아래로.
- 적용: 세션 진행 화면 = 중앙에 내담자 발화 + 음성 비주얼 하나. 평가 피드백은 화면 가장자리 차분한 사이드 패널로, 절대 모달로 튀지 않는다.
원칙 2 — 위계는 색 블록이 아니라 타이포 weight + 여백으로
Stripe·Linear의 공통점: 테두리·색 박스로 구역을 나누지 않고 여백과 글자 굵기로 나눈다. 강조는 ①font-weight(400→600) ②accent 텍스트/언더라인 ③상단 kicker(소제목) ④accent dot 으로만. border-left 강조선·좌측 사이드바 강조줄·색 박스 카드 격자 절대 금지.
- 적용: 평가 피드백 "공감 반영: 우수"를 초록 배지로 칠하지 않는다. kicker("공감 반영") + 본문 한 줄 + 미세한 accent dot 한 점으로 위계를 만든다.
원칙 3 — 임상적 차분함 = 채도를 죽인 중성 베이스 + 절제된 단일 악센트
Calm/Headspace에서 배울 건 파스텔 그라데이션이 아니라 고채도·고대비를 의도적으로 피하는 절제다. 베이스는 따뜻한 회백(off-white)·차분한 슬레이트. 악센트는 세이지-틸 단 하나를 화면당 1군데만 배급(Linear의 "accent rationing"). 빨강·주황 경고색은 진짜 오류/위험에만.
- 근거: 불안한 내담자를 다루는 훈련 도구이므로 자극적 색은 학습자 정서·임상 톤 양쪽에 해롭다. 녹색은 우울·불안·스트레스 점수를 가장 많이 낮춘 색이며 "go/긍정" 신호로도 조용히 작동한다.
원칙 4 — 따뜻함은 "둥근 카드"가 아니라 "휴머니스트 타이포 + 따뜻한 중성톤 + 넉넉한 여백"
따뜻함을 둥근 모서리·말랑한 일러스트로 내면 임상 신뢰가 깨진다(애들 앱처럼 보임). 대신 헤드라인에 휴머니스트 세리프 한 톤 옵션(therapy 웹의 정설: serif=신뢰·인간미), 본문은 깔끔한 산세리프. 순백(#FFF) 대신 따뜻한 종이색, 순흑 대신 잉크 네이비-그린. 이 미세한 온기가 "행정 시스템"과 "사람을 위한 도구"를 가른다.
- 주의: 한글 본문은 Pretendard 단일 패밀리를 기본으로 한다(국문 상담 도메인 가독·중립). 세리프 헤드라인은 랜딩·표지 같은 희소 지점에서만 선택 적용(본명조). UI 전반은 산세리프 weight 위계로 통일.
원칙 5 — 평가 AI는 판사가 아니라 조용한 멘토 (Non-judgmental surfacing)
실시간 피드백이 빨간 X·점수 폭격·"틀렸습니다"로 뜨면 학습자는 위축된다. 피드백은 관찰("이 지점에서 내담자가 방어적으로 반응했어요") → 제안("열린 질문으로 바꿔볼까요?") 순서의 서술형·저채도·비모달로. 점수는 세션이 끝난 뒤 회고 화면에서만, 절대값이 아니라 성장 추세로.
2. 무드 좌표 (임상 ↔ 따뜻함 균형)
양 극단(차가운 EHR / 말랑한 명상앱) 사이, 65% 임상 : 35% 따뜻함.
| 축 | 임상 신뢰(차가움) ← | → 우리 좌표 ← | → 따뜻함(부드러움) |
|---|---|---|---|
| 베이스색 | 순백·차가운 그레이 | 따뜻한 off-white #FBFAF8 + 차분한 슬레이트 |
크림·파스텔 |
| 악센트 | 임상 블루 | 세이지-틸 #3E7A6E |
코랄·더스티 로즈 |
| 헤드라인 | 기하 산세리프 | 산세리프 weight 위계 (희소 지점만 본명조 옵션) | 손글씨·라운드 |
| 모서리 | 직각 | 6~8px (거의 직각, 미세 라운드) | 16px+ 둥근 |
| 그림자 | 없음(헤어라인) | 1px 헤어라인 + 아주 약한 그림자 | 부드러운 큰 blur |
| 모션 | 즉각·없음 | 200~320ms ease-out, 절제 | 느린 호흡 애니메이션 |
단 하나의 예외 — 음성 듣기 중 마이크/오브의 느린 호흡 펄스. 이건 "지금 듣고 있다"는 정서적 안심 신호라 의도적으로 명상앱 호흡 모션을 차용한다.
3. 디자인 시스템 토큰 (그대로 :root에 붙여 쓰는 실측값)
CSS 변수는 primitive(원시값) → semantic(의미 별칭) 2계층. 컴포넌트는 semantic만 참조 → 다크모드 1곳에서 스왑.
3.1 중성 베이스 (Neutral) — 골격
순백/순흑 금지. 따뜻한 종이색 + 잉크 네이비-그린.
:root {
/* ── Primitive: Neutral (따뜻한 종이 + 쿨 슬레이트) ── */
--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 헤어라인 (구역 분리는 이걸로) */
}
3.2 악센트 — 1주 + 1보조 (저채도)
:root {
/* ── Sage-Teal (주 악센트 = 상담/신뢰), 화면당 1곳 배급 ── */
--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;
}
--accent #3E7A6E는 흰 배경 명도대비 약 5.0:1 → 본문 링크/버튼 텍스트 WCAG AA 통과. 2색을 동시에 큰 면적으로 쓰지 않는다 — sage가 전역 기본, clay는 학습자 모드/온기 포인트에서만 점·언더라인·소형 fill로.
3.3 의미색 (Semantic feedback) — solid + tint 페어
각 의미색은 아이콘/텍스트용 solid + 배경용 tint. tint 위 text는 모두 AA(4.5:1↑).
:root {
/* 긍정 = 녹색 (상담 진전·라포 형성·가드레일 통과·잘함) */
--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; /* 데이터 없음·미평가 */
}
핵심 원칙: 평가 AI의 실시간 피드백 기본 톤은 info(슬레이트블루) 또는 pos(녹색) 으로 — 학습자가 비난받는 느낌을 받지 않게. 빨강은 코칭 피드백에 절대 쓰지 않고, 가드레일/위급에만.
3.4 시맨틱 별칭 (라이트 모드)
컴포넌트는 primitive를 직접 부르지 않고 이 별칭만 참조.
:root {
--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: color-mix(in srgb, var(--accent) 45%, transparent);
}
3.5 다크 모드 (gloom 회피 — 충분한 밝은 면 + 절제된 악센트)
배경은 한색 슬레이트(순흑 아님). 악센트는 명도를 올려 가독 확보.
[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;
--clay: #CC8F77;
--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;
}
3.6 타이포그래피
| 역할 | 폰트 | 이유 |
|---|---|---|
| 본문/UI 기본 | Pretendard Variable | 한글 UI 사실상 표준. 9 weight 가변, 숫자 정렬 우수. 임상 데이터·평가 점수 표기에 깔끔. |
| 숫자·타임코드 | Pretendard + tabular-nums |
세션 타이머·점수는 정렬 필요 → font-variant-numeric: tabular-nums. |
| (옵션) 랜딩 헤드라인 | Noto Serif KR (본명조) | 신뢰·인간미. 희소 지점만(표지·히어로). UI 전반은 산세리프. |
:root {
--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;
}
body { font-family: var(--font-sans); -webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility; }
.tabular { font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
CDN: https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/variable/pretendardvariable.min.css
타입 스케일 (기준 본문 16px / 1.6, 한글 자간 -0.01~-0.02em. 위계는 크기보다 weight로):
| 토큰 | px / rem | line-height | weight | letter-spacing | 용도 |
|---|---|---|---|---|---|
--fs-display |
40 / 2.5rem | 1.2 | 700 | -0.02em | 표지·랜딩 대제목 (희소) |
--fs-h1 |
30 / 1.875rem | 1.3 | 700 | -0.02em | 페이지 제목 |
--fs-h2 |
24 / 1.5rem | 1.35 | 600 | -0.015em | 섹션 제목 |
--fs-h3 |
20 / 1.25rem | 1.4 | 600 | -0.01em | 카드/패널 제목 |
--fs-lead |
18 / 1.125rem | 1.6 | 400 | -0.01em | 리드 문장 |
--fs-body |
16 / 1rem | 1.6 | 400 | -0.01em | 본문 기본 |
--fs-sm |
14 / 0.875rem | 1.55 | 400 | 0 | 보조·폼 라벨 |
--fs-xs |
13 / 0.8125rem | 1.5 | 500 | 0.01em | 캡션·메타·배지 |
--fs-kicker |
12 / 0.75rem | 1.4 | 600 | 0.08em | 상단 kicker 소제목 |
강조 방식(철칙): 색 블록·border-left 금지. ① weight 400→600 ② accent 텍스트 컬러 ③ 은은한 --accent-tint 배경 틴트 ④ 상단 kicker(작은 도트 동반). kicker가 양산형 좌측바를 대체하는 위계 신호다.
3.7 스페이싱 (8pt 기반, 넉넉하게)
:root {
--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;
}
원칙: 컴포넌트 내부 패딩은 8의 배수, 섹션 간 간격은 48~96px 넉넉히. 불안 완화의 핵심은 여백. 카드 내부 패딩 기본 --sp-5(24px), 패널 --sp-6(32px), 읽기 영역 max-width 640~680px.
3.8 그리드·형태
:root {
--maxw: 1200px; /* 대시보드 콘텐츠 최대폭 */
--maxw-read: 680px; /* 읽기 영역 */
--nav-w: 240px; /* 좌측 네비 펼침 */
--nav-w-collapsed: 72px; /* 좌측 네비 아이콘 only */
--radius: 8px; /* 카드/버튼/입력 (거의 직각) */
--radius-sm: 6px; /* 입력·칩 */
--radius-lg: 12px; /* 패널 */
--gutter: 24px;
}
border-radius: 카드 8px, 버튼 8px, 입력 6px (거의 직각, 미세 라운드만). 원형은 음성 오브·아바타·dot만 예외.- 깊이는 1px 헤어라인(
--hair) + 화면용 아주 약한 그림자. glow·gradient-text·큰 blur 금지.
:root {
--shadow-sm: 0 1px 2px rgba(28,42,42,.05);
--shadow-md: 0 4px 14px rgba(28,42,42,.07); /* 모달·팝오버만 */
}
3.9 모션
:root {
--ease-out: cubic-bezier(.22,.61,.36,1);
--ease-in-out: cubic-bezier(.45,.05,.55,.95);
--dur-fast: 120ms; /* hover·포커스 */
--dur-base: 200ms; /* 버튼·토글·드롭다운 */
--dur-slow: 320ms; /* 패널 전환·모달 */
--dur-breathe: 3200ms; /* 음성 호흡 펄스 */
}
@media (prefers-reduced-motion: reduce) {
*,*::before,*::after { animation-duration:.01ms!important; transition-duration:.01ms!important; }
}
원칙: 차분·짧게·자연 이징. 불안 도구이므로 튀는 스프링/바운스 금지. 화면 전환은 페이드+미세 슬라이드(translateY 8px 이하). 실시간 피드백 칩은 자리 미리 확보 후 fade(밀어내기/점프 금지 — 레이아웃 시프트가 불안 유발). prefers-reduced-motion 필수 대응.
4. 가상 내담자 아바타 (이 플랫폼의 심장)
학습자의 시선이 30~50분간 머무는 단 하나의 오브젝트. 산만하면 라포 연습을 방해하고, 과하면 실제 환자로 오인되며, 빈약하면 몰입이 깨진다.
4.1 추천: "반(半)구상 페르소나 카드 (Semi-figurative Persona Card)"
사실적 얼굴도, 추상 오브도 아닌 그 사이. 사실성 35~45%의 양식화 반구상 흉상 + 호흡하는 광배(aura).
표현 방식 비교 (왜 이걸 골랐나)
| 방식 | 몰입 | 제작비용(20주) | 윤리/오인 위험 | 불쾌한 골짜기 | 결론 |
|---|---|---|---|---|---|
| A. 추상 오브 / 음성 비주얼라이저 | 낮음 | 매우 낮음(1주) | 없음 | 없음 | 폴백으로만(미선택/로딩/에러) |
| B. 2D 양식화 일러스트(반구상) | 높음 | 중간(3~6주) | 낮음 | 회피 가능 | ★ 1차 채택 |
| C. 실사 / AI생성 얼굴 | 매우 높음 | 높음 | 높음 | 직격(최악) | 탈락 |
| D. Live2D / 3D | 매우 높음 | 매우 높음(6~10주+) | 중간 | 리깅 품질 의존 | 2차/장기 후보 |
- B를 고른 이유: 사실성을 의도적으로 35~45%에 고정 → 불쾌한 골짜기 회피. 벡터라 페르소나별 파라미터(피부톤·헤어·눈 형태·연령감)를 데이터로 찍어내 시스템화 가능(페르소나 무한 확장). 눈·눈썹·입꼬리·어깨선 4채널만으로 저항/라포/정서를 은은히 표현. 윤찬 미니멀 톤과 정합.
- C 탈락: 80% 사실성에서 미세 립싱크 오차가 더 크게 불쾌. 미성년·취약 내담자를 실사로 그리면 "이 아이 실존?" 윤리 문제 + 데이터 편향.
- A를 폴백으로 흡수: ElevenLabs Orb 같은 성숙한 React 자산을 페르소나 미선택/로딩/에러 화면에 영구 잔존.
카드 구조
┌─────────────────────────────────────┐
│ [상단 kicker] 교육용 가상 내담자 │ ← 상시 라벨(오인 차단)
│ │
│ ╭───────────────╮ │
│ │ 양식화 흉상 │ │ ← 어깨~머리, 평면 벡터
│ │ (얼굴 35% 사실) │ │ 배경에 호흡하는 광배
│ ╰───────────────╯ │
│ │
│ 서연 · 17세 · 고2 │ ← 페르소나 메타 1줄
│ ────────── │
│ 지금: 머뭇거리며 듣는 중 │ ← 상태 텍스트 1줄
└─────────────────────────────────────┘
핵심: 얼굴은 양식화하되 "사람"으로 읽히고, 절대 "실존 인물"로는 안 읽히게. 코·주름·모공 없음. 눈·눈썹·입꼬리·어깨 각도가 정서를 나르는 4개 채널.
4.2 음성 발화 중 반응 — 상태 모델 (4-state)
음성 UI 표준 idle/listening/thinking/speaking을 내담자 맥락으로 재정의.
| 상태 | 트리거 | 입 | 눈/눈썹 | 광배(aura) | 어깨 호흡 |
|---|---|---|---|---|---|
| listening (학습자 발화 중) | 마이크 입력 감지 | 닫힘, 미세 입꼬리 | 학습자 쪽 응시, 깜빡임 4~6s | 차분 펄스 0.06 opacity, 5.5s | 진폭 1.5px, 4s |
| thinking (응답 생성 대기) | STT 종료~TTS 시작 | 살짝 다묾 | 시선 살짝 아래/옆, 깜빡임 느려짐 | 안쪽 수축, 채도 -8% | 정지에 가깝게 |
| speaking (내담자 발화) | TTS 오디오 재생 | 립싱크 진폭 | 정면 복귀, 눈썹 정서 반영 | 발화 진폭에 약동기(0.04~0.12) | 발화 리듬따라 미세 |
| idle (시작 전/일시정지) | 무입력 | 닫힘 | 천천히 깜빡, 가끔 시선 이동 | 최소 펄스 | 느린 호흡 3.5s |
4.3 립싱크 — "정밀 음소 매칭 안 함"이 정답
35% 사실성 얼굴에 정밀 비짐(viseme) 매핑은 오히려 불쾌. 오디오 진폭(RMS) → 입 열림 높이 1채널 매핑.
// Web Audio AnalyserNode → 매 프레임 RMS → 입 SVG path 세로 스케일
const rms = getRMS(analyser); // 0~1
const mouthOpen = smooth(rms, 0.18); // 지수평활(튐 방지), τ≈180ms
// <path class="mouth" style={{ transform:`scaleY(${0.35 + mouthOpen*0.65})` }}/>
- 입 모양: 닫힘=얇은 호, 열림=타원.
scaleY만 변형. 이빨·혀 절대 안 그림. - 데드존: rms < 0.04는 닫힘 고정 → 무음 구간 입 떨림 제거.
- 평활 필수: raw RMS 직결하면 입이 경련함. τ=150~200ms.
4.4 모션 진폭 가이드 (산만함 차단의 핵심)
| 모션 | 진폭 | 주기/속도 | 비고 |
|---|---|---|---|
| 어깨 호흡(idle) | translateY 1.5~2.5px | 3.5~5.5s ease-in-out 무한 | "있는 줄 모르게" |
| 깜빡임 | scaleY 1→0.08→1 | 120ms, 간격 4~7s 랜덤 | 생체감의 80%가 여기서 |
| 광배 펄스 | opacity ±0.04~0.08 | 5~6s | speaking 때만 오디오 동기 |
| 시선 이동(saccade) | ±3px | 0.3s, 간격 6~10s | thinking에서 빈도↑ |
| 립싱크 | scaleY 0.35~1.0 | 오디오 실시간 | 데드존+평활 |
원칙: 동시에 움직이는 채널 ≤ 2개. speaking이면 입+광배만. listening이면 깜빡임+호흡만.
4.5 정서 시각화 — 6파라미터 상태머신
valence/arousal 축을 아바타 파라미터로 인코딩. 과장 절대 금지(만화적 눈물·홍조·땀방울 전부 금지). 상담 도구의 정서는 "감지되되 단언되지 않는" 수준.
| 내담자 정서 | 눈/눈썹 | 입꼬리 | 자세(어깨선) | 광배 색조 | 호흡 |
|---|---|---|---|---|---|
| 중립/라포 | 눈썹 수평, 눈맞춤 | 미세 상향 | 정면, 약간 열림 | 세이지 #3E7A6E 4% |
안정 4s |
| 우울(저각성·부정) | 눈꺼풀 1~2px 하강, 시선 아래 | 수평~미세 하향 | 어깨 안으로·아래로 | 청회색 #5B6B73 5% |
느리고 얕게 5.5s |
| 불안(고각성·부정) | 깜빡임↑(2~4s), saccade↑ | 다묾, 긴장 | 약간 들썩 | 차가운 청 #3B6E8F 5%, 빠른 펄스 |
빠르고 얕게 3s |
| 저항/방어 | 시선 회피(옆/아래 고정) | 굳게 닫힘 | 반쯤 돌아선 각도(turn 8°) | 중립 회색, 무펄스 | 정지에 가깝게 |
상태머신이 (eyelidDrop, gazeAvert, shoulderTurn, breathRate, auraHue, blinkInterval) 6개 숫자만 바꾸면 정서 전환. 라포가 쌓이면(세션 진행) 저항 페르소나가 shoulderTurn 8°→0°, gazeAvert→눈맞춤으로 서서히 완화 → 학습자가 "내가 라포를 만들었다"를 시각적으로 체감. 이게 이 도구의 교육적 핵심. 정서 전환은 0.8~1.2s ease로 천천히, 동시 변하는 파라미터 ≤3개.
4.6 페르소나 파라미터 시스템 (새 페르소나 = 새 숫자 묶음)
// persona/seoyeon.json — 청소년 페르소나 예시
{
"id": "seoyeon",
"label": "서연 · 17세 · 고2",
"appearance": {
"ageBand": "teen", // teen | youngAdult | adult | senior
"skinTone": "#E8C9A8",
"hair": { "style": "long-straight", "color": "#2B2723" },
"faceRatio": { "eyeSize": 1.15, "jawWidth": 0.92 }, // teen=큰 눈·좁은 턱
"outfit": "hoodie", // 실루엣만, 디테일 X
"realism": 0.40 // 0.35~0.45 고정(불쾌한 골짜기 회피)
},
"voice": { "provider": "openai", "preset": "soft-young-fem", "rate": 0.96 },
"baseAffect": "anxious", // 시작 정서
"resistance": 0.6, // 0~1, 라포로 감소
"auraHueShift": -6
}
연령대 외형 규칙
| ageBand | 눈 크기 | 턱/얼굴 | 실루엣 | 광배 채도 | 음성 톤 |
|---|---|---|---|---|---|
| teen | 1.1~1.2× | 좁은 턱·둥근 | 후디·체크 | 약간 높음(생기) | 부드럽고 높은 |
| youngAdult | 1.0× | 표준 | 캐주얼 | 중간 | 표준 |
| adult | 0.95× | 각진 턱 | 셔츠·니트 | 낮음(차분) | 낮고 안정 |
| senior | 0.92× | 부드러운 윤곽 | 단정 | 낮고 따뜻 | 느린 |
- 미성년 페르소나는 realism을 0.35로 더 낮추고
교육용 가상 내담자(미성년 시뮬레이션)라벨 강화 → 실존 미성년 오인 원천 차단. - 외형은 인종·특정 외모 stereotype 회피를 위해 중립 톤 팔레트 3~4종만 제공(편향 최소화).
4.7 아바타 구현 기술 — SVG 1차, Rive 2차
| 기술 | 적합 | 근거 |
|---|---|---|
| SVG + CSS/Web Animations + Web Audio | ★ 1차 | 페르소나=벡터 파라미터, 립싱크=RMS 1채널, 의존성 0, React 친화, 미니멀 정합 |
| Rive (상태머신) | ★ 2차 고도화 | 상태머신이 .riv에 내장, React는 input만 wiring. Lottie 대비 파일 10~15× 작음 |
| Lottie | 보조 | 정형 애니(로딩·전환)엔 OK, 실시간 오디오 반응엔 부적합 |
| Live2D/3D | 장기 | 표현력 최고지만 20주·시스템화 불리 |
1차안 컴포넌트 구조 (SVG)
<ClientAvatar persona state affect analyser/>
├─ <AuraLayer hue affect state/> // 배경 광배: radial-gradient, opacity 펄스
├─ <BodySilhouette persona/> // 어깨선, shoulderTurn 각도
├─ <FaceGroup persona affect>
│ ├─ <Eyes blink gaze eyelidDrop/>
│ ├─ <Brows affect/> // 정서별 각도 2~3px
│ └─ <Mouth analyser state/> // RMS→scaleY 립싱크
└─ <PersonaLabel/> // "교육용 가상 내담자" 상시
// 단일 rAF 루프, 의존성 0
function useAvatarMotion(state, affect, analyser) {
const [frame, setFrame] = useState(initFrame(affect));
useEffect(() => {
let raf, t0 = performance.now();
const loop = (t) => {
const dt = (t - t0) / 1000;
const breath = Math.sin(dt * (2*Math.PI/affect.breathPeriod)) * affect.breathAmp;
const mouth = state==='speaking' ? smoothRMS(analyser) : 0;
const blink = blinkSchedule(t, affect.blinkInterval);
setFrame({ breath, mouth, blink, gaze: gazeFor(state) });
raf = requestAnimationFrame(loop);
};
raf = requestAnimationFrame(loop);
return () => cancelAnimationFrame(raf);
}, [state, affect, analyser]);
return frame;
}
- 퍼포먼스: SVG 변형은
transform(GPU 합성)만, layout 트리거 속성(width/top) 금지. 단일 rAF, 60fps 여유. - RN 이전 대비: SVG는
react-native-svg, Web Audio는react-native-audio-api로 대체 → 로직 재사용. Rive도 RN 런타임 있음. - 접근성:
prefers-reduced-motion켜지면 호흡·광배 정지, 상태는 텍스트로만.
5. 상담 세션 화면 (메인 화면)
핵심 긴장: 몰입 vs 평가 가시성. 실시간 피드백이 라포 형성을 방해하면 안 되고, 평가가 사후로만 묶이면 학습 효과가 떨어진다. 해법 = 2단(two-tier) 피드백 모델 — 실시간은 "신호(signal)" 수준, 정밀 평가는 "회기말 리뷰"로.
5.1 3-column 골격 (데스크탑 ≥1280px)
┌──────────────────────────────────────────────────────────────────────────┐
│ 상단 바 (56px) 좌:세션 제목/시나리오 중앙:회기 단계 우:경과시간·종료 │
├──────────────────┬──────────────────────────────────┬──────────────────────┤
│ LEFT (320px) │ CENTER (flexible) │ RIGHT (300px) │
│ ───────────── │ ───────────────── │ ───────────── │
│ 회기 단계 트랙커 │ 내담자 아바타 / 음성 STAGE │ 피드백 신호 패널 │
│ (라포→탐색→ │ (어두운 배경, 중앙 집중) │ (은은한, 평시 접힘) │
│ 개입→정리) │ │ │
│ 내담자 컨텍스트 │ [ 아바타 + 음성 오브 ] │ ── 내담자 상태 ── │
│ 카드 │ │ ── 라이브 신호 ── │
│ │ 실시간 자막 (아래→위 흐름) │ (최대 1개, 페이드) │
│ │ │ ── 셀프 노트 ── │
├──────────────────┴──────────────────────────────────┴──────────────────────┤
│ 하단 컨트롤 바 (80px) 마이크 · 일시정지 · [몰입│은은│코칭] · (밀어서)종료 │
└──────────────────────────────────────────────────────────────────────────┘
비율 의도: 시각적 무게 = 주의 배분. 내담자 stage 60%, 평가 패널 15%(평시 대부분 접힘). 화면은 대시보드가 아니라 상담실이다.
반응형:
- 태블릿(768~1279px): RIGHT 패널이 오버레이 drawer(평시 숨김, 우상단 신호 도트만). LEFT 280px.
- 모바일/RN(<768px): 단일 컬럼. stage 상단 고정(40vh), 자막 스크롤, 단계는 상단 가는 진행선, 평가는 하단 시트(bottom sheet). 컨트롤 바 하단 고정.
5.2 중앙 STAGE — 음성 오브 (Voice Orb, 4상태)
자극적 막대 파형 대신 차분한 호흡(breathing) 애니메이션. 아바타(원형 220px) 둘레 오브 링.
| 상태 | 시각 | 색 | 모션 |
|---|---|---|---|
| 내담자 말하는 중 | 따뜻한 링, 음량 따라 미세 진동 | --clay glow, 두께 4px |
음량 연동 amplitude(0.5~3px), 0% 형광 |
| 학습자 말하는 중 | 세이지 링, 호흡 펄스 | --accent glow |
1.4s ease breathing scale 1.0→1.04 |
| 대기/침묵(thinking) | 가는 점선 매우 느리게 회전 | --ink-3 |
8s/회전, 거의 안 보일 정도 |
| 일시정지 | 반투명 오버레이 + "일시정지" | --bg-stage 60% |
정지 |
.orb {
position: absolute; inset: -14px; border-radius: 50%;
box-shadow: 0 0 0 3px var(--ring-color),
0 0 28px 4px color-mix(in srgb, var(--ring-color) 35%, transparent);
transition: box-shadow 320ms ease;
}
.orb[data-state="learner"] { --ring-color: var(--accent); animation: breathe 1.4s ease-in-out infinite; }
.orb[data-state="client"] { --ring-color: var(--clay); /* JS가 RMS 음량을 --amp로 주입 */ }
@keyframes breathe { 0%,100%{ transform: scale(1) } 50%{ transform: scale(1.035) } }
음량 연동은 JS onVolumeChange/RMS → CSS 변수 --amp로 box-shadow blur 미세 모듈레이션. 막대 파형(equalizer) 금지 — 너무 "녹음 앱"스럽고 자극적. 호흡하는 빛이 임상적이다.
5.3 실시간 자막 (Transcript) — 대본 스타일
음성이 주, 자막은 보조. stage 하단, 아래→위 흐름. 채팅 버블 양산형 회피, 대본(script) 스타일:
─────────────────────────────────────
내담자 14:32
요즘은 그냥… 아침에 일어나는 게
제일 힘들어요.
나(학습자) 14:33
아침에 일어나는 것조차 버겁게
느껴지시는군요. 그 마음을 조금
더 들려주실 수 있을까요?
─────────────────────────────────────
- 화자 라벨 12px 600 메타톤. 내담자 발화는
--clay-tint옅은 배경 틴트 블록, 학습자 발화는--accent-tint틴트. 버블 외곽선·꼬리·border-left 없음. 틴트 블록만으로 화자 구분. - 진행 중 발화(partial)는
--ink-3흐릿하게 타이핑되다 확정되면--ink또렷하게. - 자동 스크롤, 위로 스크롤하면 자동스크롤 해제 + "최신으로" 작은 칩.
5.4 회기 단계 표시 (라포 → 탐색 → 개입 → 정리)
LEFT 패널 세로 트랙커 + 상단 바 가로 미니. 현재 단계만 또렷, 나머지는 은은히 (단계 압박 방지).
회기 진행 ← kicker
● 라포 형성 09:12 ← 완료: 채워진 sage 도트 + 소요시간
│
◉ 탐색 진행중 ← 현재: 링+채움, accent, 굵게
│
○ 개입 ← 예정: 빈 도트, ink-3
│
○ 정리
- 연결선 1px
--hair, 완료 구간만--accent-bright. - 자동 추적 기본, 수동 보정 가능. 백그라운드 평가 AI가 대화로 현재 단계 추정해
◉이동. 학습자가 직접 "다음 단계로" 가능(자각 촉진). 추정과 인식 어긋나면 회기말 리뷰에서 짚음. - 자극 배제: 단계 전환 시 화면 전환·효과음·축하 애니 없음. 도트가 조용히 채워질 뿐(400ms ease).
5.5 실시간 피드백 — "거슬리지 않게" (이 설계의 심장)
3단계 강도 모델을 학습자가 토글로 고른다 (하단 컨트롤 바 segmented control, 기본=은은한 신호):
| 모드 | 실시간 표시 | 대상 |
|---|---|---|
| 몰입(Immersive) | 라이브 신호 전혀 없음. 백그라운드 기록만. 모든 피드백 회기말. | 숙련·실전 압박 연습 |
| 은은한 신호(Ambient) ★기본 | 우측 패널에 앰비언트 도트 1개만, 색으로 톤만. 텍스트 없음(호버 시) | 일반 연습 |
| 코칭(Coached) | 발화 직후 짧은 텍스트 힌트 페이드인 | 초보·특정 기법 집중 |
앰비언트 신호 (기본 모드 핵심)
우측 패널에 단 하나의 부드러운 도트가 직전 발화 평가 톤만 색으로 전한다. 점수·숫자·순위 없음.
라이브 신호 ← kicker
● 공감적 반영 ← pos 도트 + 한 단어. 6초 후 페이드.
최근 흐름 · · ● · ◦ · ● ← 최근 6개 발화 톤 미니 시퀀스(클릭 가능)
- 도트 색:
--pos-solid(잘함) /--warn-solid(살펴볼 점) /--ink-3(중립). 빨강·점수 없음. - 등장: 발화 종료+평가 완료 시 opacity 0→1, 350ms. 6초 후 자동 페이드 — 누적되면 대시보드가 됨.
- 거슬림 최소화 3원칙: ①한 번에 1개 ②텍스트 기본 숨김(색만) ③시간 제한 페이드. 진동/소리/번쩍임 전무. 주변시로 "느낄" 수준.
코칭 모드 텍스트 힌트
코칭 힌트 ← kicker
방금 질문은 '예/아니오'로 닫혀 있었어요.
내담자가 더 말하도록 열어보면 어떨까요?
예: "그때 어떤 마음이 드셨는지 들려주세요."
[ 알겠어요 ] ← 가볍게 dismiss
--accent-tint배경 틴트 블록(border 없음), 13~14px. 예시 문장--ink-2이탤릭. 한 번에 1개, 새 힌트 오면 이전 것 교체(stack 안 함).- 위험 신호만 예외적 즉시 표시(모드 무관): 자해·위기 언급을 학습자가 놓치면 모드 무관하게 우측에 차분한(빨강 아닌
--warn-solid) 알림 — "안전 점검: 방금 위기 신호가 있었어요." 교육적으로 놓치면 안 되는 지점이라 항상 노출.
내담자 상태 미터 (우측 상단) — "반응 읽기 연습"의 거울
내담자 상태
감정 톤 불안 ▓▓▓▓░░ ← 가로 게이지, clay 계열, 라벨 텍스트
마음 열림 ▓▓░░░░ ← 라포 진척 대리지표
게이지는 매우 천천히(2s ease) — 실시간 흔들림이 산만하지 않게. 막대 라운드 없는 사각, 4px 높이.
5.6 하단 컨트롤 바
- 마이크 버튼(주 컨트롤): 56px 원형. 학습자 말하는 중 =
--accent채움 + 호흡. 음소거 = 아웃라인. push-to-talk 또는 토글. 상태 텍스트 한 줄("듣고 있어요" / "마이크 꺼짐"). - 일시정지: 타이머·평가·녹음 모두 정지. 정지 중 stage 차분한 오버레이.
- 종료: 오발동 방지 — 밀어서 종료(slide-to-confirm) 또는 0.8s 길게. 종료가 유일하게 진한 경고색(
--crit-solid) 허용 영역. - 접근성: 키보드(Space=마이크, P=일시정지),
--focus-ring2px. 음성 상태는 색만이 아니라 텍스트 병기(색각 이상 대응).
5.7 회기 종료 후 리뷰 화면 (진짜 학습이 일어나는 곳)
실시간에선 일부러 참았던 정밀 평가를 여기서 푼다. 구조: 타임라인 + 기법 라벨 + 슈퍼바이저 코멘트 + 개선점.
┌──────────────────────────────────────────────────────────────────────────┐
│ 리뷰 · 시나리오:우울 호소 20대/회기1 오디오 다시듣기▷ PDF 내보내기 │
├──────────────────────────────────────────────────────────────────────────┤
│ 한 줄 요약 (큰 문장 하나, 24px/600 — 에디토리얼 미니멀의 핵심) │
│ "라포는 안정적으로 형성됐지만, 탐색 단계에서 닫힌 질문이 반복돼 │
│ 내담자의 자기개방이 일찍 멈췄습니다." │
├───────────────────────────────────────┬──────────────────────────────────┤
│ LEFT (62%) 타임라인 + 자막 │ RIGHT (38%) 분석 사이드 │
│ 회기 타임라인(가로 막대) │ ── 기법 사용 분포 ── │
│ 라포▏▏탐색▏▏▏▏▏개입▏▏정리▏ │ 반영적 경청 ▓▓▓▓▓▓ │
│ ▲ 마커(코멘트·기법·하이라이트) │ 개방형 질문 ▓▓ │
│ ─── 스크롤 자막(마커 연동) ─── │ 닫힌 질문 ⚠ ▓▓▓▓▓ (살펴볼 점) │
│ 나 14:33 ◆[감정 반영] … │ ── 잘한 순간 ── │
│ └ 슈퍼바이저: "좋은 반영…" │ ── 다음 회기 개선점(최대 3개) ── │
└───────────────────────────────────────┴──────────────────────────────────┘
- 타임라인(가로): 4단계 너비 비례 구간으로 색 농담 구분. 마커 = 기법 라벨·살펴볼 순간·잘한 순간·슈퍼바이저 코멘트(이모지 아님, SVG 글리프/도트). 클릭하면 좌측 자막 점프 + 오디오 그 시점으로.
- 자막 + 인라인 분석: 학습자 발화마다 기법 라벨 칩(
[반영적 경청][개방형 질문][닫힌 질문]),--accent-tint/--warn-tint배경, border 없음 11px. 라벨 아래 들여쓰기로 슈퍼바이저 코멘트(AI 자동 + 교수자 직접). AI코멘트--ink-2, 사람 코멘트--accent+ "교수자" 태그. - 분석 사이드: 기법 사용 분포(가로 게이지, 점수 아님 빈도+적절성, 과다·과소만
--warn라벨). 잘한 것 먼저, 개선점 최대 3개(실행 가능 구체적, 각 항목 타임라인 마커 연결). 다음 회기 개선점은 "다음에 시도할 한 문장" 형태. - 톤: 점수·등급·합불 없음. 상담 역량은 성장 곡선이지 시험이 아니다. 추세는 별도 "학습자 성장" 화면에서만, 단일 리뷰엔 절대점수 안 보임. PDF 내보내기(슈퍼바이저 제출), 오디오 다시듣기, 클립 공유.
6. 3역할 대시보드 (학습자 / 교수자 / 관리자)
6.1 4가지 설계 결정 (근거)
| 결정 | 근거 |
|---|---|
| 레이더차트 금지, small-multiples 라인 채택 | 레이더는 항목 순서로 모양 왜곡 + 범주선 "증가/감소" 착시. Tufte small-multiples + 공통 축이 정확. 윤찬 "카드덤프 금지"와도 정합. |
| 신뢰·차분 = 단일 accent + 넉넉한 여백 + 위계 | 의료 UI: blue=신뢰, green=정상범위, red는 진짜 임상 긴급에만 예약. whitespace·typography로 불안 완화. |
| 교수자 화면은 "인지"가 아니라 "개입"까지 | 교사용 LA 대시보드 대부분이 awareness만 주고 actionable intervention이 없다는 비판 → 첫 화면을 "개입 필요 학생" 액션 큐로. |
| 역할 전환 = 컨텍스트 라벨 + accent 토큰 | 과한 색·뱃지 남용은 양산형. 좌상단 역할 라벨 + accent 한 토큰 교체로 "지금 누구의 화면인지"만 조용히. |
6.2 역할별 accent 오버라이드
body[data-role="learner"] { --accent:#3E7A6E; --accent-bright:#5F968B; --accent-tint:#EEF4F2; } /* 세이지-틸: 성장 */
body[data-role="instructor"] { --accent:#3A5BA0; --accent-bright:#5478C4; --accent-tint:#EAEFF7; } /* 인디고-블루: 감독 */
body[data-role="admin"] { --accent:#5B5F6B; --accent-bright:#7D818E; --accent-tint:#EEEFF1; } /* 슬레이트: 운영 */
세 역할 모두 채도를 낮춰 자극 없게. red(--crit-solid)는 어느 역할에서도 장식으로 안 쓴다 — 오직 "지금 개입해야 하는 학생/세션" 단 한 곳.
6.3 공통 앱 셸 (Shell)
같은 골격 공유, 좌측 네비 항목 + 좌상단 컨텍스트 라벨만 차이. 역할 전환 시 화면 리셋이 아니라 메뉴·accent만 바뀜.
- 톱바 56px
background:var(--surface); border-bottom:1px solid var(--hair);(그림자 없음). - 좌측 네비 기본 72px 아이콘 전용(Lucide stroke 1.75), hover/포커스 시 220px 슬라이드(160ms). 활성 = 아이콘
color:var(--accent)+ 3px 강조바 금지 → 아이콘 배경var(--accent-tint)border-radius:8px알약형(네비 알약만 8px 관용). - 역할 컨텍스트 라벨: 톱바 좌측,
font-family:var(--font-num); font-size:13px; letter-spacing:0.04em; color:var(--accent);— "학습 대시보드"/"교수 콘솔"/"운영 콘솔". "지금 누구로 보고 있나"의 유일하고 조용한 신호. - 권한 표현 원칙: 권한 없으면 DOM에서 제거. disabled 회색처리도 안 함 — 존재를 숨김. 화면이 깨끗해지고 "내가 못 하는 것" 노이즈 제거. 임상 데이터 프라이버시(관리자는 학생 개별 성장곡선·transcript 기본 접근 불가, 메뉴 자체 렌더 안 함).
6.4 학습자 대시보드 (홈 = "당신은 나아지고 있다")
COUNSELING PRACTICE · 4주차 ← kicker
수련생님, 지금까지 12회 연습했어요. ← h1 28px/700
최근 라포 형성이 눈에 띄게 안정됐습니다. ← lead, accent .em
┌─────────────────────────────┐ ┌───────────────────────────┐ ← cols 7:5 비대칭
│ 종합 상담 역량 │ │ 다음 연습 │
│ ╭───────╮ │ │ ▸ 불안 호소 내담자·중급 │
│ │ 72 │ +6 ▲ │ │ 이전 약점: 침묵 다루기 │
│ ╰───────╯ 지난주 대비 │ │ [ 연습 시작 → ] │
│ (단일 게이지, 둘레 progress)│ │ 추천 이유: 라포는 올랐지만 │
└─────────────────────────────┘ │ 위기개입 정확도 정체 │
└───────────────────────────┘
역량별 추이 (최근 8회) ← kicker
┌────────┐┌────────┐┌────────┐┌────────┐ ← small multiples 4열, 공통 y축 0-100
│라포형성 ││기법사용 ││개입정확도││공감반영 │
│ ╱╲__╱ ││ __╱╲╱ ││ ╲__╱╲ ││ __╱╱ │
│ 82 ▲ ││ 68 → ││ 61 ▼ ││ 75 ▲ │
└────────┘└────────┘└────────┘└────────┘
최근 피드백 모아보기 (테이블, 카드 아님) ← kicker
6/24 불안 내담자 │ "침묵 견디지 못하고 성급히 해결책 제시…" — 개입 ▸
6/22 분노 내담자 │ "반영 정확. 다만 감정 명명이 늦음." — 공감 ▸
- 종합 게이지(radar 대체): 단일 원형. SVG
<circle>트랙stroke:var(--neutral-150), fill arcstroke:var(--accent); stroke-linecap:round. 중앙 숫자 48px/700. 변화량+6 ▲=--pos-solid, 하락이면--warn-solid(앰버, 빨강 아님). 아래 한 문장 자연어 요약. - 역량별 추이 = small multiples: 4개 동일 크기 미니 라인. 공통 y축 0-100(첫 카드만 0/50/100 옅게 표기). 라인
stroke:var(--ink-2); stroke-width:1.5. 마지막 점만r:3; fill:var(--accent). 카드background:var(--surface); border:1px solid var(--hair); border-radius:8px; padding:16px. 상단 강조선·좌측바 금지. 우하단 최신값+추세 화살표(▲--pos, →--ink-3, ▼--warn). 호버 툴팁, 클릭 시 세션 상세. - 피드백 모아보기 = 테이블, 카드 아님: 날짜/내담자유형/인용 코멘트/태그/
▸. zebra 없이 행border-bottom:1px solid--paper-2. 행 hoverbackground:var(--accent-tint). 카드 격자로 흩뿌리지 않고 시간순 한 컬럼 위계.
6.5 교수자 콘솔 (홈 = "누구를 먼저 도와야 하는가")
핵심: awareness가 아니라 triage. 첫 화면 최상단이 "개입 필요 학생" 액션 큐.
상담실습 II · 28명 담당 ← kicker
이번 주, 3명에게 개입이 필요합니다. ← h1, accent .em
┌──────────────────────────────────────────────────────────────┐ ← 개입 큐(최우선, 풀폭)
│ ● 박서연 개입정확도 3주 연속 하락(61→52→44) 미연습 5일 ▸ │ 좌측 dot = --crit-solid
│ ● 이도현 위기상담 시뮬 중도이탈 2회 마지막 6/20 ▸ │
│ ● 최민준 라포 점수 급락, 피드백 미열람 7건 ▸ │
└──────────────────────────────────────────────────────────────┘
학생별 현황 [정렬: 위험도▾] [영역: 전체▾]
학생 세션 라포 기법 개입 공감 추세 마지막 상태
김수련 12 82↗ 68→ 61↘ 75↗ ▁▃▅▆▇ 6/24 양호
박서연 8 55↘ 49↘ 44↘ 58↘ ▇▅▃▂▁ 6/19 ● 개입
이도현 14 88↗ 79↗ 71→ 84↗ ▅▆▆▇▇ 6/23 양호
- 개입 큐(triage): 풀폭 패널. 각 행 좌측 dot
8px; background:var(--crit-solid)(red의 유일한 정당한 사용처). 사유 = 수치 변화 + 행동 신호(미연습일·미열람·중도이탈)를 자연어로. 알고리즘이 뽑되 교수가 최종 판단하도록 "왜"를 노출. - 반 평균 역량 분포 = 가로 막대(레이더 아님): fill
var(--accent), 트랙var(--paper-2). 최저 항목만 우측← 취약color:var(--warn-solid). 나머지 차분한 무채. - 학생별 테이블: sparkline(▁▃▅▆▇, 셀 안 7px SVG, 최근 8회 종합점수). 역량 셀
82↗(화살표 색만 시맨틱). 정렬 기본 위험도 순. 상태 "양호"는--ink-3무채, "● 개입"만--crit-soliddot. red를 한 곳에 집중. 행 클릭 → 학생 드릴다운(학습자 화면 + 세션별 utterance-level 평가 타임라인).
6.6 관리자 콘솔 (홈 = "시스템이 건강한가 · 누가 무엇을 했는가")
핵심: 운영 신뢰성 + 감사. 학습 내용엔 개입 안 하고 시스템·사용·교수활동 감사에 집중. 무채 슬레이트로 가장 차분.
- 운영 KPI 5: 사용자/강좌/세션주/가동%/API오류. mono 32px. 오류 0 초과 시 해당 KPI만
--warn-solid, 임계 초과--crit-solid. 정상 땐 전부 무채 — "조용한 게 정상". - 세션 추이 = 단일 라인차트(막대 아님). 피크 1점만 라벨.
- 음성·AI 사용 + 비용: OpenAI 음성 분/평가 토큰/추정 비용/예산 대비 게이지(80% 넘으면
--warn-solid). 운영비 가시화(윤찬 graphify 비용 통제 문화). - 교수 활동 감사 테이블: 교수별 피드백 열람률(품질 신호)·시나리오 발행·마지막 활동. 저조 지표만
← 저조--warn. "⚠ 점검" = 관리자 리마인드 대상. - Audit log: 시간순 한 컬럼. 행위자·행위·대상. 색 없음(감사는 중립). 클릭 시 상세.
6.7 데이터 시각화 하드룰 (전 역할 공통)
| 상황 | 채택 | 금지 |
|---|---|---|
| 한 사람 여러 역량 추이 | small multiples 라인(공통 y축) | 레이더/스파이더 |
| 여러 역량 현재값 비교 | 가로 막대(공통 0-100), 최저값만 강조 | 레이더, 전항목 색칠 |
| 한 지표 시간 추이 | 단일 라인(피크/최신 1점만 라벨) | 3D·면적 그라데이션 |
| 테이블 안 추세 압축 | sparkline(7px) + 화살표 | 셀마다 미니 도넛 |
| 종합 한 숫자 | 단일 원형 게이지 + 변화량 | 여러 도넛 격자 |
차트 공통 스타일: 격자선 최소(y축 0/50/100 옅은 점선만, 세로 격자 없음). 라인 stroke-width:1.5, 점은 최신/이상치만. 색은 데이터 의미에만(추세 ↗--pos/↘--warn/→무채, 평상시 라인 --ink-2 무채 → 색 인플레이션 방지). 진입 라인 draw 280ms, 게이지 arc 400ms ease-out 1회만. 깜빡임·펄스·glow 없음. 모든 수치 tabular-nums.
7. 컴포넌트 원칙 (요약 카탈로그)
공통: border-radius 절제(6~12px, 알약 금지), 그림자 절제(헤어라인 우선), border는 --hair. 카드는 쓰되 균등 4-4-4 격자 남용 금지 → 비대칭·표·도식.
7.1 버튼 (위계 3단, 알약형 금지 radius 8px)
| 종류 | 배경 | 텍스트 | 보더 | 용도 |
|---|---|---|---|---|
| Primary | --accent |
--text-on-accent |
none | 세션 시작·저장 (화면당 1~2개) |
| Secondary | --bg-surface |
--accent |
1px --accent |
보조 액션 |
| Ghost | transparent | --text-body |
none | 취소·텍스트 액션 |
| Danger | --crit-solid |
#fff | none | 세션 강제 종료 등 위급만 |
.btn { font: 600 var(--fs-sm)/1 var(--font-sans); padding: 10px 18px;
border-radius: var(--radius); border:1px solid transparent;
transition: background .16s ease, transform .12s ease; cursor:pointer; }
.btn-primary { background: var(--accent); color: var(--text-on-accent); }
.btn-primary:hover { background: var(--accent-deep); }
.btn:active { transform: translateY(.5px); } /* 미세 누름, 과한 모션 X */
.btn:focus-visible { outline:none; box-shadow: 0 0 0 3px var(--focus-ring); }
7.2 입력 (radius 6px, 포커스 시 보더색 + 얇은 ring, 좌측바 X)
.input { width:100%; background: var(--bg-surface-2); color: var(--text-body);
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 .15s, box-shadow .15s; }
.input:focus { border-color: var(--border-focus); outline:none; box-shadow: 0 0 0 3px var(--focus-ring); }
.input[aria-invalid="true"] { border-color: var(--crit-solid); }
7.3 카드 / 패널 (헤어라인 + 미세 그림자, 상단 강조선 금지)
.card { background: var(--bg-surface); border:1px solid var(--border-subtle);
border-radius: var(--radius); padding: var(--sp-5); box-shadow: var(--shadow-sm); }
.panel { background: var(--bg-surface); border:1px solid var(--border-subtle);
border-radius: var(--radius-lg); padding: var(--sp-6); }
.panel-tint { background: var(--bg-tint); border-color: transparent; } /* 강조 패널: 틴트 배경 */
7.4 평가 피드백 콜아웃 (이 플랫폼 핵심, 좌측 컬러바 금지 → 배경 틴트 + 도트 + kicker)
.callout { border-radius: var(--radius); padding: var(--sp-4) var(--sp-5);
font-size: var(--fs-sm); line-height:1.6; }
.callout .clh { font: 600 var(--fs-xs)/1.4; letter-spacing:.04em;
display:flex; align-items:center; gap:8px; margin-bottom:6px; }
.callout .clh::before { content:""; width:7px; height:7px; border-radius:50%; } /* dot, 바 아님 */
.callout.pos { background:var(--pos-tint); color:var(--pos-text); }
.callout.pos .clh::before { background:var(--pos-solid); }
.callout.info { background:var(--info-tint); color:var(--info-text); }
.callout.info .clh::before { background:var(--info-solid); }
.callout.warn { background:var(--warn-tint); color:var(--warn-text); }
.callout.warn .clh::before { background:var(--warn-solid); }
.callout.crit { background:var(--crit-tint); color:var(--crit-text); } /* 가드레일 위반만 */
.callout.crit .clh::before { background:var(--crit-solid); }
피드백 강도 매핑: 잘함→pos(녹색), 일반 코칭/관찰→info(슬레이트), 페이싱·라포 주의→warn(앰버), 가드레일/위급→crit(적색). 학습자 화면 기본 피드백은 info/pos가 다수가 되도록.
7.5 라이브 음성 오브 (§5.2 참조)
내담자 발화=sage, 학습자 발화=clay로 화자 구분(채도 낮게). 깜빡이는 빨강 금지 → 호흡 펄스.
8. React 구현 노트
8.1 컴포넌트 트리
<AppShell role={role}> // body[data-role] 설정, 네비·톱바
<RoleNav items={navByRole}/> // 권한 없는 항목 필터링 후 렌더(DOM 제거)
<ContextLabel/> // mono accent 라벨
<Outlet/>
├ <LearnerHome> Hero1줄 + cols(Gauge, NextPractice) + MiniMultiples×4 + FeedbackTable
├ <InstructorHome> InterventionQueue + KPIs + ClassDistribution + StudentTable(sparkline)
└ <AdminHome> OpsKPIs + SessionTrend + UsageCost + FacultyAudit + AuditLog
<SessionScreen>
├ <SessionTopBar scenario stage elapsed onEnd/>
├ <SessionLayout> // CSS grid 320/1fr/300, RN은 Flex
│ ├ <StagePanel> <StageTracker/> <ClientContextCard/> // LEFT
│ ├ <ClientStage> // CENTER
│ │ ├ <ClientAvatar persona state affect analyser/> // §4 아바타
│ │ ├ <VoiceOrb state amplitude/>
│ │ └ <LiveTranscript turns partial/>
│ └ <FeedbackRail mode> // RIGHT
│ ├ <ClientStateMeter tone openness/>
│ ├ <AmbientSignal latest recentSequence/> // 은은한 신호
│ ├ <CoachHint hint onDismiss/> // 코칭 모드만
│ └ <SelfNote/>
└ <ControlBar mic paused mode onMic onPause onModeChange onEnd/>
<ReviewScreen>
├ <ReviewHeader scenario onExportPdf onReplay/>
├ <ReviewHeadline summary/> // 큰 한 문장
├ <SessionTimeline phases markers onSeek/>
├ <AnnotatedTranscript turns techniqueLabels supervisorComments audioSync/>
└ <AnalysisSide techniqueDistribution highlights improvements/>
8.2 상태 관리
- 음성 이벤트(
onModeChange/onVolumeChange/partial transcript)는 OpenAI Realtime 스트림 → 단일SessionStore(Zustand 권장)로 모아VoiceOrb·LiveTranscript·FeedbackRail·ClientAvatar구독. - 평가 AI는 **별도 채널(백그라운드)**로 발화 단위 신호를 throttle해서 push.
FeedbackRail이 6초 페이드 큐로 소비. 평가와 음성 상태 표시는 분리(평가가 음성 UX를 막지 않게). - 차트 프리미티브 4종 먼저 구축:
<Gauge><SparkLine><MiniMultiple><HBar>(§6.7 룰 내장, props=data+role토큰). 학습자 컴포넌트가 교수 드릴다운에서 재사용됨.
8.3 구현 순서
tokens.css(§3) + 앱 셸(§6.3) — 셸은 3역할 공통이므로 1회만.- 차트 프리미티브 4종.
- 학습자 홈 → 교수 홈 → 관리자 홈 순(재사용 극대화).
- 세션 화면: 폴백 추상 오브 먼저(음성 파이프라인·4-state 배선 검증) → 반구상 SVG 아바타 → 정서 6파라미터 → 페르소나 JSON.
- 리뷰 화면 + utterance-level 평가 타임라인.
- 역할 가드: 라우터에서
role기반 메뉴·라우트 렌더 제외(disable 아님). - (여유) Rive 아바타 고도화. 안 되면 SVG 1차안으로 출시(폴백 안전).
8.4 RN 전환 대비
레이아웃 토큰은 절대 px 의존 최소화(rem·flex 우선). SVG→react-native-svg, Web Audio→react-native-audio-api, Rive→RN 런타임. 브레이크포인트 sm:640/md:768/lg:1024/xl:1280.
9. 안티패턴 체크리스트 (HARD NO — 출시 전 자가검증)
- border-left 강조선 / 좌측 사이드바 형태 강조줄 — 피드백·콜아웃·카드·활성표시에 색 막대 금지. 위계는 kicker+weight+여백+dot+배경틴트.
- 이모지 — UI·일러스트·SVG 내부 장식까지 전부 금지. 아이콘은 Lucide(stroke 2, 직각). 평가 결과에 체크/엑스 이모지 금지. 추세는 ▲▼→ 기하 화살표.
- 카드 덤프 / 균등 그리드 무한반복(4-4-4) — 비대칭(7:5) 레이아웃·데이터 테이블·도식으로. 학습자 홈은 게이지+small-multiples+테이블, 교수 홈은 triage 큐+KPI+테이블.
- 양산형 SaaS 랜딩 — "그라데이션 히어로 + 떠다니는 mockup + 3컬럼 feature 카드 + 보라 그라데이션 CTA" 공식 금지.
- 과한 그라데이션·glow·gradient-text·큰 blur — 깊이는 1px 헤어라인.
- 둥근 모서리 과다(16px+)·말랑 일러스트 — 거의 직각(6~12px). 원형은 오브·아바타·dot만.
- 게임화 폭격 — 점수 팡파레, 빨간 X, 레벨업 배지, 별점 5개 즉시. 평가는 서술형·비모달·회고 시점.
- 순흑(#000)·순백(#FFF) —
--ink:#1C2A2A/--paper:#FBFAF8. - 고채도 메디컬 블루 단독 의존 — 세이지-틸로 차별화 + 온기.
- 실시간 피드백 모달 — 상담 흐름 중 팝업 금지. 차분한 사이드 패널로.
- 레이더/스파이더 차트 — small-multiples 라인 + 단일 게이지로.
- 권한 없는 메뉴 disabled 회색 나열 — DOM에서 제거.
- 빨강 장식 사용 —
--crit-solid는 (교수)개입 큐·"개입" 상태·(관리자)임계 오류에만. - One screen, one message 위반 — 각 화면 최상단 h1이 그 화면 단일 메시지("나아지고 있다"/"3명 개입 필요"/"정상 운영"/"듣고 있어요").
10. 핵심 의사결정 5줄 (윤찬이 기억할 것)
- 컬러: 세이지-틸
#3E7A6E단일 accent + 테라코타#B0735C온기 보조. 의료 블루 회피, 저채도로 임상+따뜻함 동시. 역할별 accent 한 토큰만 교체. - 아바타: 사실적 얼굴(오인·불쾌한 골짜기·편향)도 추상 오브(비언어 연습 불가)도 답 아님 → 사실성 35~45% 양식화 반구상 흉상 + 호흡 광배. 눈·눈썹·입·어깨 4채널 × 정서 6파라미터. SVG 1차 → Rive 2차. 미성년은 realism 더 낮추고
교육용 가상 내담자라벨 상시. - 세션 화면: 실시간 피드백은 "신호(색 도트, 6초 페이드, 1개)"까지만, 정밀 평가는 회기말 리뷰로. 학습자가 모드(몰입/은은/코칭)를 고른다. 음성 상태는 막대 파형이 아니라 빛의 호흡(sage=학습자, clay=내담자).
- 시각적 무게 = 주의 배분. 내담자 stage 60%, 평가 패널 15%·평시 접힘. 화면은 대시보드가 아니라 상담실.
- 대시보드: 레이더 금지 → small-multiples 라인 + 단일 게이지. 점수·합불 없음(성장 추세만). 교수자 첫 화면은 triage 큐. 권한 없으면 DOM 제거.
참고 (실조사 출처)
디자인 언어·시스템
- Linear Brand Guidelines / Stripe Design / Calm·Headspace 색 (accent rationing, 저채도, 호흡 모션)
- Fuzzy Math — Color Palettes of Mental Healthcare UI / Villa Healing Center — Calming Colors
- Halo Lab — Digital Healthcare UX Trends 2026 / Progress — Color Psychology in Healthcare
- Pretendard (orioncactus/pretendard) / az-loc — Best Fonts for CJK Websites 2026
- Aufait UX — Accessible Color Tokens / WCAG Contrast Guide 2026
아바타
- Behavioral Sciences 2026 — Human-like/Animal-like/Object-like LLM Virtual Doctor Avatar
- arXiv 2506.11890 — Graduated Realism (AI Avatars in VR Teacher Training)
- PMC12692777 — AI Physician Avatars (윤리·편향·오인 위험)
- NYU Steinhardt — AI Avatar for Social Work Counseling Practice
- ElevenLabs UI Orb / orb-ui (4-state) / Rive vs Lottie 2026 / arXiv 2502.16038 (Valence-Arousal-Dominance)
세션·대시보드
- arXiv 2502.18673 — Scaffolding Empathy (utterance-level 피드백 학습효과 vs 인지부하) ← 2단 피드백 모델 근거
- arXiv 2505.02428 — LLM-Simulated Practice/Feedback for Counselors
- assistant-ui / Vocalis / Agora (음성 상태 시각화)
- Scott Logic·Observable — Radar Charts 비판 (small-multiples 대체 근거)
- GoReact — Counseling Skills Rubric / Springer Open — LA Dashboard actionable intervention 비판
- Eleken·Fuselab — Healthcare UI/UX 2026
vault 연계 자산
Resources/50_Tech_Dev/디자인/slide-base.css(무채 골격·강조 위계 계승)Resources/50_Tech_Dev/디자인/[디자인 시스템] HTML 슬라이드·보고서 베이스 (2026-06).md(한신대/의료 행에--accent:#3E7A6E채움)