Files
voiceon-character-admin/DESIGN.md

15 KiB
Raw Blame History

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 / #086789 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.

TagInput

  • Structure: controlled comma-separated string boundary that renders committed values as visible wrapping chips and preserves an inline draft input. A visible label, help text, and error text accompany the field.
  • Controlled value: the caller owns one comma-separated string. Enter or a comma commits the non-empty draft into that string; each chip's native remove button removes only its value. Duplicate prevention and maximum-count limits are caller policy, not this pattern's policy.
  • States: default, hover, focus-visible, disabled, invalid, and empty-with-draft. The input boundary uses --input; focus-visible uses --ring; invalid text and boundary use --destructive; chip and helper surfaces use --muted, --accent, --accent-foreground, --foreground, and --muted-foreground only.
  • Responsive: chips wrap within the field at 375px, 768px, and 1280px without clipping or horizontal overflow. The inline draft remains usable after wrapped chips, Korean and CJK text may wrap naturally, and every input or remove target remains at least 44px.
  • Accessibility: label is always visible and associated with the inline input; help and error text are connected with aria-describedby. Native remove buttons expose the chip value in their accessible name, and focus-visible remains visible for the input and every remove button.
  • Motion: none.

SelectionCard

  • Structure: a native radio or checkbox remains in the DOM inside a full-card label, making the entire restrained card surface the hit target. The same grammar applies to purchase choices, preview, point, adult, comments, full-detail, and release mode.
  • States: default uses --card, --border, and --foreground; hover uses --accent and --accent-foreground; checked uses --accent, --accent-foreground, and --border; focus-visible uses --ring; disabled uses --muted and --muted-foreground; error uses --destructive with the visible error message. Control boundaries use --input and selected native-control accents use --accent with --accent-foreground only.
  • Responsive: cards wrap in a single column or available-width grid at 375px, 768px, and 1280px. Long Korean and CJK labels wrap without clipping, overflow, or shrinking the native control below its 44px target.
  • Accessibility: the native radio or checkbox keeps its normal keyboard and focus behavior and is never replaced with a custom control. The whole label activates the control, visible labels and error text state the choice and problem, and focus-visible is clear on the active card and native control.
  • Motion: none; hover and checked feedback are tonal and border state changes only.

ImageCropDialog

  • Structure: react-advanced-cropper viewport, expected result dimensions, and native reset, cancel, and apply actions form the modal crop surface; pending and error states remain visible while the result is prepared or cannot be generated.
  • Contract: the caller continues to supply the external aspect, maxWidth, and noUpscale policies; aspect: 'free' preserves the source image ratio, and apply produces the final File through the existing renderCrop seam. Domain profile names and GIF exceptions stay outside the primitive.
  • Accessibility: the dialog has a visible title and focus trap, Escape cancels it, and the viewport supports keyboard movement and zoom as well as pointer input. Reset, cancel, and apply are native button targets of at least 44px, so cropping is never pointer-only.
  • Motion: the cropper library owns interaction transitions inside its viewport; the surrounding dialog adds no decorative motion.

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.

SuccessNotification

  • Structure: one shell-level role="status" message after successful create, update, or deactivate navigation.
  • Variants: success only.
  • Tokens: --success-surface, --success, --border, --radius-lg, --space-3, --space-4.
  • Accessibility: aria-label="작업 성공"; no timer or dismiss control in P3-T3.
  • Motion: none.

AdminAudioPlayer

  • Reference: Plyr audio is the primary surface reference; Media Chrome audio supplies the explicit current/duration and playback-rate anatomy. Runtime extraction on 2026-08-03 found 4452px single-row bars, icon-only media controls, a flexible progress range, compact time/rate values, and no persistent descriptive labels.
  • Structure: native audio element wrapped by one compact control bar ordered play/pause → flexible seek → current/duration → playback rate → volume icon/range, followed by the existing generic error and manual retry block. No image, poster, video viewport, waveform, or settings menu is rendered.
  • Surface: card, border, input, primary, primary-foreground, and existing radius/spacing tokens only. The standard bar is approximately one 44px control high with 412px token spacing; remaining inline space goes to seek before volume.
  • Variants: shared signed-URL player only; it never downloads, autoplays, auto-refetches, or infers signed URL expiry.
  • Responsive: 375px, 768px, and 1280px standard viewports keep a single visual bar. Only constrained containers at 200% zoom may wrap secondary controls; clipping and horizontal overflow are forbidden.
  • Accessibility: player region is named by title, keyboard Space/Enter toggles play, seek/volume use range inputs, speed uses native select, and icon-only controls retain accessible names. Visible 볼륨 and 재생 속도 labels are omitted to match the compact reference while labels remain available to assistive technology.
  • 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.

CommentThread

  • Structure: two-level comment management surface with root comment form, root rows, direct reply expansion, reply form, and edit/delete row actions.
  • Variants: audio target includes languageCode; community target omits it. Replies never expose nested reply controls.
  • Accessibility: forms use visible labels, rows are named articles, reply lists are named regions, and mutation actions are native buttons.
  • Motion: none beyond existing control state color.

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.