feat(ui): 콘솔 재디자인 적용 (Toss 스타일) + Pico v2 호환 수정
디자인 핸드오프(design_handoff_console_redesign)를 실제 앱에 반영.
CSS 드롭인 교체 + 최소 마크업 추가, HTMX 훅·id·data 속성은 모두 보존.
- app.css: 재디자인 토큰/컴포넌트로 교체(요약카드 호버 리프트, 표
제브라, 중앙 경고형 배포 모달, secondary/ghost/danger 버튼 체계,
진행률·상태칩·스켈레톤 등).
- base.html: 브랜드 로고 큐브(.brand-mark) 추가 + 데모 배너.
- repo_list: 요약 카드 .card-top/.card-ico + 검색 돋보기 아이콘.
- table_wrap: 회전형 정렬 인디케이터, 동기화 버튼 아이콘.
- progress: .sync-head/.sync-pct/.is-active/.indeterminate 구조.
- deploy_modal: 중앙 경고형 + 현재→대상 프리뷰 카드 + 타입-투-컨펌
체크/X 시각 상태.
- deploy_success: 그려지는 체크 원 배너.
- version_list: 표 → .version-item 카드 리스트.
Pico v2 호환(미리보기는 pico-shim 기준이라 안 드러났던 것):
- Pico 의 `:is(...).secondary`(0,2,0)·hover(0,4,0) 특정성을 이기도록
오버라이드를 :is() 형태로 상향 → 동기화 버튼 흰배경+파란글씨 유지,
hover 시 어두워지던 문제 해결.
- `input[type=search]` 의 Pico 기본 돋보기 배경 제거(아이콘 중복).
- `td` 배경을 투명 처리해 tr 제브라/hover 가 보이도록(Pico 의
`td{background:var(--pico-background-color)}` 가 덮던 문제), 줄무늬
색을 --bg-alt 로 또렷하게.
- .row-action 을 40px 고정 → 버튼↔진행률↔배지 교체 시 높이 흔들림·
위아래 테두리 잘림 해소.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
237
design_handoff_console_redesign/README.md
Normal file
237
design_handoff_console_redesign/README.md
Normal file
@@ -0,0 +1,237 @@
|
||||
# Handoff: 폐쇄망 Pulp 운영 콘솔 — 시각 디자인 개선
|
||||
|
||||
## Overview
|
||||
한국은행 IT전략부 클라우드팀의 폐쇄망(인터넷 차단) Pulp 3 관리 웹 UI의 **시각 디자인 개선**입니다.
|
||||
운영자가 저장소를 동기화 → 버전 스냅샷 확인 → 검증 → 운영 배포하는 도구로, "관공서 기본 폼"
|
||||
느낌을 벗고 Toss 스타일의 신뢰감 있고 정돈된 운영 도구로 끌어올리는 것이 목표입니다.
|
||||
|
||||
기능/스택 변경 없이 **CSS(토큰 + 컴포넌트) + 최소한의 마크업 추가**만으로 개선했습니다.
|
||||
|
||||
## About the Design Files
|
||||
이 번들의 파일은 **HTML로 만든 디자인 레퍼런스**입니다. 다만 이 프로젝트는 특수하게도
|
||||
**타깃 환경이 이미 정해져 있고, 산출물이 그 환경에 그대로 들어가는 CSS**입니다:
|
||||
|
||||
- `app.css` — **실제 산출물.** 기존 앱의 `app/static/app.css`를 이 파일로 교체하면 됩니다.
|
||||
(Pico.css v2 뒤에 로드되어 변수를 오버라이드하는 커스텀 스타일. 순수 CSS, 외부 의존 0.)
|
||||
- `preview.html` — **렌더링 참고용.** 개선안이 실제로 어떻게 보이는지 확인하는 하니스입니다.
|
||||
앱에 넣지 마세요.
|
||||
- `pico-shim.css` — **미리보기 전용, 앱에 넣지 마세요.** 폐쇄망에서 CDN 없이 preview.html을
|
||||
렌더링하려고 Pico의 최소 기본 스타일만 흉내 낸 파일입니다. 실제 앱은 진짜 Pico.css v2를 씁니다.
|
||||
|
||||
**작업의 본질:** `app.css`를 교체하고, 아래 "컴포넌트별 변경 + 필요 마크업"에 적힌 소량의
|
||||
템플릿 마크업(Jinja2)을 추가/조정하는 것입니다. 프레임워크를 바꾸지 않습니다.
|
||||
|
||||
## Fidelity
|
||||
**High-fidelity.** 최종 색/타이포/간격/인터랙션이 확정된 목업입니다. 아래 값 그대로 구현하세요.
|
||||
|
||||
## 절대 지켜야 할 기술 제약
|
||||
1. **빌드 단계 없음.** React/Vue/Vite/npm/Tailwind 금지. 순수 HTML + HTMX + Jinja2 +
|
||||
Pico.css v2 + 커스텀 CSS(`app.css`)만.
|
||||
2. **폐쇄망.** 런타임에 외부 네트워크(구글폰트/CDN/원격 아이콘/fetch) 참조 금지. 모든 자원 로컬 동봉.
|
||||
새 `@import`(원격)/CDN `<link>`/`<script>` 추가 금지. **아이콘은 인라인 SVG.**
|
||||
3. **폰트는 이미 로컬 동봉:** 본문 `Pretendard Variable`, 코드/숫자 `JetBrains Mono`
|
||||
(`@font-face`로 `/static/fonts/*.woff2` 로드). 새 폰트 추가 금지. (`app.css`에 @font-face 포함됨)
|
||||
4. **크롬에 장식용 이모지 금지.** 상태는 색 있는 chip/dot으로.
|
||||
5. 뷰는 HTML 조각(fragment)을 반환, HTMX가 swap. 라이트 모드 기본, 다크는 토글 옵션.
|
||||
|
||||
### 보존해야 하는 훅 (바꾸면 기능 깨짐)
|
||||
- 모든 HTMX 속성: `hx-get/hx-post/hx-target/hx-swap/hx-trigger/hx-include/hx-vals/hx-disabled-elt`
|
||||
(값·타깃 셀렉터 유지).
|
||||
- element id: `#repo-list`, `#repo-table-wrap`, `#repo-search`, `#modal`, `#flash`,
|
||||
`#sync-{uuid}`, `#deploy-confirm-btn`.
|
||||
- data 속성: `data-state`(정렬), `data-confirm`(타입-투-컨펌), `data-theme`(테마).
|
||||
- 배포 확정 버튼의 `oninput` 비활성화 로직, 테마 토글/FOUC 방지 인라인 스크립트.
|
||||
- 클래스명을 리네임하려면 CSS와 템플릿 **양쪽 다** 맞춰 바꿀 것.
|
||||
|
||||
> 주: `preview.html`의 검색 input은 데모라 id가 없습니다. 실제 앱에서는 `#repo-search`를
|
||||
> 그대로 두고 `.repo-search` 클래스만 유지하세요.
|
||||
|
||||
---
|
||||
|
||||
## 컴포넌트별 변경 + 필요 마크업
|
||||
대부분은 기존 마크업 그대로 두고 CSS만 바뀌면 됩니다. 아래는 **새 요소/구조가 필요한 부분**만
|
||||
정리했습니다. 각 스니펫은 `preview.html`에서 실제로 렌더되는 형태입니다.
|
||||
|
||||
### 0. 상단 내비 브랜드 마크 (선택)
|
||||
차분한 단색 사각 로고 큐브. 인라인 SVG 없이 CSS만으로.
|
||||
```html
|
||||
<p class="brand"><span class="brand-mark">P</span>Pulp <span class="accent">Console</span></p>
|
||||
```
|
||||
|
||||
### 1. 요약 카드 (대시보드)
|
||||
카드 상단에 **라벨 + 아이콘 칩** 한 줄(`.card-top`)을 추가. 값은 그대로 `.value`.
|
||||
`.is-accent`(전체 저장소=파랑), `.is-success`(GPG 통과=민트)로 강조.
|
||||
```html
|
||||
<div class="card is-accent">
|
||||
<div class="card-top">
|
||||
<span class="label">전체 저장소</span>
|
||||
<span class="card-ico"><svg class="icon" viewBox="0 0 24 24" ...>…</svg></span>
|
||||
</div>
|
||||
<div class="value is-accent">18</div>
|
||||
</div>
|
||||
```
|
||||
호버 시 살짝 떠오름(translateY -2px)+그림자. (reduced-motion에서 자동 비활성)
|
||||
|
||||
### 2. 검색 툴바
|
||||
`.list-toolbar`를 relative 래퍼로 두고 왼쪽에 돋보기 인라인 SVG(`.search-ico`).
|
||||
input(`#repo-search`, `.repo-search`)의 왼쪽 패딩을 아이콘 자리만큼(40px) 확보(→ CSS가 처리).
|
||||
```html
|
||||
<div class="list-toolbar">
|
||||
<svg class="search-ico" viewBox="0 0 24 24" ...><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
<input id="repo-search" class="repo-search" type="search" placeholder="저장소 이름으로 검색…"
|
||||
hx-get="..." hx-target="#repo-table-wrap" hx-trigger="input changed delay:300ms" ...>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 3. 저장소 표
|
||||
- 카드로 감싸기(`.table-card`) — 기존 유지.
|
||||
- 확정안 = **제브라 스트라이프**: 짝수 행에 옅은 톤(CSS `tbody tr:nth-child(even)`), hover는 틴티드 블루.
|
||||
- 정렬 헤더: `a.th-sort`(활성 시 `.is-active`), 방향 인디케이터 `.sort-ind`(desc면 `.th-sort.is-active.desc`가 화살표 회전).
|
||||
- 검증 배지: `.badge.badge-pass|warn|unset|deployed`(dot + 라벨, 이모지 없음).
|
||||
- 저장소명 셀: `.repo-name > a`(모노, hover 시 accent).
|
||||
- 액션 셀: 폭·높이 고정 박스 `.row-action`(버튼↔진행률↔배지로 내용이 바뀌어도 레이아웃 안 흔들림).
|
||||
```html
|
||||
<td><div class="row-action"> …버튼/진행률/배지… </div></td>
|
||||
```
|
||||
|
||||
### 4. 버튼 색 체계 (중요)
|
||||
- **동기화(반복 노출)** = `.secondary` → **흰 배경 + 파란 글씨 + 옅은 파란 테두리.** 표에 여러 개 떠도 안 번잡.
|
||||
- **새로고침 등 유틸리티** = `.ghost` → 중립(투명 배경 + 회색 테두리). 동기화와 확실히 구분.
|
||||
- **위험(배포 확정)** = `.danger` → 빨강 채움 + 흰 글씨.
|
||||
- 공통: press 시 `scale(0.97)`, hover 시 옅은 그림자.
|
||||
- **로딩 상태**: 버튼에 `.is-loading` 클래스를 붙이면 텍스트가 스피너로 대체됨. HTMX 요청 시
|
||||
`hx-disabled-elt`와 함께 `.is-loading`을 토글하면 자연스럽다.
|
||||
```html
|
||||
<button class="secondary btn-sm btn-icon" hx-post="/sync/{{ repo.id }}"
|
||||
hx-target="#sync-{{ repo.id }}" hx-disabled-elt="this">
|
||||
<svg class="icon" ...>…</svg> 동기화
|
||||
</button>
|
||||
```
|
||||
|
||||
### 5. 동기화 진행률
|
||||
```html
|
||||
<div class="sync-progress is-active">
|
||||
<div class="sync-head"><small>동기화 중</small><small class="sync-pct">64%</small></div>
|
||||
<progress value="64" max="100"></progress>
|
||||
</div>
|
||||
```
|
||||
값을 모르는 초기 단계엔 `.sync-progress.indeterminate`(움직이는 인디터미닛 바)를 쓸 수 있음.
|
||||
|
||||
### 6. 배포 확인 모달 (`<dialog id="modal">`) — 위험 강조 + 타입-투-컨펌
|
||||
확정안 = **중앙 경고형.** 헤더 밴드 대신 상단 중앙에 원형 경고 아이콘.
|
||||
```html
|
||||
<dialog id="modal">
|
||||
<article class="deploy-modal">
|
||||
<div class="modal-head">
|
||||
<span class="warn-ico"><svg viewBox="0 0 24 24" ...>△!</svg></span>
|
||||
<div><h3>운영 배포 확인</h3><span class="head-sub">되돌리기 어려운 작업입니다</span></div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p class="deploy-note" style="text-align:center"><span class="repo-hl">rhel8-baseos</span> 저장소를 운영에 배포합니다.</p>
|
||||
|
||||
<!-- 현재 → 대상 카드형 프리뷰 -->
|
||||
<div class="deploy-preview">
|
||||
<div class="pv-box"><div class="pv-label">현재 (운영)</div><div class="pv-ver">v140</div><div class="pv-pkg">9,001 pkgs</div></div>
|
||||
<div class="pv-arrow"><svg ...>→</svg></div>
|
||||
<div class="pv-box pv-target"><div class="pv-label">대상</div><div class="pv-ver">v142</div><div class="pv-pkg">9,204 pkgs · <span class="delta-add">+203</span></div></div>
|
||||
</div>
|
||||
|
||||
<!-- 타입-투-컨펌: 이름이 정확히 일치해야 확정 버튼 열림 -->
|
||||
<label class="confirm-label">확인을 위해 저장소 이름 <span class="repo-hl">rhel8-baseos</span> 을 입력하세요.
|
||||
<div class="confirm-field" data-confirm="rhel8-baseos">
|
||||
<input type="text" autocomplete="off" placeholder="rhel8-baseos"
|
||||
oninput="document.getElementById('deploy-confirm-btn').disabled = (this.value.trim() !== this.closest('.confirm-field').dataset.confirm)">
|
||||
<svg class="confirm-state ok" ...>✓</svg>
|
||||
<svg class="confirm-state bad" ...>✕</svg>
|
||||
</div>
|
||||
</label>
|
||||
<!-- 힌트 문구 없음: 상태는 초록 체크/초록 테두리(일치) · 빨강 X/빨강 테두리(불일치)로만 표시 -->
|
||||
|
||||
<div class="modal-actions">
|
||||
<button class="ghost" onclick="this.closest('dialog').close()">취소</button>
|
||||
<button class="danger" id="deploy-confirm-btn" disabled>운영에 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</dialog>
|
||||
```
|
||||
입력 상태 시각화는 **`.confirm-field`에 `.is-valid` / `.is-invalid` 클래스**로 제어됩니다.
|
||||
- 순수 오조작 방지만 필요하면 위 `oninput`처럼 `#deploy-confirm-btn.disabled`만 토글해도 됨.
|
||||
- 체크/X 아이콘·테두리 색까지 원하면 값 일치 여부에 따라 `.confirm-field`에
|
||||
`is-valid`/`is-invalid`도 함께 토글(입력이 비면 둘 다 제거).
|
||||
- 열릴 때 배경 스크롤 잠금은 CSS `html:has(dialog[open]){overflow:hidden}` + 진입 애니메이션을
|
||||
translate 없이 scale/opacity로만 처리해 **순간 스크롤바 깜빡임이 없음.**
|
||||
|
||||
### 7. 버전 목록 (저장소 상세 — 저장소명 클릭 시 화면)
|
||||
```html
|
||||
<a href="{{ url_for('dashboard') }}" role="button" class="ghost btn-sm btn-icon back-btn">
|
||||
<svg class="icon" ...>←</svg> 대시보드
|
||||
</a>
|
||||
<div class="verify-detail"><span class="badge badge-pass">GPG 통과</span><small>서명 검증 통과 · … · 키 0x3A9F…</small></div>
|
||||
<div class="version-list">
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v142</span>
|
||||
<span class="badge badge-deployed">운영 중</span>
|
||||
<span class="ver-meta">2026-07-09 04:12 · 9,204 pkgs</span>
|
||||
<span class="spacer"></span>
|
||||
<button class="ghost btn-sm" disabled>현재 운영 버전</button>
|
||||
</div>
|
||||
<div class="version-item">
|
||||
<span class="ver-no">v141</span>
|
||||
<span class="ver-meta">… · <span class="delta-add">+40</span> <span class="delta-del">−16</span></span>
|
||||
<span class="spacer"></span>
|
||||
<button class="secondary btn-sm" hx-get="/deploy_modal/{{ repo }}/{{ v }}" hx-target="#modal">이 버전 배포</button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 8. 상태 조각들
|
||||
- **배포 완료 배너 `#flash`** (`.flash-banner`): 성공 체크 원(`.flash-check`, 그려지는 SVG 체크) +
|
||||
본문 + `.flash-sub` + `.flash-warn`(되돌리기 안내). 등장 시 페이드-업.
|
||||
- **데모 배너** (`.demo-banner`): 가짜 데이터 경고, 앰버 톤, 펄스 dot.
|
||||
- **연결 상태 칩** (`.status-line.status-ok|bad|loading|demo`): dot + 텍스트. ok는 펄스, loading은 blink.
|
||||
- **빈 상태** (`.empty-note` + `.empty-ico`/`.empty-title`/`.empty-sub`).
|
||||
- **에러 상태** (`.error-note`): 경고 아이콘 + 메시지, danger 톤.
|
||||
- **스켈레톤** (`.skeleton.skeleton-row`): 셔머 로딩. 표 초기/재조회 자리표시.
|
||||
- HTMX 스왑 중 페이드: `.htmx-swapping{opacity:.4}` 자동 적용(이미 CSS에 포함).
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens (라이트 / `:root`)
|
||||
색은 `app.css`의 `:root`(라이트)와 `[data-theme="dark"]`(다크) 두 곳에서 정의되고, 컴포넌트는
|
||||
전부 이 변수를 참조하므로 **토큰만 손보면 라이트/다크가 함께 따라옵니다.**
|
||||
|
||||
- 배경: `--bg #ffffff` · `--bg-alt #f9fafb` · `--surface #f2f4f6` · `--surface-2 #e8f3ff`(틴티드 블루)
|
||||
- 텍스트: `--text #191f28` · `--text-secondary #333d4b` · `--text-muted #6b7684` · `--text-dim #8b95a1`
|
||||
- 테두리: `--border #e5e8eb` · `--border-strong #d1d6db`
|
||||
- 액센트(Toss Blue를 살짝 톤다운): `--accent oklch(0.63 0.147 255)` · `--accent-hover oklch(0.55 0.16 255)`
|
||||
· `--accent-soft #e8f3ff`
|
||||
- 상태: `--success-text #00a37a` · `--warning-text #c5700a` · `--danger #f04452` (+ `*-soft` 반투명 배경)
|
||||
- 라운드: 카드 `--radius-card 12px`, 버튼 8px(`--pico-border-radius`)
|
||||
- 그림자: `--shadow-card`(얇게) · `--shadow-card-hover` · `--shadow-modal`(무겁게)
|
||||
- 모션: `--ease cubic-bezier(0.32,0.72,0,1)` · `--dur-fast .12s` · `--dur .2s` · `--dur-slow .32s`
|
||||
- 타이포: 본문 `Pretendard Variable`, 숫자/버전/시각/저장소명은 `JetBrains Mono`(+ `tnum` tabular).
|
||||
헤더 th는 한글 라벨이라 모노 제외.
|
||||
|
||||
**모든 애니메이션은 `prefers-reduced-motion: reduce`에서 자동 비활성**(파일 맨 끝 미디어쿼리).
|
||||
|
||||
## Interactions & Behavior
|
||||
- 버튼: hover 그림자, active `scale(0.97)`. `.is-loading`으로 스피너 교체.
|
||||
- 표: 행 hover 배경 전환, 정렬 화살표 회전, 검증 dot(pass)·연결 dot(ok) 은은한 펄스.
|
||||
- 진행률: 결정형 `<progress>` width 트랜지션, 초기엔 인디터미닛 바.
|
||||
- 모달: 배경 페이드 + 본문 scale/opacity 진입, 배경 스크롤 잠금(스크롤바 깜빡임 방지),
|
||||
타입-투-컨펌(이름 일치 시에만 확정 활성 + 초록 체크 / 불일치 시 빨강 X).
|
||||
- 성공 배너: 체크 SVG stroke-dash 그리기 애니메이션.
|
||||
|
||||
## 적용 순서 (Claude Code에게)
|
||||
1. 기존 `app/static/app.css`를 이 번들의 `app.css`로 교체.
|
||||
2. 위 "컴포넌트별 변경 + 필요 마크업"에 따라 각 Jinja2 템플릿에 새 요소(.card-top/.card-ico,
|
||||
.search-ico, .modal-head/.warn-ico/.pv-box/.confirm-state 등)를 추가. **HTMX 훅·id·data 속성은 그대로.**
|
||||
3. `preview.html`을 브라우저로 열어 렌더 기준과 대조(라이트/다크 토글).
|
||||
4. `PULP_DEMO=true`로 데모 데이터 띄워 표/검색/정렬/모달 실동작 확인.
|
||||
5. 회귀 확인: `pytest -q`(현행 58 통과), `ruff check app`.
|
||||
|
||||
## Files
|
||||
- `app.css` — 실제 산출물 (drop-in).
|
||||
- `preview.html` — 렌더 참고 하니스 (앱에 넣지 말 것).
|
||||
- `pico-shim.css` — 미리보기 전용 Pico 최소 흉내 (앱에 넣지 말 것).
|
||||
Reference in New Issue
Block a user