197 lines
10 KiB
Markdown
197 lines
10 KiB
Markdown
# 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.
|