feat:增加前端样式优化skill和mcp工具 #58
11
frontend/.mcp.json
Normal file
11
frontend/.mcp.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"shadcn": {
|
||||
"command": "npx",
|
||||
"args": [
|
||||
"shadcn@latest",
|
||||
"mcp"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>frontend</title>
|
||||
<meta name="description" content="CamTalk — 多模态实时 AI 视觉对话助手,通过摄像头和麦克风与 AI 自然交互" />
|
||||
<title>CamTalk — AI 视觉对话助手</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
188
frontend/skills/redesign-skill.md
Normal file
188
frontend/skills/redesign-skill.md
Normal file
@@ -0,0 +1,188 @@
|
||||
# Redesign Skill - Anti-Slop Frontend Framework
|
||||
|
||||
> Source: https://github.com/Leonxlnx/taste-skill/tree/main/skills/redesign-skill
|
||||
> Purpose: Audit and upgrade existing frontend code to eliminate generic AI-generated UI patterns.
|
||||
|
||||
## How This Works
|
||||
|
||||
When applied to an existing project, follow this sequence:
|
||||
|
||||
1. **Scan** — Read the codebase. Identify the framework, styling method (Tailwind, vanilla CSS, styled-components, etc.), and current design patterns.
|
||||
2. **Diagnose** — Run through the audit below. List every generic pattern, weak point, and missing state you find.
|
||||
3. **Fix** — Apply targeted upgrades working with the existing stack. Do not rewrite from scratch. Improve what's there.
|
||||
|
||||
---
|
||||
|
||||
## Design Audit
|
||||
|
||||
### Typography
|
||||
|
||||
Check for these problems and fix them:
|
||||
|
||||
- **Browser default fonts or Inter everywhere.** Replace with a font that has character. Good options: `Geist`, `Outfit`, `Cabinet Grotesk`, `Satoshi`. For editorial/creative projects, pair a serif header with a sans-serif body.
|
||||
- **Headlines lack presence.** Increase size for display text, tighten letter-spacing, reduce line-height. Headlines should feel heavy and intentional.
|
||||
- **Body text too wide.** Limit paragraph width to roughly 65 characters. Increase line-height for readability.
|
||||
- **Only Regular (400) and Bold (700) weights used.** Introduce Medium (500) and SemiBold (600) for more subtle hierarchy.
|
||||
- **Numbers in proportional font.** Use a monospace font or enable tabular figures (`font-variant-numeric: tabular-nums`) for data-heavy interfaces.
|
||||
- **Missing letter-spacing adjustments.** Use negative tracking for large headers, positive tracking for small caps or labels.
|
||||
- **All-caps subheaders everywhere.** Try lowercase italics, sentence case, or small-caps instead.
|
||||
- **Orphaned words.** Single words sitting alone on the last line. Fix with `text-wrap: balance` or `text-wrap: pretty`.
|
||||
|
||||
### Color and Surfaces
|
||||
|
||||
- **Pure `#000000` background.** Replace with off-black, dark charcoal, or tinted dark (`#0a0a0a`, `#121212`, or a dark navy).
|
||||
- **Oversaturated accent colors.** Keep saturation below 80%. Desaturate accents so they blend with neutrals instead of screaming.
|
||||
- **More than one accent color.** Pick one. Remove the rest. Consistency beats variety.
|
||||
- **Mixing warm and cool grays.** Stick to one gray family. Tint all grays with a consistent hue (warm or cool, not both).
|
||||
- **Purple/blue "AI gradient" aesthetic.** This is the most common AI design fingerprint. Replace with neutral bases and a single, considered accent.
|
||||
- **Generic `box-shadow`.** Tint shadows to match the background hue. Use colored shadows (e.g., dark blue shadow on a blue background) instead of pure black at low opacity.
|
||||
- **Flat design with zero texture.** Add subtle noise, grain, or micro-patterns to backgrounds. Pure flat vectors feel sterile.
|
||||
- **Perfectly even gradients.** Break the uniformity with radial gradients, noise overlays, or mesh gradients instead of standard linear 45-degree fades.
|
||||
- **Inconsistent lighting direction.** Audit all shadows to ensure they suggest a single, consistent light source.
|
||||
- **Random dark sections in a light mode page (or vice versa).** A single dark-background section breaking an otherwise light page looks like a copy-paste accident. Either commit to a full dark mode or keep a consistent background tone throughout. If contrast is needed, use a slightly darker shade of the same palette — not a sudden jump to `#111` in the middle of a cream page.
|
||||
- **Empty, flat sections with no visual depth.** Sections that are just text on a plain background feel unfinished. Add high-quality background imagery (blurred, overlaid, or masked), subtle patterns, or ambient gradients. Use reliable placeholder sources like `https://picsum.photos/seed/{name}/1920/1080` when real assets are not available. Experiment with background images behind hero sections, feature blocks, or CTAs — even a subtle full-width photo at low opacity adds presence.
|
||||
|
||||
### Layout
|
||||
|
||||
- **Everything centered and symmetrical.** Break symmetry with offset margins, mixed aspect ratios, or left-aligned headers over centered content.
|
||||
- **Three equal card columns as feature row.** This is the most generic AI layout. Replace with a 2-column zig-zag, asymmetric grid, horizontal scroll, or masonry layout.
|
||||
- **Using `height: 100vh` for full-screen sections.** Replace with `min-height: 100dvh` to prevent layout jumping on mobile browsers (iOS Safari viewport bug).
|
||||
- **Complex flexbox percentage math.** Replace with CSS Grid for reliable multi-column structures.
|
||||
- **No max-width container.** Add a container constraint (around 1200-1440px) with auto margins so content doesn't stretch edge-to-edge on wide screens.
|
||||
- **Cards of equal height forced by flexbox.** Allow variable heights or use masonry when content varies in length.
|
||||
- **Uniform border-radius on everything.** Vary the radius: tighter on inner elements, softer on containers.
|
||||
- **No overlap or depth.** Elements sit flat next to each other. Use negative margins to create layering and visual depth.
|
||||
- **Symmetrical vertical padding.** Top and bottom padding are always identical. Adjust optically — bottom padding often needs to be slightly larger.
|
||||
- **Dashboard always has a left sidebar.** Try top navigation, a floating command menu, or a collapsible panel instead.
|
||||
- **Missing whitespace.** Double the spacing. Let the design breathe. Dense layouts work for data dashboards, not for marketing pages.
|
||||
- **Buttons not bottom-aligned in card groups.** When cards have different content lengths, CTAs end up at random heights. Pin buttons to the bottom of each card so they form a clean horizontal line regardless of content above.
|
||||
- **Feature lists starting at different vertical positions.** In pricing tables or comparison cards, the list of features should start at the same Y position across all columns. Use consistent spacing above the list or fixed-height title/price blocks.
|
||||
- **Inconsistent vertical rhythm in side-by-side elements.** When placing cards, columns, or panels next to each other, align shared elements (titles, descriptions, prices, buttons) across all items. Misaligned baselines make the layout look broken.
|
||||
- **Mathematical alignment that looks optically wrong.** Centering by the math doesn't always look centered to the eye. Icons next to text, play buttons in circles, or text in buttons often need 1-2px optical adjustments to feel right.
|
||||
|
||||
### Interactivity and States
|
||||
|
||||
- **No hover states on buttons.** Add background shift, slight scale, or translate on hover.
|
||||
- **No active/pressed feedback.** Add a subtle `scale(0.98)` or `translateY(1px)` on press to simulate a physical click.
|
||||
- **Instant transitions with zero duration.** Add smooth transitions (200-300ms) to all interactive elements.
|
||||
- **Missing focus ring.** Ensure visible focus indicators for keyboard navigation. This is an accessibility requirement, not optional.
|
||||
- **No loading states.** Replace generic circular spinners with skeleton loaders that match the layout shape.
|
||||
- **No empty states.** An empty dashboard showing nothing is a missed opportunity. Design a composed "getting started" view.
|
||||
- **No error states.** Add clear, inline error messages for forms. Do not use `window.alert()`.
|
||||
- **Dead links.** Buttons that link to `#`. Either link to real destinations or visually disable them.
|
||||
- **No indication of current page in navigation.** Style the active nav link differently so users know where they are.
|
||||
- **Scroll jumping.** Anchor clicks jump instantly. Add `scroll-behavior: smooth`.
|
||||
- **Animations using `top`, `left`, `width`, `height`.** Switch to `transform` and `opacity` for GPU-accelerated, smooth animation.
|
||||
|
||||
### Content
|
||||
|
||||
- **Generic names like "John Doe" or "Jane Smith".** Use diverse, realistic-sounding names.
|
||||
- **Fake round numbers like `99.99%`, `50%`, `$100.00`.** Use organic, messy data: `47.2%`, `$99.00`, `+1 (312) 847-1928`.
|
||||
- **Placeholder company names like "Acme Corp", "Nexus", "SmartFlow".** Invent contextual, believable brand names.
|
||||
- **AI copywriting cliches.** Never use "Elevate", "Seamless", "Unleash", "Next-Gen", "Game-changer", "Delve", "Tapestry", or "In the world of...". Write plain, specific language.
|
||||
- **Exclamation marks in success messages.** Remove them. Be confident, not loud.
|
||||
- **"Oops!" error messages.** Be direct: "Connection failed. Please try again."
|
||||
- **Passive voice.** Use active voice: "We couldn't save your changes" instead of "Mistakes were made."
|
||||
- **All blog post dates identical.** Randomize dates to appear real.
|
||||
- **Same avatar image for multiple users.** Use unique assets for every distinct person.
|
||||
- **Lorem Ipsum.** Never use placeholder latin text. Write real draft copy.
|
||||
- **Title Case On Every Header.** Use sentence case instead.
|
||||
|
||||
### Component Patterns
|
||||
|
||||
- **Generic card look (border + shadow + white background).** Remove the border, or use only background color, or use only spacing. Cards should exist only when elevation communicates hierarchy.
|
||||
- **Always one filled button + one ghost button.** Add text links or tertiary styles to reduce visual noise.
|
||||
- **Pill-shaped "New" and "Beta" badges.** Try square badges, flags, or plain text labels.
|
||||
- **Accordion FAQ sections.** Use a side-by-side list, searchable help, or inline progressive disclosure.
|
||||
- **3-card carousel testimonials with dots.** Replace with a masonry wall, embedded social posts, or a single rotating quote.
|
||||
- **Pricing table with 3 towers.** Highlight the recommended tier with color and emphasis, not just extra height.
|
||||
- **Modals for everything.** Use inline editing, slide-over panels, or expandable sections instead of popups for simple actions.
|
||||
- **Avatar circles exclusively.** Try squircles or rounded squares for a less generic look.
|
||||
- **Light/dark toggle always a sun/moon switch.** Use a dropdown, system preference detection, or integrate it into settings.
|
||||
- **Footer link farm with 4 columns.** Simplify. Focus on main navigational paths and legally required links.
|
||||
|
||||
### Iconography
|
||||
|
||||
- **Lucide or Feather icons exclusively.** These are the "default" AI icon choice. Use Phosphor, Heroicons, or a custom set for differentiation.
|
||||
- **Rocketship for "Launch", shield for "Security".** Replace cliche metaphors with less obvious icons (bolt, fingerprint, spark, vault).
|
||||
- **Inconsistent stroke widths across icons.** Audit all icons and standardize to one stroke weight.
|
||||
- **Missing favicon.** Always include a branded favicon.
|
||||
- **Stock "diverse team" photos.** Use real team photos, candid shots, or a consistent illustration style instead of uncanny stock imagery.
|
||||
|
||||
### Code Quality
|
||||
|
||||
- **Div soup.** Use semantic HTML: `<header>`, `<main>`, `<article>`, `<section>`, `<footer>`.
|
||||
- **Inline styles mixed with CSS classes.** Move all styling to the project's styling system.
|
||||
- **Hardcoded pixel widths.** Use relative units (`%`, `rem`, `em`, `max-width`) for flexible layouts.
|
||||
- **Missing alt text on images.** Describe image content for screen readers. Never leave `alt=""` or `alt="image"` on meaningful images.
|
||||
- **Arbitrary z-index values like `9999`.** Establish a clean z-index scale in the theme/variables.
|
||||
- **Commented-out dead code.** Remove all debug artifacts before shipping.
|
||||
- **Import hallucinations.** Check that every import actually exists in `package.json` or the project dependencies.
|
||||
- **Missing meta tags.** Add proper `<title>`, `<description>`, `<og:image>`, and social sharing meta tags.
|
||||
|
||||
### Strategic Omissions (What AI Typically Forgets)
|
||||
|
||||
- **No legal links.** Add privacy policy and terms of service links in the footer.
|
||||
- **No "back" navigation.** Dead ends in user flows. Every page needs a way back.
|
||||
- **No custom 404 page.** Design a helpful, branded "page not found" experience.
|
||||
- **No form validation.** Add client-side validation for emails, required fields, and format checks.
|
||||
- **No "skip to content" link.** Essential for keyboard users. Add a hidden skip-link.
|
||||
- **No cookie consent.** If required by jurisdiction, add a compliant consent banner.
|
||||
|
||||
---
|
||||
|
||||
## Upgrade Techniques
|
||||
|
||||
When upgrading a project, pull from these high-impact techniques to replace generic patterns:
|
||||
|
||||
### Typography Upgrades
|
||||
|
||||
- **Variable font animation.** Interpolate weight or width on scroll or hover for text that feels alive.
|
||||
- **Outlined-to-fill transitions.** Text starts as a stroke outline and fills with color on scroll entry or interaction.
|
||||
- **Text mask reveals.** Large typography acting as a window to video or animated imagery behind it.
|
||||
|
||||
### Layout Upgrades
|
||||
|
||||
- **Broken grid / asymmetry.** Elements that deliberately ignore column structure — overlapping, bleeding off-screen, or offset with calculated randomness.
|
||||
- **Whitespace maximization.** Aggressive use of negative space to force focus on a single element.
|
||||
- **Parallax card stacks.** Sections that stick and physically stack over each other during scroll.
|
||||
- **Split-screen scroll.** Two halves of the screen sliding in opposite directions.
|
||||
|
||||
### Motion Upgrades
|
||||
|
||||
- **Smooth scroll with inertia.** Decouple scrolling from browser defaults for a heavier, cinematic feel.
|
||||
- **Staggered entry.** Elements cascade in with slight delays, combining Y-axis translation with opacity fade. Never mount everything at once.
|
||||
- **Spring physics.** Replace linear easing with spring-based motion for a natural, weighty feel on all interactive elements.
|
||||
- **Scroll-driven reveals.** Content entering through expanding masks, wipes, or draw-on SVG paths tied to scroll progress.
|
||||
|
||||
### Surface Upgrades
|
||||
|
||||
- **True glassmorphism.** Go beyond `backdrop-filter: blur`. Add a 1px inner border and a subtle inner shadow to simulate edge refraction.
|
||||
- **Spotlight borders.** Card borders that illuminate dynamically under the cursor.
|
||||
- **Grain and noise overlays.** A fixed, pointer-events-none overlay with subtle noise to break digital flatness.
|
||||
- **Colored, tinted shadows.** Shadows that carry the hue of the background rather than using generic black.
|
||||
|
||||
---
|
||||
|
||||
## Fix Priority
|
||||
|
||||
Apply changes in this order for maximum visual impact with minimum risk:
|
||||
|
||||
1. **Font swap** — biggest instant improvement, lowest risk
|
||||
2. **Color palette cleanup** — remove clashing or oversaturated colors
|
||||
3. **Hover and active states** — makes the interface feel alive
|
||||
4. **Layout and spacing** — proper grid, max-width, consistent padding
|
||||
5. **Replace generic components** — swap cliche patterns for modern alternatives
|
||||
6. **Add loading, empty, and error states** — makes it feel finished
|
||||
7. **Polish typography scale and spacing** — the premium final touch
|
||||
|
||||
---
|
||||
|
||||
## Rules
|
||||
|
||||
- Work with the existing tech stack. Do not migrate frameworks or styling libraries.
|
||||
- Do not break existing functionality. Test after every change.
|
||||
- Before importing any new library, check the project's dependency file first.
|
||||
- If the project uses Tailwind, check the version (v3 vs v4) before modifying config.
|
||||
- If the project has no framework, use vanilla CSS.
|
||||
- Keep changes reviewable and focused. Small, targeted improvements over big rewrites.
|
||||
98
frontend/skills/soft-skill.md
Normal file
98
frontend/skills/soft-skill.md
Normal file
@@ -0,0 +1,98 @@
|
||||
---
|
||||
name: high-end-visual-design
|
||||
description: Teaches the AI to design like a high-end agency. Defines the exact fonts, spacing, shadows, card structures, and animations that make a website feel expensive. Blocks all the common defaults that make AI designs look cheap or generic.
|
||||
---
|
||||
|
||||
# Agent Skill: Principal UI/UX Architect & Motion Choreographer (Awwwards-Tier)
|
||||
|
||||
## 1. Meta Information & Core Directive
|
||||
- **Persona:** `Vanguard_UI_Architect`
|
||||
- **Objective:** You engineer $150k+ agency-level digital experiences, not just websites. Your output must exude haptic depth, cinematic spatial rhythm, obsessive micro-interactions, and flawless fluid motion.
|
||||
- **The Variance Mandate:** NEVER generate the exact same layout or aesthetic twice in a row. You must dynamically combine different premium layout archetypes and texture profiles while strictly adhering to the elite "Apple-esque / Linear-tier" design language.
|
||||
|
||||
## 2. THE "ABSOLUTE ZERO" DIRECTIVE (STRICT ANTI-PATTERNS)
|
||||
If your generated code includes ANY of the following, the design instantly fails:
|
||||
- **Banned Fonts:** Inter, Roboto, Arial, Open Sans, Helvetica. (Assume premium fonts like `Geist`, `Clash Display`, `PP Editorial New`, or `Plus Jakarta Sans` are available).
|
||||
- **Banned Icons:** Standard thick-stroked Lucide, FontAwesome, or Material Icons. Use only ultra-light, precise lines (e.g., Phosphor Light, Remix Line).
|
||||
- **Banned Borders & Shadows:** Generic 1px solid gray borders. Harsh, dark drop shadows (`shadow-md`, `rgba(0,0,0,0.3)`).
|
||||
- **Banned Layouts:** Edge-to-edge sticky navbars glued to the top. Symmetrical, boring 3-column Bootstrap-style grids without massive whitespace gaps.
|
||||
- **Banned Motion:** Standard `linear` or `ease-in-out` transitions. Instant state changes without interpolation.
|
||||
|
||||
## 3. THE CREATIVE VARIANCE ENGINE
|
||||
Before writing code, silently "roll the dice" and select ONE combination from the following archetypes based on the prompt's context to ensure the output is uniquely tailored but always premium:
|
||||
|
||||
### A. Vibe & Texture Archetypes (Pick 1)
|
||||
1. **Ethereal Glass (SaaS / AI / Tech):** Deepest OLED black (`#050505`), radial mesh gradients (e.g., subtle glowing purple/emerald orbs) in the background. Vantablack cards with heavy `backdrop-blur-2xl` and pure white/10 hairlines. Wide geometric Grotesk typography.
|
||||
2. **Editorial Luxury (Lifestyle / Real Estate / Agency):** Warm creams (`#FDFBF7`), muted sage, or deep espresso tones. High-contrast Variable Serif fonts for massive headings. Subtle CSS noise/film-grain overlay (`opacity-[0.03]`) for a physical paper feel.
|
||||
3. **Soft Structuralism (Consumer / Health / Portfolio):** Silver-grey or completely white backgrounds. Massive bold Grotesk typography. Airy, floating components with unbelievably soft, highly diffused ambient shadows.
|
||||
|
||||
### B. Layout Archetypes (Pick 1)
|
||||
1. **The Asymmetrical Bento:** A masonry-like CSS Grid of varying card sizes (e.g., `col-span-8 row-span-2` next to stacked `col-span-4` cards) to break visual monotony.
|
||||
- **Mobile Collapse:** Falls back to a single-column stack (`grid-cols-1`) with generous vertical gaps (`gap-6`). All `col-span` overrides reset to `col-span-1`.
|
||||
2. **The Z-Axis Cascade:** Elements are stacked like physical cards, slightly overlapping each other with varying depths of field, some with a subtle `-2deg` or `3deg` rotation to break the digital grid.
|
||||
- **Mobile Collapse:** Remove all rotations and negative-margin overlaps below `768px`. Stack vertically with standard spacing. Overlapping elements cause touch-target conflicts on mobile.
|
||||
3. **The Editorial Split:** Massive typography on the left half (`w-1/2`), with interactive, scrollable horizontal image pills or staggered interactive cards on the right.
|
||||
- **Mobile Collapse:** Converts to a full-width vertical stack (`w-full`). Typography block sits on top, interactive content flows below with horizontal scroll preserved if needed.
|
||||
|
||||
**Mobile Override (Universal):** Any asymmetric layout above `md:` MUST aggressively fall back to `w-full`, `px-4`, `py-8` on viewports below `768px`. Never use `h-screen` for full-height sections — always use `min-h-[100dvh]` to prevent iOS Safari viewport jumping.
|
||||
|
||||
## 4. HAPTIC MICRO-AESTHETICS (COMPONENT MASTERY)
|
||||
|
||||
### A. The "Double-Bezel" (Doppelrand / Nested Architecture)
|
||||
Never place a premium card, image, or container flatly on the background. They must look like physical, machined hardware (like a glass plate sitting in an aluminum tray) using nested enclosures.
|
||||
- **Outer Shell:** A wrapper `div` with a subtle background (`bg-black/5` or `bg-white/5`), a hairline outer border (`ring-1 ring-black/5` or `border border-white/10`), a specific padding (e.g., `p-1.5` or `p-2`), and a large outer radius (`rounded-[2rem]`).
|
||||
- **Inner Core:** The actual content container inside the shell. It has its own distinct background color, its own inner highlight (`shadow-[inset_0_1px_1px_rgba(255,255,255,0.15)]`), and a mathematically calculated smaller radius (e.g., `rounded-[calc(2rem-0.375rem)]`) for concentric curves.
|
||||
|
||||
### B. Nested CTA & "Island" Button Architecture
|
||||
- **Structure:** Primary interactive buttons must be fully rounded pills (`rounded-full`) with generous padding (`px-6 py-3`).
|
||||
- **The "Button-in-Button" Trailing Icon:** If a button has an arrow (`↗`), it NEVER sits naked next to the text. It must be nested inside its own distinct circular wrapper (e.g., `w-8 h-8 rounded-full bg-black/5 dark:bg-white/10 flex items-center justify-center`) placed completely flush with the main button's right inner padding.
|
||||
|
||||
### C. Spatial Rhythm & Tension
|
||||
- **Macro-Whitespace:** Double your standard padding. Use `py-24` to `py-40` for sections. Allow the design to breathe heavily.
|
||||
- **Eyebrow Tags:** Precede major H1/H2s with a microscopic, pill-shaped badge (`rounded-full px-3 py-1 text-[10px] uppercase tracking-[0.2em] font-medium`).
|
||||
|
||||
## 5. MOTION CHOREOGRAPHY (FLUID DYNAMICS)
|
||||
Never use default transitions. All motion must simulate real-world mass and spring physics. Use custom cubic-beziers (e.g., `transition-all duration-700 ease-[cubic-bezier(0.32,0.72,0,1)]`).
|
||||
|
||||
### A. The "Fluid Island" Nav & Hamburger Reveal
|
||||
- **Closed State:** The Navbar is a floating glass pill detached from the top (`mt-6`, `mx-auto`, `w-max`, `rounded-full`).
|
||||
- **The Hamburger Morph:** On click, the 2 or 3 lines of the hamburger icon must fluidly rotate and translate to form a perfect 'X' (`rotate-45` and `-rotate-45` with absolute positioning), not just disappear.
|
||||
- **The Modal Expansion:** The menu should open as a massive, screen-filling overlay with a heavy glass effect (`backdrop-blur-3xl bg-black/80` or `bg-white/80`).
|
||||
- **Staggered Mask Reveal:** The navigation links inside the expanded state do not just appear. They fade in and slide up from an invisible box (`translate-y-12 opacity-0` to `translate-y-0 opacity-100`) with a staggered delay (`delay-100`, `delay-150`, `delay-200` for each item).
|
||||
|
||||
### B. Magnetic Button Hover Physics
|
||||
- Use the `group` utility. On hover, do not just change the background color.
|
||||
- Scale the entire button down slightly (`active:scale-[0.98]`) to simulate physical pressing.
|
||||
- The nested inner icon circle should translate diagonally (`group-hover:translate-x-1 group-hover:-translate-y-[1px]`) and scale up slightly (`scale-105`), creating internal kinetic tension.
|
||||
|
||||
### C. Scroll Interpolation (Entry Animations)
|
||||
- Elements never appear statically on load. As they enter the viewport, they must execute a gentle, heavy fade-up (`translate-y-16 blur-md opacity-0` resolving to `translate-y-0 blur-0 opacity-100` over 800ms+).
|
||||
- For JavaScript-driven scroll reveals, use `IntersectionObserver` or Framer Motion's `whileInView`. Never use `window.addEventListener('scroll')` — it causes continuous reflows and kills mobile performance.
|
||||
|
||||
## 6. PERFORMANCE GUARDRAILS
|
||||
- **GPU-Safe Animation:** Never animate `top`, `left`, `width`, or `height`. Animate exclusively via `transform` and `opacity`. Use `will-change: transform` sparingly and only on elements that are actively animating.
|
||||
- **Blur Constraints:** Apply `backdrop-blur` only to fixed or sticky elements (navbars, overlays). Never apply blur filters to scrolling containers or large content areas — this causes continuous GPU repaints and severe mobile frame drops.
|
||||
- **Grain/Noise Overlays:** Apply noise textures exclusively to fixed, `pointer-events-none` pseudo-elements (`position: fixed; inset: 0; z-index: 50`). Never attach them to scrolling containers.
|
||||
- **Z-Index Discipline:** Do not use arbitrary `z-50` or `z-[9999]`. Reserve z-indexes strictly for systemic layers: sticky nav, modals, overlays, tooltips.
|
||||
|
||||
## 7. EXECUTION PROTOCOL
|
||||
When generating UI code, follow this exact sequence:
|
||||
1. **[SILENT THOUGHT]** Roll the Variance Engine (Section 3). Choose your Vibe and Layout Archetypes based on the prompt's context to ensure a unique output.
|
||||
2. **[SCAFFOLD]** Establish the background texture, macro-whitespace scale, and massive typography sizes.
|
||||
3. **[ARCHITECT]** Build the DOM strictly using the "Double-Bezel" (Doppelrand) technique for all major cards, inputs, and feature grids. Use exaggerated squircle radii (`rounded-[2rem]`).
|
||||
4. **[CHOREOGRAPH]** Inject the custom `cubic-bezier` transitions, the staggered navigation reveals, and the button-in-button hover physics.
|
||||
5. **[OUTPUT]** Deliver flawless, pixel-perfect React/Tailwind/HTML code. Do not include basic, generic fallbacks.
|
||||
|
||||
## 8. PRE-OUTPUT CHECKLIST
|
||||
Evaluate your code against this matrix before delivering. This is the last filter.
|
||||
- [ ] No banned fonts, icons, borders, shadows, layouts, or motion patterns from Section 2 are present
|
||||
- [ ] A Vibe Archetype and Layout Archetype from Section 3 were consciously selected and applied
|
||||
- [ ] All major cards and containers use the Double-Bezel nested architecture (outer shell + inner core)
|
||||
- [ ] CTA buttons use the Button-in-Button trailing icon pattern where applicable
|
||||
- [ ] Section padding is at minimum `py-24` — the layout breathes heavily
|
||||
- [ ] All transitions use custom cubic-bezier curves — no `linear` or `ease-in-out`
|
||||
- [ ] Scroll entry animations are present — no element appears statically
|
||||
- [ ] Layout collapses gracefully below `768px` to single-column with `w-full` and `px-4`
|
||||
- [ ] All animations use only `transform` and `opacity` — no layout-triggering properties
|
||||
- [ ] `backdrop-blur` is only applied to fixed/sticky elements, never to scrolling content
|
||||
- [ ] The overall impression reads as "$150k agency build", not "template with nice fonts"
|
||||
1206
frontend/skills/taste-skill-main.md
Normal file
1206
frontend/skills/taste-skill-main.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,41 @@
|
||||
/* ============================================================
|
||||
CamTalk — Web 端应用样式
|
||||
双栏布局:左侧视频面板 + 右侧聊天面板
|
||||
|
||||
设计方向:深色科技极简,Ethereal Glass 美学
|
||||
字体:Inter 优先,系统字体栈兜底
|
||||
颜色:冷灰体系,单一蓝色强调色
|
||||
============================================================ */
|
||||
|
||||
:root {
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-bg: #0b0f1a;
|
||||
--color-surface: #151b2b;
|
||||
--color-surface-2: #1c2438;
|
||||
--color-text: #e2e8f0;
|
||||
--color-text-muted: #64748b;
|
||||
--color-border: #1e293b;
|
||||
--color-success: #22c55e;
|
||||
--color-warning: #f59e0b;
|
||||
--color-error: #ef4444;
|
||||
--radius: 10px;
|
||||
--radius-sm: 6px;
|
||||
--color-primary: #3b82f6;
|
||||
--color-primary-hover: #2563eb;
|
||||
--color-bg: #0a0a0f;
|
||||
--color-surface: #111118;
|
||||
--color-surface-2: #1a1a24;
|
||||
--color-surface-3: #22222e;
|
||||
--color-text: #d4d4dc;
|
||||
--color-text-muted: #5a5a6e;
|
||||
--color-border: #1f1f2a;
|
||||
--color-success: #34d399;
|
||||
--color-warning: #fbbf24;
|
||||
--color-error: #f87171;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--transition-fast: 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
--transition-smooth: 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||||
}
|
||||
|
||||
[data-theme="light"] {
|
||||
--color-primary: #2563eb;
|
||||
--color-primary-hover: #1d4ed8;
|
||||
--color-bg: #f1f5f9;
|
||||
--color-bg: #f4f4f8;
|
||||
--color-surface: #ffffff;
|
||||
--color-surface-2: #f8fafc;
|
||||
--color-text: #0f172a;
|
||||
--color-text-muted: #64748b;
|
||||
--color-border: #e2e8f0;
|
||||
--color-surface-2: #f0f0f5;
|
||||
--color-surface-3: #e8e8ee;
|
||||
--color-text: #111118;
|
||||
--color-text-muted: #6b6b80;
|
||||
--color-border: #dddde5;
|
||||
--color-success: #16a34a;
|
||||
--color-warning: #d97706;
|
||||
--color-error: #dc2626;
|
||||
@@ -44,9 +52,12 @@ html, body, #root {
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", sans-serif;
|
||||
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
|
||||
font-variant-numeric: tabular-nums;
|
||||
background: var(--color-bg);
|
||||
color: var(--color-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* ---- App Shell ---- */
|
||||
@@ -64,11 +75,14 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 20px;
|
||||
height: 48px;
|
||||
padding: 0 24px;
|
||||
height: 56px;
|
||||
background: var(--color-surface);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
box-shadow: 0 1px 8px rgba(10, 10, 15, 0.3);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header__left {
|
||||
@@ -78,14 +92,17 @@ body {
|
||||
}
|
||||
|
||||
.header__title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 700;
|
||||
font-size: 1.15rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.02em;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.header__subtitle {
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 400;
|
||||
color: var(--color-text-muted);
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.header__right {
|
||||
@@ -95,30 +112,34 @@ body {
|
||||
}
|
||||
|
||||
.header__stats {
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.72rem;
|
||||
color: var(--color-text-muted);
|
||||
padding: 4px 10px;
|
||||
padding: 4px 12px;
|
||||
background: var(--color-surface-2);
|
||||
border-radius: 12px;
|
||||
border-radius: 20px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.75rem;
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
font-size: 0.72rem;
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.badge--connected {
|
||||
color: var(--color-success);
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.3);
|
||||
background: rgba(52, 211, 153, 0.08);
|
||||
border: 1px solid rgba(52, 211, 153, 0.2);
|
||||
}
|
||||
|
||||
.badge--connecting {
|
||||
color: var(--color-warning);
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
border: 1px solid rgba(251, 191, 36, 0.2);
|
||||
}
|
||||
|
||||
.badge--disconnected {
|
||||
@@ -132,10 +153,13 @@ body {
|
||||
border: none;
|
||||
font-size: 1.1rem;
|
||||
cursor: pointer;
|
||||
padding: 6px;
|
||||
padding: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background 0.15s;
|
||||
transition: all var(--transition-fast);
|
||||
color: var(--color-text-muted);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn-icon:hover {
|
||||
@@ -143,6 +167,10 @@ body {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.btn-icon:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.lang-group {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
@@ -158,9 +186,9 @@ body {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.lang-btn:hover {
|
||||
@@ -187,8 +215,8 @@ body {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 16px;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
@@ -198,6 +226,11 @@ body {
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-border);
|
||||
transition: border-color var(--transition-fast);
|
||||
}
|
||||
|
||||
.video-container:hover {
|
||||
border-color: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.video-container .video-preview {
|
||||
@@ -210,7 +243,7 @@ body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
background: #1a1a2e;
|
||||
background: #12121a;
|
||||
}
|
||||
|
||||
.video-placeholder {
|
||||
@@ -220,14 +253,15 @@ body {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
gap: 10px;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.video-placeholder__icon {
|
||||
font-size: 2.4rem;
|
||||
opacity: 0.2;
|
||||
font-size: 2.2rem;
|
||||
opacity: 0.15;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
@@ -235,14 +269,15 @@ body {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(10, 10, 15, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
color: var(--color-success);
|
||||
padding: 6px 14px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
animation: pulse 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
|
||||
}
|
||||
|
||||
.live-badge {
|
||||
@@ -252,22 +287,23 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(10, 10, 15, 0.8);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
color: white;
|
||||
padding: 4px 12px;
|
||||
padding: 5px 12px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.75rem;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.live-badge__dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-success);
|
||||
animation: pulse 1.5s ease-in-out infinite;
|
||||
animation: pulse 1.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
|
||||
}
|
||||
|
||||
.video-indicator--loading { color: var(--color-warning); }
|
||||
@@ -278,45 +314,49 @@ body {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: rgba(37, 99, 235, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(59, 130, 246, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
color: white;
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
.observation-badge {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
background: rgba(34, 197, 94, 0.85);
|
||||
backdrop-filter: blur(8px);
|
||||
background: rgba(52, 211, 153, 0.85);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
color: white;
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 700;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
letter-spacing: 0.04em;
|
||||
animation: pulse 2s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
|
||||
}
|
||||
|
||||
.video-overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.35);
|
||||
background: rgba(10, 10, 15, 0.4);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(2px);
|
||||
backdrop-filter: blur(3px);
|
||||
-webkit-backdrop-filter: blur(3px);
|
||||
}
|
||||
|
||||
.video-overlay__spinner {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.2);
|
||||
border-top-color: white;
|
||||
border: 2.5px solid rgba(255, 255, 255, 0.15);
|
||||
border-top-color: rgba(255, 255, 255, 0.8);
|
||||
border-radius: 50%;
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
@@ -327,53 +367,66 @@ body {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 4px 0;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.video-controls__row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.btn--ctrl {
|
||||
padding: 6px 12px;
|
||||
padding: 7px 14px;
|
||||
font-size: 0.78rem;
|
||||
border-radius: var(--radius-sm);
|
||||
border: 1px solid var(--color-border);
|
||||
background: var(--color-surface-2);
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
transition: all var(--transition-fast);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn--ctrl:hover {
|
||||
background: var(--color-border);
|
||||
background: var(--color-surface-3);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.btn--ctrl:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn--ctrl-on {
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
background: rgba(52, 211, 153, 0.1);
|
||||
color: var(--color-success);
|
||||
border-color: rgba(34, 197, 94, 0.3);
|
||||
border-color: rgba(52, 211, 153, 0.25);
|
||||
}
|
||||
|
||||
.btn--ctrl-on:hover {
|
||||
background: rgba(52, 211, 153, 0.18);
|
||||
}
|
||||
|
||||
.btn--ctrl-off {
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
background: rgba(248, 113, 113, 0.08);
|
||||
color: var(--color-error);
|
||||
border-color: rgba(239, 68, 68, 0.2);
|
||||
border-color: rgba(248, 113, 113, 0.18);
|
||||
}
|
||||
|
||||
.btn--ctrl-off:hover {
|
||||
background: rgba(248, 113, 113, 0.15);
|
||||
}
|
||||
|
||||
.btn--speaking {
|
||||
animation: micPulse 0.8s ease-in-out infinite;
|
||||
box-shadow: 0 0 12px rgba(34, 197, 94, 0.5);
|
||||
animation: micPulse 0.8s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
|
||||
box-shadow: 0 0 12px rgba(52, 211, 153, 0.4);
|
||||
}
|
||||
|
||||
@keyframes micPulse {
|
||||
0%, 100% { box-shadow: 0 0 8px rgba(34, 197, 94, 0.4); transform: scale(1); }
|
||||
50% { box-shadow: 0 0 16px rgba(34, 197, 94, 0.8); transform: scale(1.05); }
|
||||
0%, 100% { box-shadow: 0 0 8px rgba(52, 211, 153, 0.3); transform: scale(1); }
|
||||
50% { box-shadow: 0 0 18px rgba(52, 211, 153, 0.7); transform: scale(1.04); }
|
||||
}
|
||||
|
||||
/* ---- 右侧聊天面板 ---- */
|
||||
@@ -391,21 +444,23 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 16px;
|
||||
padding: 14px 24px;
|
||||
font-weight: 600;
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
color: var(--color-text-muted);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.chat-panel-header__mode {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 500;
|
||||
color: var(--color-success);
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
padding: 2px 8px;
|
||||
border-radius: 10px;
|
||||
background: rgba(52, 211, 153, 0.08);
|
||||
padding: 3px 10px;
|
||||
border-radius: 12px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
.chat-panel-body {
|
||||
@@ -420,32 +475,57 @@ body {
|
||||
.chat-panel {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 12px 16px;
|
||||
padding: 16px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.chat-panel--empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
gap: 12px;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.chat-panel--empty-icon {
|
||||
font-size: 2.4rem;
|
||||
opacity: 0.12;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.chat-panel--empty-hint {
|
||||
font-size: 0.75rem;
|
||||
opacity: 0.6;
|
||||
max-width: 240px;
|
||||
text-align: center;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
padding: 10px 14px;
|
||||
padding: 12px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--color-surface-2);
|
||||
max-width: 92%;
|
||||
max-width: min(92%, 65ch);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.chat-message:hover {
|
||||
background: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.chat-message--user {
|
||||
border-left: 2px solid var(--color-primary);
|
||||
align-self: flex-end;
|
||||
background: rgba(37, 99, 235, 0.08);
|
||||
background: rgba(59, 130, 246, 0.06);
|
||||
}
|
||||
|
||||
.chat-message--user:hover {
|
||||
background: rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.chat-message--assistant {
|
||||
@@ -457,24 +537,27 @@ body {
|
||||
}
|
||||
|
||||
.chat-message__role {
|
||||
font-size: 0.65rem;
|
||||
font-size: 0.62rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 3px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.chat-message__content {
|
||||
font-size: 0.82rem;
|
||||
line-height: 1.55;
|
||||
line-height: 1.65;
|
||||
white-space: pre-wrap;
|
||||
max-width: 65ch;
|
||||
}
|
||||
|
||||
.chat-message__meta {
|
||||
margin-top: 6px;
|
||||
font-size: 0.65rem;
|
||||
margin-top: 8px;
|
||||
font-size: 0.62rem;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
/* ---- Streaming Cursor ---- */
|
||||
@@ -483,22 +566,24 @@ body {
|
||||
display: inline;
|
||||
animation: blink 0.8s step-end infinite;
|
||||
color: var(--color-success);
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
/* ---- System Message ---- */
|
||||
|
||||
.system-message {
|
||||
text-align: center;
|
||||
padding: 8px 16px;
|
||||
margin: 0 20px;
|
||||
padding: 10px 16px;
|
||||
margin: 0 24px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.8rem;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.system-message--warning {
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
background: rgba(251, 191, 36, 0.08);
|
||||
color: var(--color-warning);
|
||||
border: 1px solid rgba(245, 158, 11, 0.2);
|
||||
border: 1px solid rgba(251, 191, 36, 0.15);
|
||||
}
|
||||
|
||||
/* ---- Drawer (Config Panel) ---- */
|
||||
@@ -507,9 +592,10 @@ body {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: blur(2px);
|
||||
animation: fadeIn 0.2s ease;
|
||||
background: rgba(10, 10, 15, 0.5);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
animation: fadeIn 0.25s var(--transition-smooth);
|
||||
}
|
||||
|
||||
.drawer {
|
||||
@@ -522,22 +608,23 @@ body {
|
||||
border-left: 1px solid var(--color-border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideInRight 0.25s ease;
|
||||
box-shadow: -8px 0 24px rgba(0, 0, 0, 0.3);
|
||||
animation: slideInRight 0.35s var(--transition-smooth);
|
||||
box-shadow: -8px 0 40px rgba(10, 10, 15, 0.5);
|
||||
}
|
||||
|
||||
.drawer__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 16px;
|
||||
padding: 16px 20px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.drawer__title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
font-size: 0.88rem;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.drawer__close {
|
||||
@@ -546,9 +633,12 @@ body {
|
||||
color: var(--color-text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
padding: 4px 8px;
|
||||
padding: 6px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all 0.15s;
|
||||
transition: all var(--transition-fast);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.drawer__close:hover {
|
||||
@@ -556,26 +646,30 @@ body {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.drawer__close:active {
|
||||
transform: scale(0.92);
|
||||
}
|
||||
|
||||
.drawer__body {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
/* ---- Config Group ---- */
|
||||
|
||||
.config-group {
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.config-group__title {
|
||||
font-size: 0.7rem;
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.8px;
|
||||
letter-spacing: 0.1em;
|
||||
color: var(--color-text-muted);
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 8px;
|
||||
margin-bottom: 14px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
@@ -583,16 +677,23 @@ body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 10px 0;
|
||||
padding: 10px 8px;
|
||||
margin: 0 -8px;
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-text);
|
||||
cursor: pointer;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: background var(--transition-fast);
|
||||
}
|
||||
|
||||
.config-row:hover {
|
||||
background: var(--color-surface-2);
|
||||
}
|
||||
|
||||
.config-row__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
gap: 3px;
|
||||
}
|
||||
|
||||
.config-row__label {
|
||||
@@ -601,8 +702,9 @@ body {
|
||||
}
|
||||
|
||||
.config-row__desc {
|
||||
font-size: 0.72rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--color-text-muted);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.config-row select,
|
||||
@@ -611,23 +713,36 @@ body {
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 5px 10px;
|
||||
font-size: 0.8rem;
|
||||
padding: 6px 10px;
|
||||
font-size: 0.78rem;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.config-row select:hover,
|
||||
.config-row input[type="checkbox"]:hover {
|
||||
border-color: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.config-row select:focus,
|
||||
.config-row input[type="checkbox"]:focus {
|
||||
outline: 2px solid rgba(59, 130, 246, 0.4);
|
||||
outline-offset: 1px;
|
||||
}
|
||||
|
||||
.config-row input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
cursor: pointer;
|
||||
accent-color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* ---- Toast ---- */
|
||||
|
||||
.toast-container {
|
||||
position: fixed;
|
||||
top: 68px;
|
||||
right: 20px;
|
||||
top: 72px;
|
||||
right: 24px;
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -637,16 +752,30 @@ body {
|
||||
.toast {
|
||||
padding: 10px 16px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.82rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
animation: slideIn 0.3s ease-out;
|
||||
animation: slideIn 0.35s var(--transition-smooth);
|
||||
max-width: 320px;
|
||||
backdrop-filter: blur(8px);
|
||||
backdrop-filter: blur(12px);
|
||||
-webkit-backdrop-filter: blur(12px);
|
||||
box-shadow: 0 8px 32px rgba(10, 10, 15, 0.4);
|
||||
}
|
||||
|
||||
.toast--error { background: rgba(239, 68, 68, 0.9); color: white; }
|
||||
.toast--warning { background: rgba(245, 158, 11, 0.9); color: #000; }
|
||||
.toast--info { background: rgba(37, 99, 235, 0.9); color: white; }
|
||||
.toast--error {
|
||||
background: rgba(248, 113, 113, 0.92);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.toast--warning {
|
||||
background: rgba(251, 191, 36, 0.92);
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.toast--info {
|
||||
background: rgba(59, 130, 246, 0.92);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ---- Buttons ---- */
|
||||
|
||||
@@ -654,18 +783,23 @@ body {
|
||||
padding: 8px 20px;
|
||||
border: none;
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 0.85rem;
|
||||
font-size: 0.82rem;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
transition: all var(--transition-fast);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.btn--lg {
|
||||
padding: 12px 32px;
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.92rem;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
@@ -685,43 +819,46 @@ body {
|
||||
}
|
||||
|
||||
.btn--secondary:hover {
|
||||
background: var(--color-border);
|
||||
background: var(--color-surface-3);
|
||||
border-color: var(--color-surface-3);
|
||||
}
|
||||
|
||||
.btn--warning {
|
||||
background: var(--color-warning);
|
||||
color: #000;
|
||||
color: #111;
|
||||
}
|
||||
|
||||
.btn--warning:hover { opacity: 0.9; }
|
||||
.btn--warning:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.btn--danger {
|
||||
background: rgba(239, 68, 68, 0.15);
|
||||
background: rgba(248, 113, 113, 0.1);
|
||||
color: var(--color-error);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border: 1px solid rgba(248, 113, 113, 0.2);
|
||||
}
|
||||
|
||||
.btn--danger:hover {
|
||||
background: rgba(239, 68, 68, 0.25);
|
||||
background: rgba(248, 113, 113, 0.18);
|
||||
}
|
||||
|
||||
.btn--active {
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
box-shadow: 0 0 12px rgba(37, 99, 235, 0.4);
|
||||
animation: pulseBtn 2s ease-in-out infinite;
|
||||
box-shadow: 0 0 12px rgba(59, 130, 246, 0.35);
|
||||
animation: pulseBtn 2s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
|
||||
}
|
||||
|
||||
/* ---- Animations ---- */
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.6; }
|
||||
50% { opacity: 0.55; }
|
||||
}
|
||||
|
||||
@keyframes pulseBtn {
|
||||
0%, 100% { box-shadow: 0 0 12px rgba(37, 99, 235, 0.4); }
|
||||
50% { box-shadow: 0 0 20px rgba(37, 99, 235, 0.7); }
|
||||
0%, 100% { box-shadow: 0 0 12px rgba(59, 130, 246, 0.35); }
|
||||
50% { box-shadow: 0 0 22px rgba(59, 130, 246, 0.6); }
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
@@ -734,7 +871,7 @@ body {
|
||||
}
|
||||
|
||||
@keyframes slideIn {
|
||||
from { opacity: 0; transform: translateX(20px); }
|
||||
from { opacity: 0; transform: translateX(16px); }
|
||||
to { opacity: 1; transform: translateX(0); }
|
||||
}
|
||||
|
||||
@@ -751,7 +888,7 @@ body {
|
||||
/* ---- Scrollbar ---- */
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
@@ -760,7 +897,7 @@ body {
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: var(--color-border);
|
||||
border-radius: 3px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
|
||||
@@ -108,8 +108,12 @@ function App() {
|
||||
className="btn-icon"
|
||||
onClick={() => setShowConfig((v) => !v)}
|
||||
title="设置"
|
||||
aria-label="设置"
|
||||
>
|
||||
⚙️
|
||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1-2.83 2.83l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-4 0v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83-2.83l.06-.06A1.65 1.65 0 0 0 4.68 15a1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1 0-4h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 2.83-2.83l.06.06A1.65 1.65 0 0 0 9 4.68a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 4 0v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 2.83l-.06.06A1.65 1.65 0 0 0 19.4 9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 0 4h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</header>
|
||||
@@ -161,8 +165,11 @@ function App() {
|
||||
)}
|
||||
{!isConnected && !stream && (
|
||||
<div className="video-placeholder">
|
||||
<span className="video-placeholder__icon">📷</span>
|
||||
<span>点击右侧按钮开始对话</span>
|
||||
<svg className="video-placeholder__icon" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M23 19a2 2 0 0 1-2 2H3a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4l2-3h6l2 3h4a2 2 0 0 1 2 2z" />
|
||||
<circle cx="12" cy="13" r="4" />
|
||||
</svg>
|
||||
<span>点击下方按钮开始对话</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -44,13 +44,17 @@ export function ChatPanel({ messages, currentReply, connectionStatus }: ChatPane
|
||||
if (connectionStatus !== "connected") {
|
||||
return (
|
||||
<div className="chat-panel chat-panel--empty">
|
||||
<span className="chat-panel--empty-icon">💬</span>
|
||||
<p>点击下方按钮开始对话</p>
|
||||
<span className="chat-panel--empty-hint">连接后可通过摄像头和麦克风与 AI 交互</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="chat-panel chat-panel--empty">
|
||||
<span className="chat-panel--empty-icon">🎙️</span>
|
||||
<p>对着摄像头说话即可</p>
|
||||
<span className="chat-panel--empty-hint">AI 会自动识别你的语音并分析画面</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user