Revise design and UI components for AI video admin interface
Updated DESIGN.md to reflect a new editorial design approach with a navy palette and refined typography. Enhanced global styles in globals.css, including new color tokens and layout adjustments. Refactored components in AppShell, Sidebar, and Topbar for improved consistency and theming. Introduced a ThemeToggle component for user theme preferences and updated various pages to utilize new styles and components, ensuring a cohesive user experience across the application.
This commit is contained in:
538
DESIGN.md
538
DESIGN.md
@@ -1,99 +1,129 @@
|
||||
---
|
||||
version: alpha
|
||||
name: ai-video-admin-design
|
||||
description: A dark developer-console admin surface for managing AI video assistants. The base canvas is deep navy (`#080b13`) with cool off-white ink (`#e9eef7`); brand voltage comes from a cyan-to-blue gradient accent and soft radial glow blooms — not pastel orbs. Inter carries all UI type at 400–700 weights. CTAs use the shadcn primary blue pill; secondary actions are dark outlined panels on layered navy surfaces. The system reads as a focused ops dashboard, not a marketing site.
|
||||
description: A quietly editorial admin surface for managing AI video assistants, rendered in a navy palette with both a dark and a light theme. The dark canvas is deep navy (`#070b16`) holding off-white ink (`#f1f5ff`); the light canvas is a cool off-white (`#f3f5fb`) holding deep-navy ink (`#0c1426`). Brand voltage is photographic, not chromatic — soft pastel atmospheric gradient orbs (mint → peach → lavender → sky → rose) drift behind hero copy as the only "color" moments. Display runs Cormorant Garamond Light at weight 300 — the editorial signature; Inter carries body, navigation, captions. CTAs are subtle pills: a deep-navy ink pill in light, inverting to an off-white pill in dark. There is no neon accent and no saturated CTA color.
|
||||
|
||||
colors:
|
||||
primary: "#2563eb"
|
||||
primary-active: "#1d4ed8"
|
||||
accent-cyan: "#22d3ee"
|
||||
accent-blue: "#60a5fa"
|
||||
accent-blue-strong: "#3b82f6"
|
||||
ink: "#e9eef7"
|
||||
body: "#9aa6bd"
|
||||
body-sub: "#7f8aa3"
|
||||
muted: "#5d6880"
|
||||
hairline: "#161d2c"
|
||||
hairline-soft: "#1b2233"
|
||||
hairline-strong: "#2a3550"
|
||||
hairline-node: "#273249"
|
||||
canvas: "#080b13"
|
||||
canvas-sidebar: "#0a0e17"
|
||||
canvas-panel: "#0d121d"
|
||||
surface-card: "#0f1521"
|
||||
surface-hover: "#151e30"
|
||||
surface-node: "#111827"
|
||||
primary: "#1b2741"
|
||||
primary-active: "#0c1426"
|
||||
ink: "#0c1426"
|
||||
body: "#44516c"
|
||||
body-strong: "#1b2741"
|
||||
muted: "#5d6b86"
|
||||
muted-soft: "#94a0bd"
|
||||
hairline: "#e3e7f1"
|
||||
hairline-soft: "#eef1f8"
|
||||
hairline-strong: "#cbd3e4"
|
||||
canvas: "#f3f5fb"
|
||||
canvas-soft: "#f9fafd"
|
||||
surface-card: "#ffffff"
|
||||
surface-strong: "#e9edf7"
|
||||
on-primary: "#ffffff"
|
||||
on-accent-dark: "#04121a"
|
||||
on-dark: "#ffffff"
|
||||
gradient-cyan-start: "#22d3ee"
|
||||
gradient-blue-end: "#3b82f6"
|
||||
gradient-text-start: "#67e8f9"
|
||||
gradient-text-end: "#60a5fa"
|
||||
glow-blue: "rgba(46, 161, 255, 0.18)"
|
||||
glow-cyan: "rgba(34, 211, 238, 0.16)"
|
||||
stat-cyan: "#22d3ee"
|
||||
stat-blue: "#60a5fa"
|
||||
stat-violet: "#a78bfa"
|
||||
stat-emerald: "#34d399"
|
||||
gradient-mint: "#a7e5d3"
|
||||
gradient-peach: "#f4c5a8"
|
||||
gradient-lavender: "#c8b8e0"
|
||||
gradient-sky: "#a8c8e8"
|
||||
gradient-rose: "#e8b8c4"
|
||||
semantic-error: "#dc2626"
|
||||
semantic-success: "#16a34a"
|
||||
|
||||
colors-dark:
|
||||
primary: "#e8edf9"
|
||||
primary-foreground: "#0c1426"
|
||||
ink: "#f1f5ff"
|
||||
foreground: "#e8edf9"
|
||||
body: "#a6b2cb"
|
||||
muted: "#93a0bb"
|
||||
muted-soft: "#6c7a96"
|
||||
hairline: "#1b2740"
|
||||
hairline-soft: "#141d30"
|
||||
hairline-strong: "#283450"
|
||||
canvas: "#070b16"
|
||||
canvas-soft: "#0b1322"
|
||||
surface-card: "#0e1626"
|
||||
surface-strong: "#18233a"
|
||||
gradient-mint: "#5fae9b"
|
||||
gradient-peach: "#c08a6b"
|
||||
gradient-lavender: "#8a78ad"
|
||||
gradient-sky: "#5f86b8"
|
||||
gradient-rose: "#b07d8c"
|
||||
semantic-error: "#f87171"
|
||||
semantic-success: "#34d399"
|
||||
semantic-success: "#4ade80"
|
||||
|
||||
typography:
|
||||
display-mega:
|
||||
fontFamily: "'Cormorant Garamond', 'Times New Roman', serif"
|
||||
fontSize: clamp(2.5rem, 5vw, 4rem)
|
||||
fontWeight: 300
|
||||
lineHeight: 1.05
|
||||
letterSpacing: -0.03em
|
||||
display-xl:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 36px
|
||||
fontWeight: 700
|
||||
lineHeight: 1.1
|
||||
letterSpacing: -0.72px
|
||||
fontFamily: "'Cormorant Garamond', serif"
|
||||
fontSize: clamp(2rem, 4vw, 3rem)
|
||||
fontWeight: 300
|
||||
lineHeight: 1.08
|
||||
letterSpacing: -0.02em
|
||||
display-lg:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontFamily: "'Cormorant Garamond', serif"
|
||||
fontSize: 36px
|
||||
fontWeight: 300
|
||||
lineHeight: 1.17
|
||||
letterSpacing: -0.01em
|
||||
display-md:
|
||||
fontFamily: "'Cormorant Garamond', serif"
|
||||
fontSize: 32px
|
||||
fontWeight: 700
|
||||
lineHeight: 1.15
|
||||
letterSpacing: -0.64px
|
||||
fontWeight: 300
|
||||
lineHeight: 1.13
|
||||
letterSpacing: -0.01em
|
||||
display-sm:
|
||||
fontFamily: "'Cormorant Garamond', serif"
|
||||
fontSize: 24px
|
||||
fontWeight: 300
|
||||
lineHeight: 1.2
|
||||
letterSpacing: 0
|
||||
title-md:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 20px
|
||||
fontWeight: 600
|
||||
fontWeight: 500
|
||||
lineHeight: 1.35
|
||||
letterSpacing: 0
|
||||
title-sm:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 18px
|
||||
fontWeight: 600
|
||||
lineHeight: 1.4
|
||||
fontWeight: 500
|
||||
lineHeight: 1.44
|
||||
letterSpacing: 0
|
||||
body-md:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 15px
|
||||
fontSize: 16px
|
||||
fontWeight: 400
|
||||
lineHeight: 1.73
|
||||
letterSpacing: 0
|
||||
lineHeight: 1.5
|
||||
letterSpacing: 0.01em
|
||||
body-strong:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 15px
|
||||
fontWeight: 600
|
||||
lineHeight: 1.73
|
||||
letterSpacing: 0
|
||||
fontSize: 16px
|
||||
fontWeight: 500
|
||||
lineHeight: 1.5
|
||||
letterSpacing: 0.01em
|
||||
body-sm:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 15px
|
||||
fontWeight: 400
|
||||
lineHeight: 1.47
|
||||
letterSpacing: 0.01em
|
||||
caption:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 14px
|
||||
fontWeight: 400
|
||||
lineHeight: 1.5
|
||||
letterSpacing: 0
|
||||
caption:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 12px
|
||||
fontWeight: 400
|
||||
lineHeight: 1.4
|
||||
letterSpacing: 0
|
||||
caption-semibold:
|
||||
caption-label:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 12px
|
||||
fontWeight: 600
|
||||
lineHeight: 1.4
|
||||
letterSpacing: 0
|
||||
letterSpacing: 0.08em
|
||||
textTransform: uppercase
|
||||
button:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 14px
|
||||
@@ -106,21 +136,15 @@ typography:
|
||||
fontWeight: 400
|
||||
lineHeight: 1.4
|
||||
letterSpacing: 0
|
||||
brand-label:
|
||||
fontFamily: "'Inter', sans-serif"
|
||||
fontSize: 14px
|
||||
fontWeight: 700
|
||||
lineHeight: 1.4
|
||||
letterSpacing: 0
|
||||
|
||||
rounded:
|
||||
none: 0px
|
||||
sm: 6px
|
||||
md: 8px
|
||||
lg: 10px
|
||||
xl: 12px
|
||||
xxl: 16px
|
||||
xxxl: 24px
|
||||
sm: "calc(0.75rem * 0.6)" # ~7px
|
||||
md: "calc(0.75rem * 0.8)" # ~10px
|
||||
lg: 0.75rem # 12px (--radius)
|
||||
xl: "calc(0.75rem * 1.4)" # ~17px
|
||||
2xl: "calc(0.75rem * 1.8)" # ~22px
|
||||
3xl: "calc(0.75rem * 2.2)" # ~26px
|
||||
pill: 9999px
|
||||
full: 9999px
|
||||
|
||||
@@ -133,7 +157,7 @@ spacing:
|
||||
lg: 24px
|
||||
xl: 32px
|
||||
xxl: 48px
|
||||
section: 64px
|
||||
section: 96px
|
||||
|
||||
components:
|
||||
app-shell:
|
||||
@@ -141,7 +165,7 @@ components:
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.body-md}"
|
||||
sidebar:
|
||||
backgroundColor: "{colors.canvas-sidebar}"
|
||||
backgroundColor: "{colors.surface-card}"
|
||||
textColor: "{colors.body}"
|
||||
borderColor: "{colors.hairline}"
|
||||
width: 252px
|
||||
@@ -155,323 +179,265 @@ components:
|
||||
backgroundColor: "{colors.primary}"
|
||||
textColor: "{colors.on-primary}"
|
||||
typography: "{typography.button}"
|
||||
rounded: "{rounded.xl}"
|
||||
padding: 10px 16px
|
||||
rounded: "{rounded.pill}"
|
||||
height: 40px
|
||||
button-outline:
|
||||
backgroundColor: "{colors.surface-card}"
|
||||
textColor: "{colors.body}"
|
||||
backgroundColor: transparent
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.button}"
|
||||
rounded: "{rounded.xl}"
|
||||
borderColor: "{colors.hairline-soft}"
|
||||
padding: 10px 16px
|
||||
height: 40px
|
||||
button-accent-cyan:
|
||||
backgroundColor: "{colors.accent-cyan}"
|
||||
textColor: "{colors.on-accent-dark}"
|
||||
typography: "{typography.button}"
|
||||
rounded: "{rounded.xl}"
|
||||
padding: 10px 16px
|
||||
rounded: "{rounded.pill}"
|
||||
borderColor: "{colors.hairline-strong}"
|
||||
height: 40px
|
||||
nav-item:
|
||||
backgroundColor: transparent
|
||||
textColor: "{colors.body}"
|
||||
textColor: "{colors.muted}"
|
||||
typography: "{typography.nav-link}"
|
||||
rounded: "{rounded.xl}"
|
||||
rounded: "{rounded.pill}"
|
||||
height: 44px
|
||||
nav-item-active:
|
||||
backgroundColor: "rgba(59, 130, 246, 0.15)"
|
||||
textColor: "{colors.accent-blue}"
|
||||
backgroundColor: "{colors.surface-strong}"
|
||||
textColor: "{colors.ink}"
|
||||
hero-band:
|
||||
backgroundColor: "{colors.canvas-panel}"
|
||||
backgroundColor: "{colors.canvas-soft}"
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.display-xl}"
|
||||
rounded: "{rounded.xxxl}"
|
||||
padding: 32px
|
||||
stat-card:
|
||||
backgroundColor: "{colors.surface-card}"
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.body-md}"
|
||||
rounded: "{rounded.xxl}"
|
||||
padding: 20px
|
||||
rounded: "{rounded.3xl}"
|
||||
borderColor: "{colors.hairline}"
|
||||
padding: 64px
|
||||
feature-card:
|
||||
backgroundColor: "{colors.surface-card}"
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.title-sm}"
|
||||
rounded: "{rounded.xxl}"
|
||||
rounded: "{rounded.2xl}"
|
||||
borderColor: "{colors.hairline}"
|
||||
padding: 24px
|
||||
section-card:
|
||||
backgroundColor: "{colors.surface-card}"
|
||||
textColor: "{colors.ink}"
|
||||
rounded: "{rounded.2xl}"
|
||||
borderColor: "{colors.hairline}"
|
||||
padding: 24px
|
||||
icon-plate:
|
||||
backgroundColor: "{colors.surface-strong}"
|
||||
textColor: "{colors.ink}"
|
||||
rounded: "{rounded.full}"
|
||||
size: 40px
|
||||
text-input:
|
||||
backgroundColor: "{colors.canvas-panel}"
|
||||
textColor: "{colors.on-dark}"
|
||||
backgroundColor: "{colors.canvas}"
|
||||
textColor: "{colors.ink}"
|
||||
typography: "{typography.body-md}"
|
||||
rounded: "{rounded.lg}"
|
||||
padding: 12px 16px
|
||||
borderColor: "{colors.hairline-soft}"
|
||||
rounded: "{rounded.md}"
|
||||
borderColor: "{colors.hairline-strong}"
|
||||
badge-pill:
|
||||
backgroundColor: "rgba(34, 211, 238, 0.10)"
|
||||
textColor: "#67e8f9"
|
||||
typography: "{typography.caption-semibold}"
|
||||
backgroundColor: "{colors.surface-strong}"
|
||||
textColor: "{colors.muted}"
|
||||
typography: "{typography.caption-label}"
|
||||
rounded: "{rounded.pill}"
|
||||
padding: 4px 12px
|
||||
brand-icon:
|
||||
backgroundColor: "linear-gradient(135deg, {colors.gradient-cyan-start}, {colors.gradient-blue-end})"
|
||||
backgroundColor: "{colors.primary} + sky/lavender gradient orb overlay"
|
||||
textColor: "{colors.on-primary}"
|
||||
rounded: "{rounded.xxl}"
|
||||
rounded: "{rounded.xl}"
|
||||
size: 44px
|
||||
avatar:
|
||||
backgroundColor: "linear-gradient(135deg, {colors.gradient-cyan-start}, {colors.gradient-blue-end})"
|
||||
backgroundColor: "{colors.primary} + sky gradient orb overlay"
|
||||
textColor: "{colors.on-primary}"
|
||||
rounded: "{rounded.full}"
|
||||
size: 32px
|
||||
gradient-orb:
|
||||
background: "radial-gradient with one of {colors.gradient-*}"
|
||||
blur: 48-64px
|
||||
opacity: 0.45-0.6
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
AI 视频助手管理台 reads as a focused dark ops dashboard for configuring and monitoring AI video assistants. The base canvas is deep navy `{colors.canvas}` (#080b13) holding cool off-white ink `{colors.ink}` (#e9eef7). Brand voltage is **chromatic and directional**: a cyan-to-blue gradient accent (`{colors.gradient-cyan-start}` → `{colors.gradient-blue-end}`) marks logos, avatars, and primary CTAs; soft radial blue/cyan glow blooms add depth behind hero panels — not pastel atmospheric orbs.
|
||||
AI 视频助手管理台 reads like a quietly editorial print magazine that happens to be an admin console. It ships in **two themes built from one navy palette**:
|
||||
|
||||
Type runs **Inter** at 400–700 across all surfaces. Headings are bold (700), labels semibold (600), body at 400. There is no display serif — the voice is utilitarian and developer-console.
|
||||
- **Dark navy** (default): deep-navy canvas `{colors-dark.canvas}` (#070b16) holding off-white ink `{colors-dark.ink}` (#f1f5ff).
|
||||
- **Light navy**: cool off-white canvas `{colors.canvas}` (#f3f5fb) holding deep-navy ink `{colors.ink}` (#0c1426).
|
||||
|
||||
CTAs split into three tiers: shadcn primary blue pill (`{component.button-primary}`), dark outlined panel (`{component.button-outline}`), and cyan solid for workflow-mode emphasis (`{component.button-accent-cyan}`). Layered navy surfaces (`#0a0e17` → `#0f1521` → `#0d121d`) create depth without heavy shadows.
|
||||
The brand voltage is **photographic, not chromatic**: soft pastel atmospheric gradient orbs (mint, peach, lavender, sky, rose) drift behind hero/feature panels as the only "color" moments. There is no neon accent, no saturated CTA color, and no cyan/blue dev-console gradient anymore.
|
||||
|
||||
Type pairs **Cormorant Garamond Light** (display serif at weight 300 — the open-source substitute for Waldenburg) with **Inter** for body, navigation, captions, and buttons. The display weight at 300 is the editorial signature — never bold. Latin display runs in the serif; CJK display falls back to the system serif/sans at weight 300 (still light/editorial).
|
||||
|
||||
CTAs are subtle pills: in light mode the primary is a deep-navy ink pill (`{component.button-primary}`); in dark mode it inverts to an off-white pill (`{colors-dark.primary}` → off-white, text `{colors-dark.primary-foreground}`). The secondary is a transparent hairline-outline pill.
|
||||
|
||||
**Key Characteristics:**
|
||||
- Deep navy canvas, cool off-white ink. Blue/cyan gradient as brand accent.
|
||||
- Layered surface stack: shell → sidebar → card → panel/input.
|
||||
- Inter at 400–700 — no display serif, no weight-300 editorial voice.
|
||||
- Radial glow blooms (blue/cyan) behind hero and workflow panels only.
|
||||
- Rounded-xl (12px) for buttons and nav; rounded-2xl/3xl for cards and heroes.
|
||||
- 81px header/sidebar-brand height; 32px page padding.
|
||||
- One navy palette, two themes; dark is the default, persisted to `localStorage` with a no-flash bootstrap script in `layout.tsx`.
|
||||
- Off-white / deep-navy ink. No saturated CTA color — ink pill only.
|
||||
- Display runs Cormorant Garamond Light at weight 300 — editorial voice.
|
||||
- Body runs Inter at 400/500 with subtle +0.01em tracking.
|
||||
- Pastel gradient orbs (5 tokens) used as atmospheric decoration only.
|
||||
- Pill geometry (`{rounded.pill}`) for every CTA, nav item, and badge; `{rounded.2xl}` for cards, `{rounded.3xl}` for hero bands.
|
||||
- Hairline borders + a single soft shadow tier — no layered-surface dev-console stack.
|
||||
- 96px section rhythm; ~40px page padding; 1180px max content width.
|
||||
|
||||
## Colors
|
||||
|
||||
### Brand & Accent
|
||||
- **Primary** (`{colors.primary}` — #2563eb): shadcn primary blue — default CTA pill fill. Source: `oklch(0.55 0.22 255)`.
|
||||
- **Primary Active** (`{colors.primary-active}` — #1d4ed8): Press/hover darkening on primary.
|
||||
- **Accent Cyan** (`{colors.accent-cyan}` — #22d3ee): Workflow-mode CTA, status dots, glow accents. Tailwind `cyan-400`.
|
||||
- **Accent Blue** (`{colors.accent-blue}` — #60a5fa): Active nav text, labels, stat icons. Tailwind `blue-400`.
|
||||
- **Accent Blue Strong** (`{colors.accent-blue-strong}` — #3b82f6): Gradient end-stop, active states. Tailwind `blue-500`.
|
||||
The two `colors` / `colors-dark` blocks above are the source of truth and are wired into `globals.css` as the shadcn variables (`--background`, `--foreground`, `--card`, `--primary`, `--border`, `--muted-foreground`, …) plus editorial extras (`--ink`, `--body-text`, `--surface-strong`, `--hairline*`, `--canvas-soft`, `--gradient-*`). Tailwind utilities like `bg-background`, `text-ink`, `border-hairline`, `bg-surface-strong`, and `text-muted-foreground` resolve to the active theme automatically.
|
||||
|
||||
### Surface Stack (darkest → lightest)
|
||||
- **Canvas** (`{colors.canvas}` — #080b13): App shell floor, topbar background.
|
||||
- **Canvas Sidebar** (`{colors.canvas-sidebar}` — #0a0e17): Sidebar panel — one step lighter than shell.
|
||||
- **Canvas Panel** (`{colors.canvas-panel}` — #0d121d): Recessed inputs, toggle rows, hero base under glow.
|
||||
- **Surface Card** (`{colors.surface-card}` — #0f1521): Cards, outline buttons, dropdowns.
|
||||
- **Surface Hover** (`{colors.surface-hover}` — #151e30): Hover state for outline buttons and interactive panels.
|
||||
- **Surface Node** (`{colors.surface-node}` — #111827): Workflow step nodes.
|
||||
### Primary (CTA)
|
||||
- **Light** `{colors.primary}` (#1b2741): deep-navy ink pill, white text. Press → `{colors.primary-active}` (#0c1426).
|
||||
- **Dark** `{colors-dark.primary}` (#e8edf9): off-white pill, deep-navy text (`#0c1426`). The editorial inversion — used scarcely.
|
||||
|
||||
### Surfaces
|
||||
- **Canvas** — page floor. Light #f3f5fb / Dark #070b16.
|
||||
- **Canvas Soft** — hero/placeholder bands. Light #f9fafd / Dark #0b1322.
|
||||
- **Surface Card** — content cards, popovers. Light #ffffff / Dark #0e1626.
|
||||
- **Surface Strong** — icon plates, badges, active nav. Light #e9edf7 / Dark #18233a.
|
||||
|
||||
### Hairlines
|
||||
- **Hairline** (`{colors.hairline}` — #161d2c): Sidebar/topbar dividers, structural borders.
|
||||
- **Hairline Soft** (`{colors.hairline-soft}` — #1b2233): Default card, input, and button borders.
|
||||
- **Hairline Strong** (`{colors.hairline-strong}` — #2a3550): Hover border accent on cards and buttons.
|
||||
- **Hairline Node** (`{colors.hairline-node}` — #273249): Workflow node outlines.
|
||||
- **Hairline** — default 1px divider/card outline. Light #e3e7f1 / Dark #1b2740.
|
||||
- **Hairline Soft** — lighter divider. Light #eef1f8 / Dark #141d30.
|
||||
- **Hairline Strong** — input/outline-button border. Light #cbd3e4 / Dark #283450.
|
||||
|
||||
### Text
|
||||
- **Ink** (`{colors.ink}` — #e9eef7): Headings, primary labels, input text.
|
||||
- **Body** (`{colors.body}` — #9aa6bd): Nav links, descriptions, secondary copy.
|
||||
- **Body Sub** (`{colors.body-sub}` — #7f8aa3): Sub-navigation items.
|
||||
- **Muted** (`{colors.muted}` — #5d6880): Captions, placeholders, metadata.
|
||||
- **On Primary** (`{colors.on-primary}` — #ffffff): Text on blue/cyan buttons and gradient icons.
|
||||
- **On Accent Dark** (`{colors.on-accent-dark}` — #04121a): Text on solid cyan CTA buttons.
|
||||
- **Ink** — display & primary text. Light #0c1426 / Dark #f1f5ff.
|
||||
- **Body** — running copy. Light #44516c / Dark #a6b2cb.
|
||||
- **Muted** — descriptions, sub-labels. Light #5d6b86 / Dark #93a0bb.
|
||||
- **Muted Soft** — captions, placeholders. Light #94a0bd / Dark #6c7a96.
|
||||
|
||||
### Gradient & Glow (signature)
|
||||
- **Gradient Cyan Start** (`{colors.gradient-cyan-start}` — #22d3ee): Brand icon/avatar gradient start.
|
||||
- **Gradient Blue End** (`{colors.gradient-blue-end}` — #3b82f6): Brand icon/avatar gradient end.
|
||||
- **Gradient Text Start** (`{colors.gradient-text-start}` — #67e8f9): Brand wordmark gradient start (`cyan-300`).
|
||||
- **Gradient Text End** (`{colors.gradient-text-end}` — #60a5fa): Brand wordmark gradient end (`blue-400`).
|
||||
- **Glow Blue** (`{colors.glow-blue}` — rgba(46,161,255,0.18)): Hero panel radial bloom.
|
||||
- **Glow Cyan** (`{colors.glow-cyan}` — rgba(34,211,238,0.16)): Workflow panel radial bloom.
|
||||
|
||||
These appear as radial-gradient backgrounds behind hero/workflow sections and as box-shadow halos on status dots. Never as flat card fills.
|
||||
|
||||
### Stat Accents (dashboard metrics only)
|
||||
- **Stat Cyan** (`{colors.stat-cyan}` — #22d3ee): Assistant count icon plate.
|
||||
- **Stat Blue** (`{colors.stat-blue}` — #60a5fa): Session count icon plate.
|
||||
- **Stat Violet** (`{colors.stat-violet}` — #a78bfa): Response time icon plate.
|
||||
- **Stat Emerald** (`{colors.stat-emerald}` — #34d399): Resolution rate icon plate.
|
||||
|
||||
Each stat uses `{color}/10` opacity background with matching text color.
|
||||
### Atmospheric Gradient Orbs (signature)
|
||||
Five pastel stops — `gradient-mint`, `gradient-peach`, `gradient-lavender`, `gradient-sky`, `gradient-rose`. In dark mode they shift to lower-chroma navy-compatible variants (e.g. sky #a8c8e8 → #5f86b8). They appear ONLY as soft, blurred radial blooms behind hero/feature/placeholder copy and as the orb overlay on the brand icon/avatar. Never as button fills, text colors, or flat card surfaces.
|
||||
|
||||
### Semantic
|
||||
- **Success** (`{colors.semantic-success}` — #34d399): Trend badges, confirmation. Tailwind `emerald-400`.
|
||||
- **Error** (`{colors.semantic-error}` — #f87171): Validation errors. shadcn destructive light mode.
|
||||
|
||||
### shadcn CSS Variables (globals.css)
|
||||
|
||||
The project also defines semantic tokens in `:root` / `.dark` via oklch. Key mappings:
|
||||
|
||||
| Token | Light | Dark |
|
||||
|---|---|---|
|
||||
| `--background` | white | near-black |
|
||||
| `--foreground` | near-black | near-white |
|
||||
| `--primary` | `oklch(0.55 0.22 255)` | `oklch(0.6 0.22 255)` |
|
||||
| `--card` | white | `oklch(0.205 0 0)` |
|
||||
| `--border` | light gray | white 10% |
|
||||
| `--destructive` | red | lighter red |
|
||||
|
||||
Page components currently use hardcoded hex values above; shadcn tokens apply to base UI primitives (`Button`, `Select`, `Card` defaults).
|
||||
- **Success** — Light #16a34a / Dark #4ade80.
|
||||
- **Error** — Light #dc2626 / Dark #f87171.
|
||||
|
||||
## Typography
|
||||
|
||||
### Font Family
|
||||
**Inter** is the primary UI font (`--font-sans`). **Geist Sans** and **Geist Mono** are loaded as fallbacks/utilities. No display serif.
|
||||
**Cormorant Garamond** is the display serif (`--font-display`, weight 300) — the open-source substitute for the licensed Waldenburg Light. **Inter** carries body, navigation, captions, and buttons (`--font-sans`). **Geist Mono** is loaded for monospace. Helper classes in `globals.css`: `.font-display`, `.display-mega/-xl/-lg/-md/-sm`, `.caption-label`.
|
||||
|
||||
### Hierarchy
|
||||
|
||||
| Token | Size | Weight | Line Height | Use |
|
||||
| Token | Size | Weight | Tracking | Use |
|
||||
|---|---|---|---|---|
|
||||
| `{typography.display-xl}` | 36px | 700 | 1.1 | Page hero h1 |
|
||||
| `{typography.display-lg}` | 32px | 700 | 1.15 | Section heads |
|
||||
| `{typography.title-md}` | 20px | 600 | 1.35 | Card titles |
|
||||
| `{typography.title-sm}` | 18px | 600 | 1.4 | Sub-section heads |
|
||||
| `{typography.body-md}` | 15px | 400 | 1.73 | Default body |
|
||||
| `{typography.body-strong}` | 15px | 600 | 1.73 | Emphasized body |
|
||||
| `{typography.body-sm}` | 14px | 400 | 1.5 | Compact body |
|
||||
| `{typography.caption}` | 12px | 400 | 1.4 | Metadata |
|
||||
| `{typography.caption-semibold}` | 12px | 600 | 1.4 | Badges, trend pills |
|
||||
| `{typography.button}` | 14px | 500 | 1.0 | Button labels |
|
||||
| `{typography.nav-link}` | 14px | 400 | 1.4 | Sidebar nav |
|
||||
| `{typography.brand-label}` | 14px | 700 | 1.4 | Sidebar brand name |
|
||||
| `{typography.display-mega}` | clamp 40–64px | 300 | -0.03em | Marketing-scale hero |
|
||||
| `{typography.display-xl}` | clamp 32–48px | 300 | -0.02em | Homepage hero h1 |
|
||||
| `{typography.display-lg}` | 36px | 300 | -0.01em | Page titles |
|
||||
| `{typography.display-md}` | 32px | 300 | -0.01em | Section heads |
|
||||
| `{typography.display-sm}` | 24px | 300 | 0 | Card group titles |
|
||||
| `{typography.title-md}` | 20px | 500 | 0 | Component titles (Inter) |
|
||||
| `{typography.title-sm}` | 18px | 500 | 0 | List labels |
|
||||
| `{typography.body-md}` | 16px | 400 | +0.01em | Default body |
|
||||
| `{typography.body-strong}` | 16px | 500 | +0.01em | Emphasized body |
|
||||
| `{typography.body-sm}` | 15px | 400 | +0.01em | Compact body |
|
||||
| `{typography.caption-label}` | 12px | 600 | +0.08em, UPPER | Section labels, badges |
|
||||
| `{typography.button}` | 14px | 500 | 0 | CTA pill |
|
||||
| `{typography.nav-link}` | 14px | 400 | 0 | Sidebar nav |
|
||||
|
||||
### Principles
|
||||
- **Bold headings.** Page titles at 700 — this is an admin console, not editorial.
|
||||
- **Semibold for labels.** Form labels and card titles at 600.
|
||||
- **Tight tracking on display.** `-0.72px` on hero h1; body stays at 0 tracking.
|
||||
- **Display weight stays at 300.** Cormorant Garamond Light is the editorial signature. Never bold display copy.
|
||||
- **Subtle tracking on body.** Inter at +0.01em (applied globally on `body`).
|
||||
- **Negative tracking on display.** Tighter as size grows (-0.01em → -0.03em).
|
||||
- **Caption labels** are the editorial section marker — uppercase, 600, +0.08em.
|
||||
|
||||
## Layout
|
||||
|
||||
### Spacing System
|
||||
- **Base unit:** 4px.
|
||||
- **Tokens:** `{spacing.xxs}` 4px · `{spacing.xs}` 8px · `{spacing.sm}` 12px · `{spacing.base}` 16px · `{spacing.md}` 20px · `{spacing.lg}` 24px · `{spacing.xl}` 32px · `{spacing.xxl}` 48px · `{spacing.section}` 64px.
|
||||
- **Page padding:** 32px horizontal (`px-8`), 28px vertical (`py-7`).
|
||||
|
||||
### Grid & Container
|
||||
- Max content width: 1180px.
|
||||
- Stat cards: 4-up grid at desktop.
|
||||
### Spacing & Container
|
||||
- Base unit 4px; section rhythm 96px (`{spacing.section}`).
|
||||
- Page padding: `px-8 py-10`. Max content width 1180px.
|
||||
- Sidebar: 252px expanded, 76px collapsed.
|
||||
|
||||
### Whitespace Philosophy
|
||||
Compact ops-dashboard pacing. Cards sit 16–24px apart inside the main scroll area. Hero band gets generous internal padding (32px) with a radial glow occupying the top-left quadrant.
|
||||
Generous editorial pacing. Hero bands get large internal padding (64px) with a gradient orb in a corner; content cards sit 16–24px apart.
|
||||
|
||||
## Elevation & Depth
|
||||
|
||||
The system uses **layered navy surfaces + hairline borders**. Depth comes from surface color steps, not drop shadows.
|
||||
The system uses **hairline + soft drop**. Cards float above the canvas via a 1px hairline and a single subtle shadow tier (`shadow-sm`, deepening to `shadow-md` on hover). Atmospheric depth comes from the gradient orbs, not from layered surface steps.
|
||||
|
||||
| Level | Treatment | Use |
|
||||
|---|---|---|
|
||||
| Shell | `{colors.canvas}` (#080b13) | App background |
|
||||
| Sidebar | `{colors.canvas-sidebar}` (#0a0e17) | Navigation panel |
|
||||
| Card | `{colors.surface-card}` (#0f1521) | Content cards, buttons |
|
||||
| Recessed | `{colors.canvas-panel}` (#0d121d) | Inputs, toggle rows |
|
||||
| Hairline border | 1px `{colors.hairline-soft}` | Card/input outlines |
|
||||
| Hover border | 1px `{colors.hairline-strong}` | Interactive hover accent |
|
||||
| Radial glow | `{colors.glow-blue}` or `{colors.glow-cyan}` | Hero/workflow atmospheric depth |
|
||||
| Gradient shadow | `shadow-cyan-500/30` | Brand icon elevation |
|
||||
|
||||
### Decorative Depth
|
||||
- **Radial glow blooms** sit behind hero and workflow panels — blue for prompt mode, cyan for workflow mode.
|
||||
- **Status dots** use colored glow halos: `box-shadow: 0 0 0 4px rgba(...,.16), 0 0 14px rgba(...,.35)`.
|
||||
| Canvas | `{colors.canvas}` | App background, topbar |
|
||||
| Card | `{colors.surface-card}` | Content cards, popovers |
|
||||
| Hairline border | 1px `{colors.hairline}` | Card/input outlines |
|
||||
| Soft drop | `shadow-sm` → `shadow-md` on hover | Card elevation |
|
||||
| Gradient orb | blurred radial bloom, opacity 0.45–0.6 | Atmospheric depth only |
|
||||
|
||||
## Shapes
|
||||
|
||||
### Border Radius Scale
|
||||
### Border Radius Scale (`--radius: 0.75rem` = 12px)
|
||||
|
||||
| Token | Value | Use |
|
||||
| Token | Approx | Use |
|
||||
|---|---|---|
|
||||
| `{rounded.sm}` | 6px | Compact elements |
|
||||
| `{rounded.lg}` | 10px | shadcn base (`--radius: 0.625rem`) |
|
||||
| `{rounded.xl}` | 12px | Buttons, nav items, inputs |
|
||||
| `{rounded.xxl}` | 16px | Stat cards, brand icon |
|
||||
| `{rounded.xxxl}` | 24px | Hero bands, workflow panels |
|
||||
| `{rounded.pill}` | 9999px | Trend badges, status pills |
|
||||
| `{rounded.full}` | 9999px | Avatars |
|
||||
| `{rounded.md}` | ~10px | Form inputs |
|
||||
| `{rounded.lg}` | 12px | Compact elements |
|
||||
| `{rounded.xl}` | ~17px | Brand icon |
|
||||
| `{rounded.2xl}` | ~22px | Feature/section cards |
|
||||
| `{rounded.3xl}` | ~26px | Hero/placeholder bands |
|
||||
| `{rounded.pill}` | 9999px | All buttons, nav items, badges |
|
||||
| `{rounded.full}` | 9999px | Avatars, icon plates |
|
||||
|
||||
## Components
|
||||
|
||||
### App Shell
|
||||
|
||||
**`app-shell`** — Background `{colors.canvas}`, text `{colors.ink}`. Flex row: sidebar left, main column right (topbar + scrollable content).
|
||||
**`app-shell`** — `bg-background text-foreground`. Flex row: sidebar left, main column right (topbar + scrollable content at `px-8 py-10`).
|
||||
|
||||
### Sidebar
|
||||
|
||||
**`sidebar`** — Background `{colors.canvas-sidebar}`, border-right `{colors.hairline}`, width 252px (76px collapsed). Brand block at top with `{component.brand-icon}` + gradient text label. Nav items at `{component.nav-item}` height 44px; active state uses `{component.nav-item-active}`.
|
||||
**`sidebar`** — `bg-sidebar`, border-right `{colors.hairline}`, width 252/76px. Brand block: gradient-orb brand icon + serif wordmark + uppercase `管理台` caption-label. Nav items are pill-shaped (44px); active uses `bg-sidebar-accent` with foreground text.
|
||||
|
||||
### Top Bar
|
||||
**`topbar`** — `bg-background`, border-bottom hairline, height 81px. Right-aligned: Help outline pill, **theme toggle** (sun/moon), notification bell, user avatar pill — all hairline-outline style.
|
||||
|
||||
**`topbar`** — Background `{colors.canvas}`, border-bottom `{colors.hairline}`, height 81px. Right-aligned: Help button, notification bell, user avatar dropdown — all `{component.button-outline}` style.
|
||||
### Buttons (pills)
|
||||
**`button-primary`** — ink pill (light) / off-white pill (dark), `{rounded.pill}`, height 40px (`size="lg"`).
|
||||
**`button-outline`** — transparent pill, 1px `{colors.hairline-strong}` border, text ink; hover `bg-surface-strong`.
|
||||
|
||||
### Buttons
|
||||
### Hero & Atmospheric
|
||||
**`hero-band`** — `bg-canvas-soft`, `{rounded.3xl}`, hairline border, 64px padding, with 1–2 blurred gradient orbs (sky + lavender) behind editorial display copy, a caption-label eyebrow, and two CTAs.
|
||||
**`gradient-orb`** — blurred radial bloom in one of the five pastel tokens; pure atmosphere, holds no content.
|
||||
|
||||
**`button-primary`** — Blue pill. Background `{colors.primary}`, text `{colors.on-primary}`, rounded `{rounded.xl}`, height 40px.
|
||||
### Cards
|
||||
**`feature-card` / `section-card`** — `bg-card`, `{rounded.2xl}`, hairline border, `shadow-sm`. Icon plate is a 40px `{colors.surface-strong}` circle with neutral foreground icon (no colored accent).
|
||||
|
||||
**`button-outline`** — Dark panel with border. Background `{colors.surface-card}`, text `{colors.body}`, border `{colors.hairline-soft}`. Hover: background `{colors.surface-hover}`, text `{colors.ink}`, border `{colors.hairline-strong}`.
|
||||
|
||||
**`button-accent-cyan`** — Workflow-mode CTA. Background `{colors.accent-cyan}`, text `{colors.on-accent-dark}`, rounded `{rounded.xl}`.
|
||||
|
||||
### Hero & Panels
|
||||
|
||||
**`hero-band`** — Background `{colors.canvas-panel}` with `{colors.glow-blue}` radial gradient overlay. Rounded `{rounded.xxxl}`, border `{colors.hairline-soft}`, padding 32px. Contains brand icon, blue accent label, bold h1, body copy, and two CTAs.
|
||||
|
||||
**`stat-card`** — 4-up metric grid. Background `{colors.surface-card}`, rounded `{rounded.xxl}`, padding 20px. Icon plate uses stat accent colors at 10% opacity background.
|
||||
|
||||
### Forms
|
||||
|
||||
**`text-input`** — Background `{colors.canvas-panel}`, text white, placeholder `{colors.muted}`, border `{colors.hairline-soft}`, rounded `{rounded.xl}`.
|
||||
|
||||
**`badge-pill`** — Cyan-tinted pill for workflow status. Background cyan 10%, text cyan-300, rounded `{rounded.pill}`.
|
||||
### Forms & Tags
|
||||
**`text-input`** — `bg-background`, text ink, 1px `{colors.hairline-strong}` border, `{rounded.md}`; focus thickens border to ring.
|
||||
**`badge-pill`** — `bg-surface-strong`, caption-label text, `{rounded.pill}`.
|
||||
|
||||
### Brand Elements
|
||||
**`brand-icon`** / **`avatar`** — `{colors.primary}` base with a sky/lavender gradient-orb overlay, `{rounded.xl}` (icon) / `{rounded.full}` (avatar). The pastel orb replaces the old cyan→blue gradient.
|
||||
|
||||
**`brand-icon`** — 44×44px rounded `{rounded.xxl}` with `linear-gradient(135deg, cyan-400, blue-500)`, white icon, `shadow-cyan-500/30`, inset `ring-white/20`.
|
||||
## Theming
|
||||
|
||||
**`avatar`** — 32×32px circle with same gradient, bold initial letter.
|
||||
- Two themes from one palette: `.dark` toggles the variable set in `globals.css`. Dark is the default `<html className="dark">`.
|
||||
- A `ThemeToggle` (sun/moon) in the topbar flips `documentElement.classList` and writes `localStorage.theme`.
|
||||
- A no-flash inline script in `layout.tsx` applies the stored theme before paint (defaults to dark).
|
||||
|
||||
## Do's and Don'ts
|
||||
|
||||
### Do
|
||||
- Use the layered navy surface stack for depth — don't flatten everything to one background.
|
||||
- Use cyan-to-blue gradient for brand marks (logo, avatars) only.
|
||||
- Use `{colors.accent-blue}` at 15% opacity for active nav backgrounds.
|
||||
- Use `{rounded.xl}` for interactive controls; `{rounded.xxl}` / `{rounded.xxxl}` for containers.
|
||||
- Use Inter at 700 for page titles, 600 for labels, 400 for body.
|
||||
- Reserve `{colors.primary}` (ink/off-white pill) for primary CTAs.
|
||||
- Use Cormorant Garamond Light at weight 300 for every display headline. Never bold.
|
||||
- Use Inter at +0.01em tracking for body.
|
||||
- Use atmospheric gradient orbs (mint/peach/lavender/sky/rose) as decoration only.
|
||||
- Use the pill shape for every CTA, nav item, and badge.
|
||||
- Drive color from semantic tokens (`bg-card`, `text-ink`, `border-hairline`) so both themes work — never inline hex.
|
||||
|
||||
### Don't
|
||||
- Don't introduce warm/off-white editorial palettes — this is a dark admin console.
|
||||
- Don't use pastel gradient orbs — glows are blue/cyan radial blooms only.
|
||||
- Don't use a display serif — Inter only.
|
||||
- Don't use pill-shaped CTAs for primary actions — rounded-xl (12px) is the button geometry.
|
||||
- Don't use saturated stat colors as primary CTAs — reserve `{colors.primary}` and `{colors.accent-cyan}` for actions.
|
||||
- Don't reintroduce the cyan/blue dev-console accent or layered-navy surface stack.
|
||||
- Don't bold display copy — it sits at weight 300.
|
||||
- Don't use gradient orbs as button fills, text colors, or flat card backgrounds.
|
||||
- Don't use sharp 0px or `{rounded.xl}` corners on CTAs — pill geometry is the brand button.
|
||||
- Don't drop body Inter to weight 300 to match the serif — body stays at 400/500.
|
||||
|
||||
## Responsive Behavior
|
||||
|
||||
### Breakpoints
|
||||
|
||||
| Name | Width | Key Changes |
|
||||
|---|---|---|
|
||||
| Mobile | < 640px | Stat cards 1-up; sidebar collapsed; user name hidden |
|
||||
| Tablet | 640–1024px | Stat cards 2-up |
|
||||
| Desktop | 1024–1280px | Stat cards 4-up; full sidebar |
|
||||
| Mobile | < 640px | Feature grid 1-up; sidebar collapsed; user name hidden; display sizes shrink via clamp |
|
||||
| Tablet | 640–1024px | Feature grid 2-up |
|
||||
| Desktop | 1024–1280px | Feature grid 3-up; full sidebar |
|
||||
| Wide | > 1280px | Content caps at 1180px |
|
||||
|
||||
### Touch Targets
|
||||
- Nav items at 44px height.
|
||||
- Primary buttons at 40px height (`size="lg"`).
|
||||
|
||||
### Collapsing Strategy
|
||||
- Sidebar collapses to 76px icon-only via toggle button.
|
||||
- Stat grid: 4-up → 2-up → 1-up.
|
||||
|
||||
## Iteration Guide
|
||||
|
||||
1. Focus on a single component at a time.
|
||||
2. CTAs default to `{rounded.xl}`. Cards use `{rounded.xxl}` or `{rounded.xxxl}`.
|
||||
3. Variants live as separate entries.
|
||||
4. Use `{token.refs}` everywhere — never inline hex in new code (migrate existing hardcoded values over time).
|
||||
5. Hover states: surface step up + border accent to `{colors.hairline-strong}`.
|
||||
6. Inter 700 for display, 600 for labels, 400/500 for body and buttons.
|
||||
7. Glow blooms scoped to hero/workflow panels only.
|
||||
- Nav items 44px; primary buttons 40px (`size="lg"`).
|
||||
- Sidebar collapses to 76px icon-only via toggle.
|
||||
|
||||
## Known Gaps
|
||||
|
||||
- Page components use hardcoded hex values; shadcn CSS variables exist but aren't fully wired to page-level surfaces yet.
|
||||
- Light mode tokens exist in `:root` but the app renders dark-only (no `.dark` class toggle).
|
||||
- Animation timings (glow pulse, sidebar collapse) out of scope.
|
||||
- Form validation states beyond defaults not fully documented.
|
||||
- Cormorant Garamond covers Latin only; CJK display headings fall back to the system serif/sans at weight 300. A bundled light CJK serif (e.g. a Noto Serif SC weight) is not yet wired in.
|
||||
- Several inner pages (Components, History, Test, Workflow, Profile) currently use the shared `PlaceholderPage` editorial header and await real content.
|
||||
- Animation timings (orb drift, hero entrance, theme cross-fade) out of scope.
|
||||
- Form validation states beyond focus not yet documented.
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
--color-foreground: var(--foreground);
|
||||
--font-sans: var(--font-sans);
|
||||
--font-mono: var(--font-geist-mono);
|
||||
--font-heading: var(--font-sans);
|
||||
--font-display: var(--font-display);
|
||||
--font-heading: var(--font-display);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
@@ -39,6 +40,24 @@
|
||||
--color-popover: var(--popover);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-card: var(--card);
|
||||
|
||||
/* Editorial (ElevenLabs-derived) tokens — navy palette */
|
||||
--color-canvas-soft: var(--canvas-soft);
|
||||
--color-surface-strong: var(--surface-strong);
|
||||
--color-ink: var(--ink);
|
||||
--color-body: var(--body-text);
|
||||
--color-muted-soft: var(--muted-soft);
|
||||
--color-hairline: var(--hairline);
|
||||
--color-hairline-soft: var(--hairline-soft);
|
||||
--color-hairline-strong: var(--hairline-strong);
|
||||
--color-on-primary: var(--on-primary);
|
||||
--color-success: var(--success);
|
||||
--color-gradient-mint: var(--gradient-mint);
|
||||
--color-gradient-peach: var(--gradient-peach);
|
||||
--color-gradient-lavender: var(--gradient-lavender);
|
||||
--color-gradient-sky: var(--gradient-sky);
|
||||
--color-gradient-rose: var(--gradient-rose);
|
||||
|
||||
--radius-sm: calc(var(--radius) * 0.6);
|
||||
--radius-md: calc(var(--radius) * 0.8);
|
||||
--radius-lg: var(--radius);
|
||||
@@ -48,73 +67,114 @@
|
||||
--radius-4xl: calc(var(--radius) * 2.6);
|
||||
}
|
||||
|
||||
/* ---------- Light navy (editorial off-white, navy ink) ---------- */
|
||||
:root {
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.145 0 0);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.145 0 0);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.145 0 0);
|
||||
--primary: oklch(0.55 0.22 255);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.97 0 0);
|
||||
--secondary-foreground: oklch(0.205 0 0);
|
||||
--muted: oklch(0.97 0 0);
|
||||
--muted-foreground: oklch(0.556 0 0);
|
||||
--accent: oklch(0.97 0 0);
|
||||
--accent-foreground: oklch(0.205 0 0);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.922 0 0);
|
||||
--input: oklch(0.922 0 0);
|
||||
--ring: oklch(0.708 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--radius: 0.625rem;
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.145 0 0);
|
||||
--sidebar-primary: oklch(0.205 0 0);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.97 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||
--sidebar-border: oklch(0.922 0 0);
|
||||
--sidebar-ring: oklch(0.708 0 0);
|
||||
--background: #f3f5fb;
|
||||
--foreground: #0f1b33;
|
||||
--card: #ffffff;
|
||||
--card-foreground: #0f1b33;
|
||||
--popover: #ffffff;
|
||||
--popover-foreground: #0f1b33;
|
||||
--primary: #1b2741;
|
||||
--primary-foreground: #ffffff;
|
||||
--secondary: #e9edf7;
|
||||
--secondary-foreground: #1b2741;
|
||||
--muted: #eef1f8;
|
||||
--muted-foreground: #5d6b86;
|
||||
--accent: #e9edf7;
|
||||
--accent-foreground: #1b2741;
|
||||
--destructive: #dc2626;
|
||||
--border: #dfe4f0;
|
||||
--input: #cbd3e4;
|
||||
--ring: #94a0bd;
|
||||
--chart-1: #1b2741;
|
||||
--chart-2: #3a4a6b;
|
||||
--chart-3: #5d6b86;
|
||||
--chart-4: #94a0bd;
|
||||
--chart-5: #c8d0e2;
|
||||
--radius: 0.75rem;
|
||||
|
||||
/* Editorial tokens */
|
||||
--canvas-soft: #f9fafd;
|
||||
--surface-strong: #e9edf7;
|
||||
--ink: #0c1426;
|
||||
--body-text: #44516c;
|
||||
--muted-soft: #94a0bd;
|
||||
--hairline: #e3e7f1;
|
||||
--hairline-soft: #eef1f8;
|
||||
--hairline-strong: #cbd3e4;
|
||||
--on-primary: #ffffff;
|
||||
--success: #16a34a;
|
||||
|
||||
/* Atmospheric pastel orbs (cool-leaning for navy) */
|
||||
--gradient-mint: #a7e5d3;
|
||||
--gradient-peach: #f4c5a8;
|
||||
--gradient-lavender: #c8b8e0;
|
||||
--gradient-sky: #a8c8e8;
|
||||
--gradient-rose: #e8b8c4;
|
||||
|
||||
--sidebar: #ffffff;
|
||||
--sidebar-foreground: #0f1b33;
|
||||
--sidebar-primary: #1b2741;
|
||||
--sidebar-primary-foreground: #ffffff;
|
||||
--sidebar-accent: #eef1f8;
|
||||
--sidebar-accent-foreground: #1b2741;
|
||||
--sidebar-border: #e3e7f1;
|
||||
--sidebar-ring: #94a0bd;
|
||||
}
|
||||
|
||||
/* ---------- Dark navy (deep navy canvas, off-white ink) ---------- */
|
||||
.dark {
|
||||
--background: oklch(0.145 0 0);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.205 0 0);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.205 0 0);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.6 0.22 255);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.269 0 0);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.269 0 0);
|
||||
--muted-foreground: oklch(0.708 0 0);
|
||||
--accent: oklch(0.269 0 0);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.556 0 0);
|
||||
--chart-1: oklch(0.87 0 0);
|
||||
--chart-2: oklch(0.556 0 0);
|
||||
--chart-3: oklch(0.439 0 0);
|
||||
--chart-4: oklch(0.371 0 0);
|
||||
--chart-5: oklch(0.269 0 0);
|
||||
--sidebar: oklch(0.205 0 0);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.269 0 0);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.556 0 0);
|
||||
--background: #070b16;
|
||||
--foreground: #e8edf9;
|
||||
--card: #0e1626;
|
||||
--card-foreground: #e8edf9;
|
||||
--popover: #0e1626;
|
||||
--popover-foreground: #e8edf9;
|
||||
--primary: #e8edf9;
|
||||
--primary-foreground: #0c1426;
|
||||
--secondary: #18233a;
|
||||
--secondary-foreground: #e8edf9;
|
||||
--muted: #141d30;
|
||||
--muted-foreground: #93a0bb;
|
||||
--accent: #18233a;
|
||||
--accent-foreground: #e8edf9;
|
||||
--destructive: #f87171;
|
||||
--border: #1b2740;
|
||||
--input: #283450;
|
||||
--ring: #4a5876;
|
||||
--chart-1: #e8edf9;
|
||||
--chart-2: #c8d0e2;
|
||||
--chart-3: #93a0bb;
|
||||
--chart-4: #5d6b86;
|
||||
--chart-5: #2a3654;
|
||||
|
||||
/* Editorial tokens */
|
||||
--canvas-soft: #0b1322;
|
||||
--surface-strong: #18233a;
|
||||
--ink: #f1f5ff;
|
||||
--body-text: #a6b2cb;
|
||||
--muted-soft: #6c7a96;
|
||||
--hairline: #1b2740;
|
||||
--hairline-soft: #141d30;
|
||||
--hairline-strong: #283450;
|
||||
--on-primary: #0c1426;
|
||||
--success: #4ade80;
|
||||
|
||||
--gradient-mint: #5fae9b;
|
||||
--gradient-peach: #c08a6b;
|
||||
--gradient-lavender: #8a78ad;
|
||||
--gradient-sky: #5f86b8;
|
||||
--gradient-rose: #b07d8c;
|
||||
|
||||
--sidebar: #0a111e;
|
||||
--sidebar-foreground: #e8edf9;
|
||||
--sidebar-primary: #e8edf9;
|
||||
--sidebar-primary-foreground: #0c1426;
|
||||
--sidebar-accent: #18233a;
|
||||
--sidebar-accent-foreground: #e8edf9;
|
||||
--sidebar-border: #1b2740;
|
||||
--sidebar-ring: #4a5876;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -123,8 +183,52 @@
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
html {
|
||||
@apply font-sans;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
/* Waldenburg Light substitute — EB Garamond at weight 300. The editorial
|
||||
display signature: serif, light, tightly tracked. Never bold. */
|
||||
.font-display {
|
||||
font-family: var(--font-display), "Times New Roman", serif;
|
||||
font-weight: 300;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.display-mega {
|
||||
font-size: clamp(2.5rem, 5vw, 4rem);
|
||||
line-height: 1.05;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.display-xl {
|
||||
font-size: clamp(2rem, 4vw, 3rem);
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
.display-lg {
|
||||
font-size: 2.25rem;
|
||||
line-height: 1.17;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.display-md {
|
||||
font-size: 2rem;
|
||||
line-height: 1.13;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.display-sm {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
/* Caption label — uppercase, tracked, the editorial section marker */
|
||||
.caption-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.4;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono, Inter } from "next/font/google";
|
||||
import { Geist_Mono, Inter, Cormorant_Garamond } from "next/font/google";
|
||||
import "./globals.css";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const inter = Inter({subsets:['latin'],variable:'--font-sans'});
|
||||
const inter = Inter({ subsets: ["latin"], variable: "--font-sans" });
|
||||
|
||||
const geistSans = Geist({
|
||||
variable: "--font-geist-sans",
|
||||
// Waldenburg Light is licensed; Cormorant Garamond at weight 300 is a close
|
||||
// open-source substitute for the light editorial display voice.
|
||||
const display = Cormorant_Garamond({
|
||||
subsets: ["latin"],
|
||||
weight: ["300", "400", "500"],
|
||||
variable: "--font-display",
|
||||
});
|
||||
|
||||
const geistMono = Geist_Mono({
|
||||
@@ -16,10 +19,13 @@ const geistMono = Geist_Mono({
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Create Next App",
|
||||
description: "Generated by create next app",
|
||||
title: "AI 视频助手 · 管理台",
|
||||
description: "创建、配置、测试并发布 AI 视频助手",
|
||||
};
|
||||
|
||||
// Apply the persisted theme before paint to avoid a flash. Defaults to dark navy.
|
||||
const themeScript = `(function(){try{var t=localStorage.getItem('theme');if(t==='light'){document.documentElement.classList.remove('dark')}else{document.documentElement.classList.add('dark')}}catch(e){document.documentElement.classList.add('dark')}})();`;
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -27,9 +33,21 @@ export default function RootLayout({
|
||||
}>) {
|
||||
return (
|
||||
<html
|
||||
lang="en"
|
||||
className={cn("h-full", "antialiased", geistSans.variable, geistMono.variable, "font-sans", inter.variable)}
|
||||
lang="zh-CN"
|
||||
className={cn(
|
||||
"h-full",
|
||||
"antialiased",
|
||||
"dark",
|
||||
geistMono.variable,
|
||||
"font-sans",
|
||||
inter.variable,
|
||||
display.variable,
|
||||
)}
|
||||
suppressHydrationWarning
|
||||
>
|
||||
<head>
|
||||
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
|
||||
</head>
|
||||
<body className="min-h-full flex flex-col">{children}</body>
|
||||
</html>
|
||||
);
|
||||
|
||||
@@ -28,7 +28,7 @@ export function AppShell() {
|
||||
const [collapsed, setCollapsed] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="flex h-screen overflow-hidden bg-[#080b13] text-[#e9eef7]">
|
||||
<div className="flex h-screen overflow-hidden bg-background text-foreground">
|
||||
<Sidebar
|
||||
active={active}
|
||||
collapsed={collapsed}
|
||||
@@ -39,7 +39,7 @@ export function AppShell() {
|
||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||||
<Topbar />
|
||||
|
||||
<div className="flex-1 overflow-y-auto px-8 py-7">
|
||||
<div className="flex-1 overflow-y-auto px-8 py-10">
|
||||
{active === "home" && <HomePage onNavigate={setActive} />}
|
||||
|
||||
{active === "assistant-prompt" && <AssistantPage />}
|
||||
|
||||
@@ -55,27 +55,33 @@ export function Sidebar({
|
||||
return (
|
||||
<aside
|
||||
className={[
|
||||
"flex shrink-0 flex-col border-r border-[#161d2c] bg-[#0a0e17] transition-all",
|
||||
"flex shrink-0 flex-col border-r border-sidebar-border bg-sidebar transition-all",
|
||||
collapsed ? "w-[76px]" : "w-[252px]",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className="flex h-[81px] items-center gap-3 border-b border-[#161d2c] px-5">
|
||||
<div className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-cyan-400 to-blue-500 text-white shadow-lg shadow-cyan-500/30">
|
||||
<Video size={22} />
|
||||
<div className="absolute inset-0 rounded-2xl ring-1 ring-inset ring-white/20" />
|
||||
<div className="flex h-[81px] items-center gap-3 border-b border-sidebar-border px-5">
|
||||
<div
|
||||
className="relative flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl text-on-primary shadow-sm"
|
||||
style={{
|
||||
backgroundColor: "var(--primary)",
|
||||
backgroundImage:
|
||||
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%), radial-gradient(circle at 80% 90%, color-mix(in srgb, var(--gradient-lavender) 65%, transparent), transparent 55%)",
|
||||
}}
|
||||
>
|
||||
<Video size={22} style={{ color: "var(--primary-foreground)" }} />
|
||||
</div>
|
||||
|
||||
{!collapsed && (
|
||||
<div>
|
||||
<div className="bg-gradient-to-r from-cyan-300 to-blue-400 bg-clip-text text-sm font-bold text-transparent">
|
||||
<div className="font-display text-base text-foreground">
|
||||
AI 视频助手
|
||||
</div>
|
||||
<div className="text-xs text-[#5d6880]">管理台</div>
|
||||
<div className="caption-label text-muted-soft">管理台</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="flex-1 space-y-2 px-3 py-4">
|
||||
<nav className="flex-1 space-y-1 px-3 py-5">
|
||||
<NavButton
|
||||
active={active === "home"}
|
||||
collapsed={collapsed}
|
||||
@@ -84,55 +90,51 @@ export function Sidebar({
|
||||
onClick={() => onNavigate("home")}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div className="pt-2">
|
||||
<div
|
||||
className={[
|
||||
"flex h-11 w-full items-center gap-3 rounded-xl px-3 text-sm",
|
||||
assistantActive
|
||||
? "bg-blue-500/10 text-blue-300"
|
||||
: "text-[#9aa6bd]",
|
||||
"flex h-11 w-full items-center gap-3 rounded-full px-3 text-sm",
|
||||
assistantActive ? "text-foreground" : "text-muted-foreground",
|
||||
collapsed ? "justify-center" : "",
|
||||
].join(" ")}
|
||||
>
|
||||
<Bot size={18} />
|
||||
{!collapsed && <span>创建助手</span>}
|
||||
{!collapsed && <span className="font-medium">创建助手</span>}
|
||||
</div>
|
||||
|
||||
<div
|
||||
className={[
|
||||
"mt-1 space-y-1",
|
||||
collapsed ? "pl-0" : "pl-6",
|
||||
].join(" ")}
|
||||
>
|
||||
<div className={["mt-1 space-y-1", collapsed ? "pl-0" : "pl-5"].join(" ")}>
|
||||
{assistantSubItems.map((item) => (
|
||||
<SubNavButton
|
||||
<NavButton
|
||||
key={item.key}
|
||||
active={active === item.key}
|
||||
collapsed={collapsed}
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
onClick={() => onNavigate(item.key)}
|
||||
small
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{mainItems.slice(1).map((item) => (
|
||||
<NavButton
|
||||
key={item.key}
|
||||
active={active === item.key}
|
||||
collapsed={collapsed}
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
onClick={() => onNavigate(item.key)}
|
||||
/>
|
||||
))}
|
||||
<div className="pt-2">
|
||||
{mainItems.slice(1).map((item) => (
|
||||
<NavButton
|
||||
key={item.key}
|
||||
active={active === item.key}
|
||||
collapsed={collapsed}
|
||||
icon={item.icon}
|
||||
label={item.label}
|
||||
onClick={() => onNavigate(item.key)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div className="border-t border-[#161d2c] p-3">
|
||||
<div className="border-t border-sidebar-border p-3">
|
||||
<button
|
||||
onClick={onToggle}
|
||||
className="flex h-10 w-full items-center justify-center rounded-xl border border-[#1b2233] bg-[#0f1521] text-[#9aa6bd] hover:text-white"
|
||||
className="flex h-10 w-full items-center justify-center rounded-full border border-hairline-strong text-muted-foreground transition-colors hover:bg-surface-strong hover:text-foreground"
|
||||
>
|
||||
<ChevronLeft
|
||||
size={18}
|
||||
@@ -150,58 +152,30 @@ function NavButton({
|
||||
icon: Icon,
|
||||
label,
|
||||
onClick,
|
||||
small = false,
|
||||
}: {
|
||||
active: boolean;
|
||||
collapsed: boolean;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
small?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
title={collapsed ? label : undefined}
|
||||
className={[
|
||||
"flex h-11 w-full items-center gap-3 rounded-xl px-3 text-sm transition",
|
||||
"mt-1 flex w-full items-center gap-3 rounded-full px-3 text-sm transition-colors",
|
||||
small ? "h-10" : "h-11",
|
||||
active
|
||||
? "bg-blue-500/15 text-blue-400"
|
||||
: "text-[#9aa6bd] hover:bg-white/5 hover:text-white",
|
||||
? "bg-sidebar-accent text-sidebar-accent-foreground font-medium"
|
||||
: "text-muted-foreground hover:bg-sidebar-accent/60 hover:text-foreground",
|
||||
collapsed ? "justify-center" : "",
|
||||
].join(" ")}
|
||||
>
|
||||
<Icon size={18} />
|
||||
<Icon size={small ? 16 : 18} />
|
||||
{!collapsed && <span>{label}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function SubNavButton({
|
||||
active,
|
||||
collapsed,
|
||||
icon: Icon,
|
||||
label,
|
||||
onClick,
|
||||
}: {
|
||||
active: boolean;
|
||||
collapsed: boolean;
|
||||
icon: React.ComponentType<{ size?: number }>;
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
title={collapsed ? label : undefined}
|
||||
className={[
|
||||
"flex h-10 w-full items-center gap-3 rounded-xl px-3 text-sm transition",
|
||||
active
|
||||
? "bg-blue-500/15 text-blue-400"
|
||||
: "text-[#7f8aa3] hover:bg-white/5 hover:text-white",
|
||||
collapsed ? "justify-center" : "",
|
||||
].join(" ")}
|
||||
>
|
||||
<Icon size={16} />
|
||||
{!collapsed && <span>{label}</span>}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
34
src/components/layout/ThemeToggle.tsx
Normal file
34
src/components/layout/ThemeToggle.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
|
||||
export function ThemeToggle() {
|
||||
const [dark, setDark] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
setDark(document.documentElement.classList.contains("dark"));
|
||||
}, []);
|
||||
|
||||
function toggle() {
|
||||
const next = !dark;
|
||||
setDark(next);
|
||||
document.documentElement.classList.toggle("dark", next);
|
||||
try {
|
||||
localStorage.setItem("theme", next ? "dark" : "light");
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
onClick={toggle}
|
||||
title={dark ? "切换到浅色" : "切换到深色"}
|
||||
aria-label="切换主题"
|
||||
className="flex size-10 items-center justify-center rounded-full border border-hairline-strong text-muted-foreground transition-colors hover:bg-surface-strong hover:text-foreground"
|
||||
>
|
||||
{dark ? <Sun size={17} /> : <Moon size={17} />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Bell, HelpCircle, ChevronDown } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ThemeToggle } from "./ThemeToggle";
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
@@ -9,43 +10,51 @@ import {
|
||||
|
||||
export function Topbar() {
|
||||
return (
|
||||
<header className="flex h-[81px] shrink-0 items-center justify-end border-b border-[#161d2c] bg-[#080b13] px-8">
|
||||
<header className="flex h-[81px] shrink-0 items-center justify-end gap-2 border-b border-border bg-background px-8">
|
||||
<TooltipProvider>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="rounded-xl border-[#1b2233] bg-[#0f1521] text-[#9aa6bd] hover:bg-[#151e30] hover:text-white hover:border-[#2a3550] transition-colors"
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="border-hairline-strong text-muted-foreground hover:bg-surface-strong hover:text-foreground"
|
||||
>
|
||||
<HelpCircle />
|
||||
帮助
|
||||
</Button>
|
||||
|
||||
<ThemeToggle />
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-lg"
|
||||
className="border-hairline-strong text-muted-foreground hover:bg-surface-strong hover:text-foreground"
|
||||
>
|
||||
<Bell />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">通知</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<button className="flex items-center gap-2.5 rounded-full border border-hairline-strong px-3 py-1.5 transition-colors hover:bg-surface-strong">
|
||||
<div
|
||||
className="flex h-8 w-8 items-center justify-center rounded-full text-sm font-medium text-on-primary"
|
||||
style={{
|
||||
backgroundColor: "var(--primary)",
|
||||
backgroundImage:
|
||||
"radial-gradient(circle at 30% 20%, color-mix(in srgb, var(--gradient-sky) 70%, transparent), transparent 60%)",
|
||||
color: "var(--primary-foreground)",
|
||||
}}
|
||||
>
|
||||
<HelpCircle />
|
||||
帮助
|
||||
</Button>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon-lg"
|
||||
className="rounded-xl border-[#1b2233] bg-[#0f1521] text-[#9aa6bd] hover:bg-[#151e30] hover:text-white hover:border-[#2a3550] transition-colors"
|
||||
>
|
||||
<Bell />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom">通知</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<button className="flex items-center gap-2.5 rounded-xl border border-[#1b2233] bg-[#0f1521] px-3 py-2 transition-colors hover:bg-[#151e30] hover:border-[#2a3550]">
|
||||
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-gradient-to-br from-cyan-400 to-blue-500 text-sm font-bold text-white shadow-lg shadow-cyan-500/25">
|
||||
A
|
||||
</div>
|
||||
<div className="hidden text-sm md:block text-left">
|
||||
<div className="font-semibold text-[#e9eef7]">管理员</div>
|
||||
<div className="text-xs text-[#5d6880]">admin</div>
|
||||
</div>
|
||||
<ChevronDown size={14} className="hidden md:block text-[#5d6880]" />
|
||||
</button>
|
||||
</div>
|
||||
A
|
||||
</div>
|
||||
<div className="hidden text-sm md:block text-left">
|
||||
<div className="font-medium text-foreground">管理员</div>
|
||||
<div className="text-xs text-muted-soft">admin</div>
|
||||
</div>
|
||||
<ChevronDown size={14} className="hidden md:block text-muted-soft" />
|
||||
</button>
|
||||
</TooltipProvider>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,28 +66,31 @@ export function AssistantPage() {
|
||||
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex items-start justify-between gap-6">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-3 w-3 rounded-full bg-blue-400 shadow-[0_0_0_4px_rgba(46,161,255,.16),0_0_14px_rgba(46,161,255,.35)]" />
|
||||
<h1 className="text-3xl font-bold">创建助手 · 提示词模式</h1>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-[#5d6880]">
|
||||
<div className="caption-label text-muted-soft">提示词模式</div>
|
||||
<h1 className="font-display display-lg mt-3 text-ink">
|
||||
创建助手
|
||||
</h1>
|
||||
<p className="mt-3 text-[15px] text-muted-foreground">
|
||||
通过提示词、模型、语音和知识库快速创建 AI 视频助手
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3">
|
||||
<Button variant="outline" className="gap-2 border-[#1b2233] bg-[#0f1521] text-[#9aa6bd] hover:bg-[#1b2233] hover:text-[#e9eef7]">
|
||||
<div className="flex shrink-0 gap-3">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
|
||||
>
|
||||
<Save size={16} />
|
||||
保存草稿
|
||||
</Button>
|
||||
<Button className="gap-2">
|
||||
<Button size="lg" className="gap-2">
|
||||
<Rocket size={16} />
|
||||
创建助手
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="space-y-5">
|
||||
@@ -192,16 +195,16 @@ function SectionCard({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<Card className="border-[#1b2233] bg-[#0f1521] text-[#e9eef7]">
|
||||
<Card className="rounded-2xl border-hairline bg-card text-card-foreground shadow-sm">
|
||||
<CardHeader>
|
||||
<div className="flex items-start gap-3">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-xl bg-blue-500/10 text-blue-400">
|
||||
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<CardTitle className="text-base">{title}</CardTitle>
|
||||
<CardDescription className="mt-1 text-[#5d6880]">
|
||||
<CardTitle className="text-base font-medium">{title}</CardTitle>
|
||||
<CardDescription className="mt-1 text-muted-foreground">
|
||||
{description}
|
||||
</CardDescription>
|
||||
</div>
|
||||
@@ -226,12 +229,12 @@ function TextField({
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<div className="mb-2 text-sm font-medium text-[#9aa6bd]">{label}</div>
|
||||
<div className="mb-2 text-sm font-medium text-foreground">{label}</div>
|
||||
<Input
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
className="border-[#1b2233] bg-[#0d121d] text-white placeholder:text-[#5d6880]"
|
||||
className="border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
@@ -252,13 +255,13 @@ function TextAreaField({
|
||||
}) {
|
||||
return (
|
||||
<label className="block">
|
||||
<div className="mb-2 text-sm font-medium text-[#9aa6bd]">{label}</div>
|
||||
<div className="mb-2 text-sm font-medium text-foreground">{label}</div>
|
||||
<Textarea
|
||||
value={value}
|
||||
placeholder={placeholder}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
rows={rows}
|
||||
className="resize-none border-[#1b2233] bg-[#0d121d] text-white placeholder:text-[#5d6880]"
|
||||
className="resize-none border-hairline-strong bg-background text-foreground placeholder:text-muted-soft"
|
||||
/>
|
||||
</label>
|
||||
);
|
||||
@@ -277,20 +280,16 @@ function SelectField({
|
||||
}) {
|
||||
return (
|
||||
<div className="block">
|
||||
<div className="mb-2 text-sm font-medium text-[#9aa6bd]">{label}</div>
|
||||
<div className="mb-2 text-sm font-medium text-foreground">{label}</div>
|
||||
|
||||
<Select value={value} onValueChange={onChange}>
|
||||
<SelectTrigger className="w-full border-[#1b2233] bg-[#0d121d] text-white">
|
||||
<SelectTrigger className="w-full border-hairline-strong bg-background text-foreground">
|
||||
<SelectValue placeholder={`请选择${label}`} />
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent className="border-[#1b2233] bg-[#0f1521] text-white">
|
||||
<SelectContent className="border-hairline bg-popover text-popover-foreground">
|
||||
{options.map((item) => (
|
||||
<SelectItem
|
||||
key={item}
|
||||
value={item}
|
||||
className="focus:bg-blue-500/15 focus:text-blue-300"
|
||||
>
|
||||
<SelectItem key={item} value={item}>
|
||||
{item}
|
||||
</SelectItem>
|
||||
))}
|
||||
@@ -312,10 +311,10 @@ function ToggleRow({
|
||||
onChange: (checked: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center justify-between rounded-xl border border-[#1b2233] bg-[#0d121d] p-4">
|
||||
<div className="flex items-center justify-between rounded-xl border border-hairline bg-canvas-soft p-4">
|
||||
<div>
|
||||
<div className="font-semibold">{title}</div>
|
||||
<div className="mt-1 text-sm text-[#5d6880]">{description}</div>
|
||||
<div className="font-medium text-foreground">{title}</div>
|
||||
<div className="mt-1 text-sm text-muted-foreground">{description}</div>
|
||||
</div>
|
||||
<Switch checked={checked} onCheckedChange={onChange} />
|
||||
</div>
|
||||
|
||||
@@ -1,47 +1,61 @@
|
||||
import { ArrowRight, Boxes, GitBranch, Plus, Workflow } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export function AssistantWorkflowPage() {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6">
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="h-3 w-3 rounded-full bg-cyan-400 shadow-[0_0_0_4px_rgba(34,211,238,.16),0_0_14px_rgba(34,211,238,.35)]" />
|
||||
<h1 className="text-3xl font-bold">创建助手 · 工作流模式</h1>
|
||||
</div>
|
||||
|
||||
<p className="mt-2 text-sm text-[#5d6880]">
|
||||
通过节点编排方式创建复杂 AI 视频助手流程,适合多轮任务、工具调用、知识库检索和人工协同场景。
|
||||
<div className="caption-label text-muted-soft">工作流模式</div>
|
||||
<h1 className="font-display display-lg mt-3 text-ink">创建助手</h1>
|
||||
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
|
||||
通过节点编排方式创建复杂 AI
|
||||
视频助手流程,适合多轮任务、工具调用、知识库检索和人工协同场景。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="rounded-[28px] border border-cyan-500/25 bg-[radial-gradient(circle_at_top_left,rgba(34,211,238,.16),transparent_34%),#0f1521] p-8">
|
||||
<div className="flex items-start gap-5">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-3xl bg-cyan-400 text-[#04121a] shadow-[0_0_32px_rgba(34,211,238,.22)]">
|
||||
<Workflow size={32} />
|
||||
<section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft p-10">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-20 -top-24 h-72 w-72 rounded-full opacity-55 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-mint) 55%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative flex items-start gap-5">
|
||||
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-surface-strong text-foreground">
|
||||
<Workflow size={30} />
|
||||
</div>
|
||||
|
||||
<div className="flex-1">
|
||||
<div className="inline-flex rounded-full border border-cyan-500/30 bg-cyan-500/10 px-3 py-1 text-xs font-semibold text-cyan-300">
|
||||
<div className="caption-label inline-flex rounded-full bg-surface-strong px-3 py-1 text-muted-foreground">
|
||||
即将开放
|
||||
</div>
|
||||
|
||||
<h2 className="mt-5 text-2xl font-bold">工作流画布正在设计中</h2>
|
||||
<h2 className="font-display display-sm mt-5 text-ink">
|
||||
工作流画布正在设计中
|
||||
</h2>
|
||||
|
||||
<p className="mt-3 max-w-2xl text-sm leading-7 text-[#9aa6bd]">
|
||||
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-body">
|
||||
后续这里会提供可视化节点画布,你可以拖拽配置开始节点、意图识别节点、知识库检索节点、
|
||||
大模型回答节点、工具调用节点、人工接管节点和结束节点。
|
||||
</p>
|
||||
|
||||
<div className="mt-6 flex gap-3">
|
||||
<button className="flex h-10 items-center gap-2 rounded-xl bg-cyan-400 px-4 text-sm font-semibold text-[#04121a]">
|
||||
<div className="mt-7 flex gap-3">
|
||||
<Button size="lg" className="gap-2">
|
||||
<Plus size={16} />
|
||||
新建工作流
|
||||
</button>
|
||||
</Button>
|
||||
|
||||
<button className="flex h-10 items-center gap-2 rounded-xl border border-[#1b2233] bg-[#0d121d] px-4 text-sm font-semibold text-[#9aa6bd] hover:text-white">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
|
||||
>
|
||||
查看模板
|
||||
<ArrowRight size={16} />
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,28 +81,30 @@ export function AssistantWorkflowPage() {
|
||||
/>
|
||||
</section>
|
||||
|
||||
<section className="rounded-2xl border border-[#1b2233] bg-[#0f1521] p-6">
|
||||
<div className="mb-5 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-lg font-bold">未来画布结构预览</h2>
|
||||
<p className="mt-1 text-sm text-[#5d6880]">
|
||||
当前是静态占位,后续可替换为 React Flow 或自研画布组件。
|
||||
</p>
|
||||
</div>
|
||||
<section className="rounded-2xl border border-hairline bg-card p-6 shadow-sm">
|
||||
<div className="mb-5">
|
||||
<h2 className="font-display display-sm text-ink">未来画布结构预览</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">
|
||||
当前是静态占位,后续可替换为 React Flow 或自研画布组件。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3 overflow-x-auto rounded-2xl border border-[#1b2233] bg-[#0d121d] p-5">
|
||||
<div className="flex items-center gap-3 overflow-x-auto rounded-2xl border border-hairline bg-canvas-soft p-5">
|
||||
{["开始", "意图识别", "知识库检索", "模型回答", "工具调用", "结束"].map(
|
||||
(item, index) => (
|
||||
<div key={item} className="flex items-center gap-3">
|
||||
<div className="min-w-[128px] rounded-2xl border border-[#273249] bg-[#111827] p-4 text-center">
|
||||
<div className="text-sm font-semibold">{item}</div>
|
||||
<div className="mt-1 text-xs text-[#5d6880]">
|
||||
<div className="min-w-[128px] rounded-xl border border-hairline bg-card p-4 text-center shadow-sm">
|
||||
<div className="text-sm font-medium text-foreground">
|
||||
{item}
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-muted-soft">
|
||||
Node {index + 1}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{index < 5 && <ArrowRight size={18} className="text-[#5d6880]" />}
|
||||
{index < 5 && (
|
||||
<ArrowRight size={18} className="shrink-0 text-muted-soft" />
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
@@ -108,13 +124,15 @@ function FeatureCard({
|
||||
description: string;
|
||||
}) {
|
||||
return (
|
||||
<div className="rounded-2xl border border-[#1b2233] bg-[#0f1521] p-5">
|
||||
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-xl bg-cyan-500/10 text-cyan-300">
|
||||
<div className="rounded-2xl border border-hairline bg-card p-6 shadow-sm transition-shadow hover:shadow-md">
|
||||
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-surface-strong text-foreground">
|
||||
{icon}
|
||||
</div>
|
||||
|
||||
<div className="font-bold">{title}</div>
|
||||
<p className="mt-2 text-sm leading-6 text-[#5d6880]">{description}</p>
|
||||
<div className="font-medium text-foreground">{title}</div>
|
||||
<p className="mt-2 text-sm leading-6 text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function ComponentsPage() {
|
||||
return <div className="text-3xl font-bold">组件库</div>;
|
||||
}
|
||||
return (
|
||||
<PlaceholderPage
|
||||
label="资源管理"
|
||||
title="组件库"
|
||||
description="统一管理大语言模型、语音识别、声音资源、知识库与工具插件。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function HistoryPage() {
|
||||
return <div className="text-3xl font-bold">历史记录</div>;
|
||||
}
|
||||
return (
|
||||
<PlaceholderPage
|
||||
label="运行记录"
|
||||
title="历史记录"
|
||||
description="查看助手的对话历史、运行日志与调用明细。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Boxes, Plus, Video } from "lucide-react";
|
||||
import { Boxes, Plus } from "lucide-react";
|
||||
import type { NavKey } from "@/components/layout/AppShell";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
@@ -8,47 +8,58 @@ type HomePageProps = {
|
||||
|
||||
export function HomePage({ onNavigate }: HomePageProps) {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-6 pt-[4vh]">
|
||||
<section className="relative overflow-hidden rounded-3xl border border-[#1b2233] bg-[radial-gradient(circle_at_top_left,rgba(46,161,255,.18),transparent_36%),#0d121d] p-8">
|
||||
<div className="mb-8 flex items-center gap-4">
|
||||
<div className="relative flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-gradient-to-br from-cyan-400 to-blue-500 text-white shadow-lg shadow-cyan-500/30">
|
||||
<Video size={28} />
|
||||
<div className="absolute inset-0 rounded-2xl ring-1 ring-inset ring-white/20" />
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-12 pt-[3vh]">
|
||||
{/* Hero band — atmospheric gradient orbs behind editorial display copy */}
|
||||
<section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft px-10 py-16">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-24 -top-24 h-80 w-80 rounded-full opacity-60 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-sky) 55%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -bottom-28 left-10 h-72 w-72 rounded-full opacity-50 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-lavender) 55%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative max-w-2xl">
|
||||
<div className="caption-label text-muted-soft">AI 视频助手 · 管理台</div>
|
||||
|
||||
<h1 className="font-display display-xl mt-5 text-ink">
|
||||
你好,开始管理你的智能视频助手
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-xl text-[16px] leading-7 text-body">
|
||||
在这里创建、配置、测试并发布 AI
|
||||
视频助手,统一管理模型、语音识别、声音资源、知识库与工具插件。
|
||||
</p>
|
||||
|
||||
<div className="mt-9 flex gap-3">
|
||||
<Button
|
||||
size="lg"
|
||||
className="gap-2 px-5"
|
||||
onClick={() => onNavigate("assistant-prompt")}
|
||||
>
|
||||
<Plus size={17} />
|
||||
创建助手
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="gap-2 border-hairline-strong text-foreground hover:bg-surface-strong"
|
||||
onClick={() => onNavigate("components")}
|
||||
>
|
||||
<Boxes size={17} />
|
||||
配置组件
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="text-sm font-semibold text-blue-400">
|
||||
AI 视频助手 · 管理台
|
||||
</div>
|
||||
<h1 className="mt-2 text-4xl font-bold tracking-tight text-[#e9eef7]">
|
||||
你好,开始管理你的智能视频助手
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="max-w-2xl text-[15px] leading-7 text-[#9aa6bd]">
|
||||
在这里创建、配置、测试并发布 AI 视频助手,统一管理模型、语音识别、声音资源、知识库与工具插件。
|
||||
</p>
|
||||
|
||||
<div className="mt-8 flex gap-3">
|
||||
<Button
|
||||
size="lg"
|
||||
className="rounded-xl gap-2"
|
||||
onClick={() => onNavigate("assistant-prompt")}
|
||||
>
|
||||
<Plus size={17} />
|
||||
创建助手
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="rounded-xl gap-2 border-[#1b2233] bg-[#0f1521] text-[#9aa6bd] hover:bg-[#151e30] hover:text-[#e9eef7] hover:border-[#2a3550]"
|
||||
onClick={() => onNavigate("components")}
|
||||
>
|
||||
<Boxes size={17} />
|
||||
配置组件
|
||||
</Button>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
53
src/components/pages/PlaceholderPage.tsx
Normal file
53
src/components/pages/PlaceholderPage.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
type PlaceholderPageProps = {
|
||||
label: string;
|
||||
title: string;
|
||||
description: string;
|
||||
};
|
||||
|
||||
export function PlaceholderPage({
|
||||
label,
|
||||
title,
|
||||
description,
|
||||
}: PlaceholderPageProps) {
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-[1180px] flex-col gap-8">
|
||||
<div>
|
||||
<div className="caption-label text-muted-soft">{label}</div>
|
||||
<h1 className="font-display display-lg mt-3 text-ink">{title}</h1>
|
||||
<p className="mt-3 max-w-2xl text-[15px] leading-7 text-muted-foreground">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<section className="relative overflow-hidden rounded-3xl border border-hairline bg-canvas-soft px-10 py-20 text-center">
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -right-24 top-0 h-72 w-72 rounded-full opacity-50 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-sky) 50%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
<div
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute -left-20 bottom-0 h-64 w-64 rounded-full opacity-45 blur-3xl"
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, color-mix(in srgb, var(--gradient-lavender) 50%, transparent), transparent 70%)",
|
||||
}}
|
||||
/>
|
||||
<div className="relative">
|
||||
<div className="caption-label inline-flex rounded-full bg-surface-strong px-3 py-1 text-muted-foreground">
|
||||
建设中
|
||||
</div>
|
||||
<p className="font-display display-sm mx-auto mt-5 max-w-md text-ink">
|
||||
这个页面正在编写中
|
||||
</p>
|
||||
<p className="mx-auto mt-3 max-w-md text-sm leading-7 text-body">
|
||||
页面骨架与设计语言已就绪,后续将填充具体功能与数据。
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function ProfilePage() {
|
||||
return <div className="text-3xl font-bold">个人中心</div>;
|
||||
}
|
||||
return (
|
||||
<PlaceholderPage
|
||||
label="账户设置"
|
||||
title="个人中心"
|
||||
description="管理账户信息、团队成员、密钥与偏好设置。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function TestPage() {
|
||||
return <div className="text-3xl font-bold">测试助手</div>;
|
||||
}
|
||||
return (
|
||||
<PlaceholderPage
|
||||
label="实时调试"
|
||||
title="测试助手"
|
||||
description="在发布前通过实时视频对话测试助手的表现与交互体验。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,11 @@
|
||||
import { PlaceholderPage } from "./PlaceholderPage";
|
||||
|
||||
export function WorkflowPage() {
|
||||
return <div className="text-3xl font-bold">工作流</div>;
|
||||
}
|
||||
return (
|
||||
<PlaceholderPage
|
||||
label="流程编排"
|
||||
title="工作流"
|
||||
description="管理与编排可复用的助手工作流,支持多轮任务与工具调用。"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Slot } from "radix-ui"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const buttonVariants = cva(
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
"group/button inline-flex shrink-0 items-center justify-center rounded-full border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/30 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
||||
Reference in New Issue
Block a user