feat(ai-character): 관리자 인증 셸 구현

This commit is contained in:
Yu Sung
2026-07-27 15:04:03 +09:00
parent ba43dd829e
commit 5356bd1e6a
82 changed files with 6017 additions and 18 deletions

196
DESIGN.md Normal file
View File

@@ -0,0 +1,196 @@
# AI Character Admin Design System
## 0. Research Log
- PRD baseline: adopted `10.1~10.9` as the visual contract because Task 1.1 is PRD-driven setup, not a new visual exploration.
- ui-ux-pro-max: ran `.opencode/skills/ui-ux-pro-max/scripts/search.py` with the PRD `10.9` design-system query. Adopted dense operational dashboard, subtle motion, status colors, focus visibility, reduced-motion, and no emoji icons. Excluded generated green palette, white on primary, dark mode support, Fira remote fonts, oversized landing typography, glass/continuous animation, and GSAP page transitions because they conflict with PRD `10.1~10.3`.
- ui-ux-pro-max UX: ran `.opencode/skills/ui-ux-pro-max/scripts/search.py "animation accessibility z-index loading" --domain ux -n 12`. Adopted loading feedback for waits over 300ms, semantic z-index scale, reduced motion, 150-300ms micro-interactions, and no decorative infinite animation. Deferred lazy-loaded media, loading buttons, skeletons, and route-level loading because Task 1.1 has no async page, media, form submit, or router surface yet.
- Existing UI: only the Phase 0 root shell exists, so this document defines the minimal system before new UI consumes it.
## 1. Atmosphere & Identity
A bright Korean operations console: dense, calm, and explicit. The signature is cyan as an operational signal, used for primary action, links, focus, and information states while surfaces stay quiet and readable.
## 2. Color
### Primitive Tokens
| Role | Token | Light | Usage |
|---|---|---:|---|
| Brand 50 | `--color-brand-50` | `#F0FBFF` | Faint emphasis background |
| Brand 100 | `--color-brand-100` | `#D9F6FF` | Selected row, accent surface |
| Brand 200 | `--color-brand-200` | `#B5EEFF` | Emphasis border |
| Brand 300 | `--color-brand-300` | `#7CE2FF` | Decorative low emphasis |
| Brand 400 | `--color-brand-400` | `#36D1FF` | Secondary accent |
| Brand 500 | `--color-brand-500` | `#00BDF7` | Fixed main color and primary background |
| Brand 600 | `--color-brand-600` | `#00A9DE` | Primary hover |
| Brand 700 | `--color-brand-700` | `#009DCE` | Primary active |
| Brand 800 | `--color-brand-800` | `#007EA8` | Link, focus ring, info |
| Brand 900 | `--color-brand-900` | `#086789` | Link hover |
| Brand 950 | `--color-brand-950` | `#063747` | Deep brand accent |
### Semantic Tokens
| Role | Token | Light | Usage |
|---|---|---:|---|
| Background | `--background` | `#F6FBFD` | Page background |
| Foreground | `--foreground` | `#102A33` | Main text |
| Card / Popover | `--card`, `--popover` | `#FFFFFF` | Surfaces and overlays |
| Muted | `--muted` | `#E9F4F7` | Muted surface |
| Muted foreground | `--muted-foreground` | `#425F69` | Secondary text |
| Secondary | `--secondary` | `#E1F5FA` | Secondary controls |
| Secondary foreground | `--secondary-foreground` | `#123E4B` | Text on secondary |
| Accent | `--accent` | `#D9F6FF` | Hover and selected surface |
| Accent foreground | `--accent-foreground` | `#0C566F` | Text on accent |
| Border | `--border` | `#D5E8EE` | Decorative separators |
| Input | `--input` | `#577581` | Required control boundary |
| Primary | `--primary` | `#00BDF7` | Main CTA |
| Primary foreground | `--primary-foreground` | `#062B36` | Text/icons on primary |
| Ring / Link | `--ring`, `--link` | `#007EA8` | Focus and link |
| Info | `--info` | `#086789` | Small informational labels |
| Success | `--success` | `#167347` | Open/success state |
| Success surface | `--success-surface` | `#EAF8F0` | Success Badge surface |
| Warning | `--warning` | `#9A5B00` | Scheduled/warning state |
| Warning surface | `--warning-surface` | `#FFF7E6` | Warning Badge surface |
| Destructive | `--destructive` | `#B42318` | Error/destructive state |
| Inactive | `--inactive` | `#52636A` | Inactive state |
| Inactive surface | `--inactive-surface` | `#EEF3F5` | Inactive Badge surface |
### Rules
- Feature components use semantic or component tokens, not raw hex.
- `#00BDF7` never uses white foreground; `--primary-foreground` is `#062B36`.
- Light theme only in this release. No `.dark`, theme provider, theme toggle, or system dark integration.
## 3. Typography
| Level | Size | Weight | Line Height | Usage |
|---|---:|---:|---:|---|
| Page title | `24px` | 700 | 1.3 | Main page title |
| Section title | `20px` | 650 | 1.4 | Section headers |
| Body | `14px` | 400 | 1.5 | Dense admin text and tables |
| Small | `13px` | 400 | 1.45 | Secondary metadata |
| Caption | `12px` | 600 | 1.4 | Labels and badges |
| Mobile input | `16px` | 400 | 1.5 | iOS-safe input text |
Primary font stack: `Pretendard`, `Noto Sans KR`, `Apple SD Gothic Neo`, `system-ui`, `sans-serif`.
## 4. Spacing & Layout
- Base spacing is an 8px grid, with 4px available only for tight icon-label gaps.
- Control target minimum is 44px.
- Initial shell remains simple: the document scrolls until Task 1.4 introduces the admin shell.
- Future desktop shell dimensions follow PRD: 240px sidebar and 56px header.
## 5. Components
### StatusBadge
- Structure: inline status container with a decorative dot and visible Korean text label.
- Variants: `OPEN`, `SCHEDULED`, `INACTIVE`.
- Spacing: `--space-1`, `--space-2`.
- States: static display only in Task 1.1.
- Accessibility: `aria-label="상태: {label}"`; color never carries status alone.
- Motion: none.
### IconOnlyAction
- Structure: button with icon slot, accessible name, and tooltip description.
- Variants: single icon-only control primitive.
- Spacing: 44px minimum target, centered icon.
- States: hover, active, focus-visible, disabled.
- Accessibility: `aria-label`, tooltip element, 3:1 control boundary and focus ring tokens. Tooltip text equal to the label is not wired as `aria-describedby` to avoid duplicate name/description.
- Motion: 150ms transform/color transition, disabled under reduced motion.
### PageState
- Structure: tokenized card surface for loading, empty, error, and content pass-through states.
- Variants: loading and empty use `role="status"`; error uses `role="alert"` and optional retry button.
- Accessibility: state meaning is visible Korean copy plus semantic role; retry is a native button.
- Motion: none.
### SearchToolbar
- Structure: controlled search input with optional filter slot inside a bordered card surface.
- Variants: generic query only; domain endpoint query names stay outside the primitive.
- Accessibility: search input has a visible label and mobile-safe input sizing.
- Motion: none.
### ResourcePagination
- Structure: `PageData` summary, page-size select, and native previous/next buttons.
- Variants: disabled previous/next derive from page bounds and `hasNext`.
- Accessibility: native controls expose Korean names and preserve keyboard behavior.
- Motion: none beyond control state color.
### ResponsiveResourceList
- Structure: desktop and mobile rendering slots inside one region; the primitive owns breakpoint visibility only.
- Variants: none; domain columns, DTOs, and action unions stay in consuming screens.
- Accessibility: region is named by the caller.
- Motion: none.
### ConfirmDeactivateDialog
- Structure: modal confirmation dialog with target name, impact copy, cancel, and destructive confirm action.
- Variants: confirmation only; never a switch replacement.
- Accessibility: `alertdialog`, visible title, native buttons.
- Motion: none.
### UnsavedChangesGuard
- Structure: render-prop guard for route-leave triggers that opens a confirmation dialog only while dirty.
- Variants: dirty blocks, safe/saved state lets the route callback run immediately.
- Accessibility: cancel keeps the user in context and returns focus to the trigger.
- Motion: none.
### FileField
- Structure: controlled `File | null` field with visible label, native file input, accept guidance, selected filename, and clear button.
- Variants: domain-neutral only; allowed extensions, MIME, and max bytes are injected by caller policy.
- Accessibility: label targets the input; description, accept guidance, and error are connected with `aria-describedby`; clear is a native button.
- Motion: none.
### ImageCropDialog
- Structure: modal crop surface with preview, output size, directional move buttons, zoom range, reset, cancel, and apply.
- Variants: caller injects `aspect`, `maxWidth`, and `noUpscale`; domain profile names and GIF exceptions stay outside the primitive.
- Accessibility: dialog has visible title, keyboard preview controls, range input, and button alternatives. No pointer-only requirement in Phase 1.6.
- Motion: transform-only preview adjustment. No crop dependency is added; Canvas is used only when generating the final `File`.
### UploadProgress
- Structure: status label, optional filename, progressbar, and optional cancel/retry buttons.
- Variants: display-only upload state; no upload client, request adapter, or domain form ownership.
- Accessibility: progressbar exposes `aria-valuenow`; cancel/retry are native buttons.
- Motion: none.
### AdminAudioPlayer
- Structure: native audio element wrapped with play/pause, seek, time, volume, speed, generic error, and manual retry controls.
- Variants: shared signed-URL player only; it never downloads, autoplays, auto-refetches, or infers signed URL expiry.
- Accessibility: player region is named by title, keyboard Space/Enter toggles play, seek/volume use range inputs, speed uses native select.
- Motion: none.
### AudioPlaybackProvider
- Structure: shared context coordinates active audio by player id so only one player continues at a time.
- Variants: stores only player ids in React state; signed URLs are not logged, persisted, or passed into the provider.
- Accessibility: no direct rendered surface.
- Motion: none.
## 6. Motion & Interaction
- Motion is limited to 150ms micro-interactions for real control state changes.
- Only `transform`, `opacity`, and color changes are used for Task 1.1 primitives.
- `prefers-reduced-motion: reduce` disables non-essential transitions and animations.
## 7. Depth & Surface
Strategy: mixed but restrained. Dense admin surfaces primarily use borders and tonal shifts; shadows are reserved for overlays in later phases.
## 8. Accessibility Constraints & Accepted Debt
- Target WCAG 2.2 AA: body text 4.5:1, control boundary/focus indicator 3:1, visible focus on every interactive element.
- Korean text must keep system fallbacks and inputs must stay at least 16px on mobile.
- Accepted debt: no component showcase route in Task 1.1 because the requested scope is token/base/component setup only; RTL tests exercise the primitive states.