한달품 데모 브랜드 — 가상 산후조리원 테마
데모 브랜드 테마 규약(
cc-flutter:demo-brand-themeR1~R7)의 저장소 정본이다. 설계 초안은 cocode#1275 (docs/projects/postpartum-crm-demo/brand.md)이고, 구현은 cocode#1276 이다. 앱 이름 · 조직 같은 정체성 값은identity.yaml이 가진다(이 문서에 적지 않는다).
어디에 있나
| 무엇 | 파일 |
|---|---|
| 브랜드 결정(7축) — 유일한 곳 | package/resources/lib/src/theme/postpartum_brand.dart(PostpartumBrand) |
| 플랫폼 테마(앱 · 콘솔 · 위젯북 · 골든이 같은 것을 쓴다) | package/resources/lib/src/theme/app_theme.dart(AppTheme.light/dark · 키오스크 kioskLight/kioskDark) |
| 실제 슬롯 값 · 대비 시험(이 문서의 표가 이 시험과 같아야 한다) | package/resources/test/app_theme_test.dart |
| 위젯북 「브랜드 토큰」 페이지(R7) | package/postpartum_ui/widgetbook/lib/src/brand_tokens_use_case.dart |
| 웹 첫 프레임 전 배경(pre-boot) | AppTheme.scaffoldBackgroundOf → app/*/web/index.html(생성물 — app/app 의 UPDATE_PRE_BOOT=1 flutter test test/web/pre_boot_test.dart) |
| 모노그램 「품」(앱 안) | package/postpartum_ui/lib/src/brand/brand_mark.dart(BrandMark — 사이드바 머리 · 로그인 C-00 왼쪽 패널은 inverted) |
| 웹 아이콘 · 파비콘 · manifest 아이콘 | python3 tool/brand/build_web_icons.py → app/app/web/icons/Icon-{192,512}.png(둥근 사각) · Icon-maskable-*(꽉 찬 면, 글자는 안전 영역 안) · app/{app,console,app_widgetbook}/web/favicon.png |
| Pages 랜딩 · 문서 사이트 색 | tool/pages/landing.py · tool/pages/docs/bin/build_docs.dart 의 CSS 변수(아래 표의 surface · onSurface · onSurfaceVariant · outlineVariant · primary) |
| 키오스크(자가 입력 앱) 테마 | app/app/lib/src/app.dart 가 AppTheme.kioskLight/kioskDark — 바탕은 콘솔과 같다(pre-boot 하나) |
화면 코드는 PostpartumBrand 를 읽지 않는다 — context.theme.colorScheme.* · Text 체인(Text('…').titleSmall.onSurfaceVariant) ·
CoreColor.token(CoreColors.*) 만 쓴다. 원시 Color(0x…) · Colors.* · BorderRadius.circular · BoxShadow 는 쓰지 않는다.
성격과 근거
- 포근함 — 산모가 한 달을 머무는 곳이다. 라벤더 자두색 주색과 카라멜 보조색으로 차갑지 않은 업무 화면을 만든다.
- 안심 — 기록 · 결제 · 환불이 오가는 CRM 이다. 상태색(성공 · 경고 · 오류 · 정보)과 주색이 헷갈리지 않아야 한다.
- 정돈 — 하루 종일 쓰는 콘솔이다. 평면 표면 · 1px 테두리 · 보통 밀도로 정보를 촘촘하지만 차분하게 둔다.
색
시드(HSL)와 중립 hue — coui PrimitiveColors.generate → 표준 매핑(CoreSemanticMappingSet.standard):
PrimitiveColors.generate(
primaryHSL: (h: 282, s: .30, l: .42), // 라벤더 자두 — 주 행동 · 선택 · 진행
secondaryHSL: (h: 30, s: .40, l: .42), // 카라멜 — 컨테이너 면 · 보조 강조
tertiaryHSL: (h: 152, s: .24, l: .36), // 세이지 — 분류 태그 · 장식
successHSL: (h: 142, s: .52, l: .32),
infoHSL: (h: 212, s: .54, l: .38),
warningHSL: (h: 42, s: .70, l: .38),
errorHSL: (h: 6, s: .65, l: .40),
neutralHue: 282,
)
| 기준 | 거리 |
|---|---|
| 상태색 — 정보 212 · 오류 6 · 경고 42 · 성공 142 | 70° 이상(가장 가까운 정보 212 와 70°) |
| 같은 의료 패밀리 — 동물병원 174 · 치과 244 · 방문요양 314 | 32° 이상 |
| 다른 데모 — 15 · 22 · 190 · 193 · 230 · 250 | 32° 이상 |
고대비 매핑은 쓰지 않는다 — 라이트 주색이 거의 검정(OKLCH L 26%)이 되어 브랜드 성격이 사라진다. 표준 매핑으로 대비 감사를 통과한다(아래).
실제 슬롯 값(59)
coui_core coui_core-v0.159.0(루트 dependency_overrides)에서 AppTheme.core(b).colorScheme.resolved(b).toMap() 으로 뽑았다.
설계 초안(coui_core 0.159.0 기준)의 표와 값이 모두 같다.
| 슬롯 | 라이트 | 다크 |
|---|---|---|
primary |
#784B8B |
#BE9FCC |
on-primary |
#FDFCFD |
#2F1D36 |
primary-container |
#EEE6F1 |
#36223F |
on-primary-container |
#2F1D36 |
#EEE6F1 |
secondary |
#966B40 |
#D3B598 |
on-secondary |
#FDFCFD |
#3A2A19 |
secondary-container |
#F3EBE4 |
#44311D |
on-secondary-container |
#3A2A19 |
#F3EBE4 |
tertiary |
#46725D |
#9BC2B0 |
on-tertiary |
#FDFCFD |
#1F332A |
tertiary-container |
#E6F0EB |
#23392F |
on-tertiary-container |
#1F332A |
#E6F0EB |
error |
#A83124 |
#E48C82 |
on-error |
#FDFCFD |
#44140F |
error-container |
#F8E1DE |
#4F1711 |
on-error-container |
#44140F |
#F8E1DE |
error-strong |
#4F1711 |
#F8E1DE |
focus-ring |
#784B8B |
#BE9FCC |
primary-hover |
#4E305A |
#E2D4E8 |
primary-pressed |
#36223F |
#EEE6F1 |
secondary-hover |
#61452A |
#EBDED1 |
secondary-pressed |
#44311D |
#F3EBE4 |
error-pressed |
#6E2017 |
#F3CCC7 |
success |
#277C46 |
#7ED69E |
on-success |
#FDFCFD |
#133D23 |
success-container |
#DFF5E7 |
#154427 |
on-success-container |
#133D23 |
#DFF5E7 |
warning |
#A57C1D |
#E8C77A |
on-warning |
#1A181B |
#46350C |
warning-container |
#F9F1DD |
#503C0E |
on-warning-container |
#46350C |
#F9F1DD |
info |
#2D5D95 |
#87AEDB |
on-info |
#FDFCFD |
#13283F |
info-container |
#E0EAF6 |
#162D48 |
on-info-container |
#13283F |
#E0EAF6 |
surface |
#FDFCFD |
#1A181B |
on-surface |
#1A181B |
#FDFCFD |
on-surface-variant |
#504D51 |
#C5C3C6 |
placeholder |
#676468 |
#B1AEB2 |
surface-tint |
#784B8B |
#BE9FCC |
surface-dim |
#F3F2F3 |
#000000 |
surface-bright |
#FDFCFD |
#3C383D |
surface-container-lowest |
#FDFCFD |
#000000 |
surface-container-low |
#F3F2F3 |
#1A181B |
surface-container |
#E6E4E7 |
#2A272B |
surface-container-high |
#D7D5D7 |
#3C383D |
surface-container-highest |
#C5C3C6 |
#504D51 |
outline |
#676468 |
#B1AEB2 |
outline-variant |
#C5C3C6 |
#676468 |
inverse-surface |
#1A181B |
#FDFCFD |
inverse-on-surface |
#FDFCFD |
#1A181B |
inverse-primary |
#BE9FCC |
#4E305A |
shadow |
#000000 |
#FFFFFF |
surface-highlight |
#FFFFFF |
#B1AEB2 |
scrim |
#000000 |
#000000 |
disabled-content |
#817D82 |
#817D82 |
disabled-container |
#E6E4E7 |
#2A272B |
disabled-outline |
#C5C3C6 |
#504D51 |
대비 측정(WCAG AA)
CoreContrast.auditScheme(coui_core 0.159.0)를 라이트 · 다크 모두 돌린 결과다 — 시험이 실패 0건을 단정한다.
라이트: 0 of 24 pairs fail · 다크: 0 of 24 pairs fail.
| 쌍(전경 on 배경) | 기준 | 라이트 | 다크 |
|---|---|---|---|
onSurface on surface |
aaNormalText ≥4.5 | 17.24:1 ✓ | 17.24:1 ✓ |
onSurfaceVariant on surface |
aaNormalText ≥4.5 | 8.14:1 ✓ | 10.08:1 ✓ |
onSurface on surfaceContainer |
aaNormalText ≥4.5 | 13.96:1 ✓ | 14.41:1 ✓ |
onSurfaceVariant on surfaceContainer |
aaNormalText ≥4.5 | 6.59:1 ✓ | 8.43:1 ✓ |
placeholder on surface |
aaNormalText ≥4.5 | 5.70:1 ✓ | 8.04:1 ✓ |
primaryText on surface |
aaNormalText ≥4.5 | 14.08:1 ✓ | 12.45:1 ✓ |
primaryTextLarge on surface |
aaLargeText ≥3.0 | 14.08:1 ✓ | 7.58:1 ✓ |
error on surface |
aaNormalText ≥4.5 | 6.56:1 ✓ | 7.03:1 ✓ |
onPrimary on primary |
aaNormalText ≥4.5 | 6.46:1 ✓ | 6.68:1 ✓ |
onPrimaryContainer on primaryContainer |
aaNormalText ≥4.5 | 12.76:1 ✓ | 11.82:1 ✓ |
onSecondary on secondary |
aaNormalText ≥4.5 | 4.59:1 ✓ | 7.10:1 ✓ |
onSecondaryContainer on secondaryContainer |
aaNormalText ≥4.5 | 11.68:1 ✓ | 10.46:1 ✓ |
onTertiary on tertiary |
aaNormalText ≥4.5 | 5.36:1 ✓ | 6.86:1 ✓ |
onTertiaryContainer on tertiaryContainer |
aaNormalText ≥4.5 | 11.52:1 ✓ | 10.62:1 ✓ |
onError on error |
aaNormalText ≥4.5 | 6.56:1 ✓ | 6.19:1 ✓ |
onErrorContainer on errorContainer |
aaNormalText ≥4.5 | 12.43:1 ✓ | 11.49:1 ✓ |
onSuccess on success |
aaNormalText ≥4.5 | 5.06:1 ✓ | 6.98:1 ✓ |
onSuccessContainer on successContainer |
aaNormalText ≥4.5 | 10.67:1 ✓ | 9.72:1 ✓ |
onWarning on warning |
aaNormalText ≥4.5 | 4.62:1 ✓ | 7.26:1 ✓ |
onWarningContainer on warningContainer |
aaNormalText ≥4.5 | 10.51:1 ✓ | 9.36:1 ✓ |
onInfo on info |
aaNormalText ≥4.5 | 6.60:1 ✓ | 6.49:1 ✓ |
onInfoContainer on infoContainer |
aaNormalText ≥4.5 | 12.31:1 ✓ | 11.49:1 ✓ |
inverseOnSurface on inverseSurface |
aaNormalText ≥4.5 | 17.24:1 ✓ | 17.24:1 ✓ |
outline on surface |
nonText ≥3.0 | 5.70:1 ✓ | 8.04:1 ✓ |
감사 밖이지만 공용 위젯(package/postpartum_ui)이 더 쓰는 쌍 — 같은 시험이 CoreContrast.meets 로 단정한다:
| 테마 | 쌍 | 기준 | 비율 |
|---|---|---|---|
| 라이트 | onSurfaceVariant on surfaceContainerLow(모의 안내 글자) |
4.5 | 7.46:1 ✓ |
| 라이트 | primary on surfaceContainerLow(모의 안내 아이콘) |
3.0 | 5.92:1 ✓ |
| 라이트 | primary on surface(KPI 아이콘) |
3.0 | 6.46:1 ✓ |
| 라이트 | outline on surfaceContainer(중립 상태 칩 점) |
3.0 | 4.62:1 ✓ |
| 다크 | onSurfaceVariant on surfaceContainerLow |
4.5 | 10.08:1 ✓ |
| 다크 | primary on surfaceContainerLow |
3.0 | 7.58:1 ✓ |
| 다크 | primary on surface |
3.0 | 7.58:1 ✓ |
| 다크 | outline on surfaceContainer |
3.0 | 6.72:1 ✓ |
(이 표의 비율은 WCAG 2.x 공식으로 계산했고, 통과 여부는 시험이 coui 대비 API 로 다시 확인한다.)
상태(성공 · 경고 · 오류)는 색만으로 구분하지 않는다 — 상태 칩 · 상태 화면은 늘 문구(와 아이콘)를 함께 둔다.
7축 결정
| 축 | 선택 | 구현 | 이유 |
|---|---|---|---|
| 색 | 위 시드 · 표준 매핑 · neutralHue 282 | PostpartumBrand.brand · core() |
상태색과 거리, 포근한 중립 회색 |
| 타이포 | CoreTypography.coui 17역할, 헤드라인 3종 semibold |
core().typography |
표 스캔 · 차분한 위계. 본문 글꼴은 Pretendard(번들), 그 밖 글자는 LocaleFallback(웹 전역 폴백 Roboto 자리 — tool/fonts) |
| 형태 | 카드 radius12 · 버튼 radius10 · 입력 radius8 · 배지/칩 radius9999 · 다이얼로그 radius12, 테두리 outlineVariant stroke1 | CoreComponentTheme(card · button · textField · badge · chip · dialog) |
업무 화면 밀도에 맞는 중간 반경, 상태 칩은 알약형 |
| 간격 · 밀도 | 카드 안쪽 space16, 버튼 높이 48 · 입력 최소 높이 48 · 달력 칸 48, 콘솔은 보통 밀도 · 자가 입력은 여유 밀도 | 같은 컴포넌트 테마 | 터치 48dp × scaling(R-4) — 태블릿 사용이 많다 |
| 효과 | 카드는 surface + outlineVariant 1px · 평면(elevation none), 다이얼로그 · 시트만 그림자 sm, 기본 포커스 링 | card · dialog 테마 | 배경 회색 카드 금지(card-surface-tokens) |
| 모션 | 카드 · 버튼 전환 CoreDuration.normal(테마), 끌어 옮기기(칸반 · 객실 보드)는 CoreDuration.fast(기능 화면 몫), 동작 줄이기 존중(coui) |
card · button 테마 | 직접 조작은 즉시, 전환은 차분하게 |
| 아이콘 | Lucide — 콘솔 20 · 자가 입력 24, onSurfaceVariant / primary | PostpartumBrand.console · kiosk(AppTheme.light/dark · kioskLight/kioskDark) |
밀도 · 접근성. coui 묶음 밖 아이콘은 AppIcons(tool/icons)가 싣는다 |
탭 소리(tapSoundEnabled)는 끈다 — 하루 종일 쓰는 콘솔이다.
배율(scaling)
AppTheme 은 AdaptiveScaling 을 걸지 않는다 — coui 0.157 부터 휴대폰 기본 배율이 1 이고, 같은 커널 세대의 데모(vet ·
daycare)도 걸지 않는다. 터치 타깃은 컴포넌트 높이(48)로 지키고(시험: 기본 배율에서 버튼 ≥ 48, 명시 배율 1.25 에서 ≥ 60),
레이아웃 시험은 testScaleInvariant(scaling 1.25 × textScaler 1.0 · 1.15 · 1.3 · 2.0)로 더 엄격한 조건을 본다.
실존 브랜드 모방 여부
- 이름 「한달품」(한 달을 품는 곳)은 설계 단계에서 웹 검색 1회로 같은 이름의 산후조리원을 찾지 못했다. 상표 검색은 하지 않았다 — 공개 전에 확인할 것.
- 받은 자료의 고객 브랜드 색 · 로고 · 문구는 쓰지 않았다.