Files
Hyemin Lee 9b686e35ba 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>
2026-07-10 10:05:17 +09:00

238 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 최소 흉내 (앱에 넣지 말 것).