` rectangles to simulate a screenshot. Use real images, generated images, or skip the preview.
+* **NO broken Unsplash links.** Use `https://picsum.photos/seed/{descriptive-string}/{w}/{h}`, or generated photo placeholders, or actual assets.
+* **shadcn/ui customization:** Allowed, but NEVER in default state. Customize radii, colors, shadows, typography to the project aesthetic.
+* **Production-Ready Cleanliness:** Code visually clean, memorable, meticulously refined.
+
+### 9.F Production-Test Tells (banned outright)
+
+These patterns came out of real LLM-generated landing-page tests. They are the signatures the model defaults to when it tries to "look designed." Treat them as hard bans unless the brief explicitly calls for one.
+
+**Hero & top-of-page**
+* **NO version labels in the hero.** `V0.6`, `v2.0`, `BETA`, `INVITE-ONLY PREVIEW`, `EARLY ACCESS`, `ALPHA` - banned as default eyebrows. Only acceptable when the brief is explicitly about a product launch / preview status.
+* **NO "Brand · No. 01"-style sub-eyebrows.** "Marrow · No. 01 · The 6-quart" type micro-meta lines. Skip them.
+
+**Section numbering & micro-labels**
+* **NO section-number eyebrows.** `00 / INDEX`, `001 · Capabilities`, `002 · Featured commission`, `06 · how it works`, `05 · The honest table` - banned. Eyebrows should name the topic in plain language, not enumerate.
+* **NO `01 / 4`-style pagination on images or bento tiles.** If the user can count, they don't need the label.
+* **NO `Scroll · 001 Capabilities`-style scroll cues.** A simple arrow or "Scroll" is enough; no section-number prefix.
+* **NO "Index of Work, 2018 - 2026"-style range labels** as eyebrows. Just say what the section is.
+
+**Separators & dots**
+* **The middle-dot (`·`) is rationed.** Maximum 1 per line in metadata strips. Do NOT use it as the default separator for everything ("foo · bar · baz · qux · quux"). If you need a separator family, prefer line breaks, hairlines, or columns.
+* **NO decorative colored status dots on every list/nav/badge.** A colored dot before "ONE Q4 SLOT OPEN" or before every nav link, or every task row - banned by default. Acceptable only when the dot conveys actual semantic state (a server status, an availability flag) and is used sparingly.
+
+**Em-dashes & typography flourishes**
+* **NO em-dash (`—`) as a design element OR anywhere else.** See Section 9.G below for the complete, non-negotiable ban. The em-dash character is forbidden in headlines, eyebrows, pills, body copy, quotes, attribution, captions, button text, and alt text. Use the regular hyphen (`-`).
+* **NO `
`-broken-and-italicized headlines** as a default "design move." "for thirty\
*years.*" type splits. Headlines should read naturally first, get clever only when the brief demands it.
+* **NO vertical rotated text** ("INDEX OF WORK, 2018 - 2026" rotated 90°). Agency-portfolio cliché. Use it only when the brief is explicitly agency / Awwwards / experimental AND it serves a real composition purpose.
+* **NO crosshair / hairline grid lines as decoration.** Vertical and horizontal lines drawn just to make the page "feel designed" - banned. Use them only when they organize real content.
+
+**Fake product previews**
+* **NO div-based fake product UI in the hero** (fake task list, fake terminal, fake dashboard built from styled divs). It is the #1 LLM-design Tell. Use a real screenshot, a generated image, a real component preview, or none at all.
+* **NO fake version footers** ("v0.6.2-rc.1", "last sync 4s ago · main") inside fake screenshots. Adds nothing, screams AI.
+
+**Marketing-copy Tells**
+* **NO "Quietly in use at" / "Quietly trusted by"** social-proof headers. Use natural language: "Trusted by", "Used at", "Customers include", or skip the heading entirely if the logos speak.
+* **NO "From the field" / "Field notes" / "Currently on the bench" / "On our desks" / "Loose plates" style poetic labels** on quote, blog, or sidebar sections. Reads as performative-craftsman. Use plain functional labels ("Testimonials", "Latest writing", "Now working on") or skip the label.
+* **NO "We respect the French ones"-style** mock-humble industry-references in body copy. Cute and AI-y.
+* **NO weather / locale strips** ("LIS 14:23 · 18°C") in headers/footers unless the brief is explicitly about a place / time-zone-distributed studio.
+* **NO micro-meta-sentences under eyebrows.** Sentences like *"Each of these is a feature we ship today, not a roadmap promise. The list will stay short on purpose."* sitting under a section heading are clutter. Eyebrow + Headline + Body is enough.
+* **NO generic step labels.** "Stage 1 / Stage 2 / Stage 3", "Step 1 / Step 2 / Step 3", "Phase 01 / Phase 02 / Phase 03", "Pass One / Pass Two / Pass Three". Banned. The actual step content is the label. If you must show progression, use the verb-noun directly ("Install", "Configure", "Ship") not "Stage 1: Install".
+
+**Pills, labels and version stamps**
+* **NO pills/labels/tags overlaid on images.** No `
` overlays on photos with tags like `Brand · 02`, `PLATE · BRAND`, `Field notes - journal`. Either let the image speak alone, or add a caption directly below (outside the image).
+* **NO photo-credit captions as decoration.** Strings like `Field study no. 12 · Ines Caetano`, `Plate 03 · House archive`, `Frame XII · 35mm` under stock/picsum images are pretentious. Photo credit is allowed ONLY when there is a real photographer being credited for a real photo (with permission). Otherwise: skip the caption or use a one-line functional caption ("The 6-quart, in Sage.").
+* **NO version footers on marketing pages.** Footer strings like `v1.4.2`, `Build 0048`, `last sync 4s ago · main` are CLI / devtool fixtures, not landing-page content. Banned on marketing/landing/portfolio pages.
+* **NO "Reservation 412 of 800"-style live-stock counters** as decoration. Only if the brief is explicitly a limited-run waitlist with real data.
+
+**Decoration text strips**
+* **NO decoration text strip at hero bottom.** Patterns like `BRAND. MOTION. SPATIAL.`, `TYPE / FORM / MOTION`, `DESIGN · BUILD · SHIP`, `ESTD. 2018 · LISBON · BRAND. MOTION. SPATIAL.` as a small mono-caps strip across the bottom of the hero are an agency-portfolio cliché. Banned by default. Only acceptable when the strip carries real, navigable links (sticky bottom nav) or real status info (cookie banner, build info on a docs site).
+* **NO floating top-right sub-text in section headings.** Pattern: section has a giant left-aligned headline; in the top-right corner of the same section header there is a small explainer paragraph floating with no clear alignment to anything else. That floater is the Tell. Either put the sub-text directly under the headline, or build a clean 2-column header (left: headline, right: aligned body), but not a tiny corner paragraph.
+
+**Lists, dividers and scoring**
+* **NO `border-t` + `border-b` on every row of a long list / spec table.** Pick one (bottom-border between rows OR top-border above the group) and use it sparsely. A 10-row spec table with hairlines under each row is the laziest layout - see Section 4.9 for alternative UI components.
+* **NO scoring/progress bars with filled background tracks** as comparison visuals. If you need to show "X out of Y" comparisons, prefer a number + small icon, or a tiny inline bar WITHOUT a background track. Big filled `bg-zinc-200` tracks with a partial fill on top are dashboard-UI clutter on a landing page.
+
+**Locale, time, scroll cues**
+* **Locale / city-name / time / weather strips are banned for 99% of briefs.** "Lisbon, working with founders" in the hero, "1200-690 Lisbon, Portugal" in the footer, "Lisbon 14:23 · 18°C" in the nav. These are agency-portfolio decoration tells. Allowed ONLY when: the brief explicitly describes a globally-distributed studio with timezone-relevant work, OR a travel-focused brand, OR a real-world physical venue. A single contact-address mention in the footer is fine; an atmospheric locale strip is not.
+* **Scroll cues are banned.** `Scroll`, `↓ scroll`, `Scroll to explore`, `Scroll to walk through it`, animated mouse-wheel icons. If the user has not scrolled yet, they are looking at the hero. They know what scroll is. The bottom of the viewport does not need a label.
+* **ZERO decorative status dots by default.** A coloured dot before nav items, before list rows, before badges, before status labels is a Tell. Only acceptable when conveying real semantic state (a live indicator on actual server status, a live availability flag) and limited to one per page section.
+
+### 9.G EM-DASH BAN (the single most-violated Tell)
+
+**Em-dash (`—`) is COMPLETELY banned.** It is the LLM's signature stylistic crutch and it is the #1 visual Tell in production tests. There is no "limited use" allowance, no "natural language frequency" allowance, no "in body copy is fine" allowance. None.
+
+* **Banned in headlines.** Use a period or a comma.
+* **Banned in eyebrows / labels / pills / button text / image captions / nav items.** Replace with line breaks, columns, or hairlines.
+* **Banned in body copy.** Restructure the sentence: two sentences with a period, OR a comma, OR parentheses, OR a colon.
+* **Banned in quote attribution.** Use a normal hyphen with spaces (` - `) or a line break + smaller-weight name.
+* **Banned in en-dash form too (`–`) when used as a separator.** Date ranges (`2018-2026`) use a hyphen. Number ranges (`€40-80k`) use a hyphen.
+
+The ONLY permitted dash characters on the page are:
+* Regular hyphen `-` (for compound words, ranges, line dividers in markup)
+* Minus sign in math (`-5°C`)
+
+If your output contains a single `—` or `–` anywhere visible to the user, the output fails the Pre-Flight Check and must be rewritten.
+
+This rule is non-negotiable. The agent has historically ignored em-dash limits when phrased as "use sparingly." The phrasing here is binary: zero em-dashes.
+
+---
+
+## 10. REFERENCE VOCABULARY (Pattern Names the Agent Should Know)
+
+This is a vocabulary, not a library. The agent should KNOW these pattern names to communicate about them, design with them in mind, and reach for them when the design read calls for them. **Implementations and code sketches live in the Block Library (Section 12), which is populated iteratively.**
+
+### Hero Paradigms
+* **Asymmetric Split Hero** - Text on one side, asset on the other, generous white space.
+* **Editorial Manifesto Hero** - Large type, no asset, almost-poster.
+* **Video / Media Mask Hero** - Type cut out as mask over video background.
+* **Kinetic-Type Hero** - Animated typography as the primary visual.
+* **Curtain-Reveal Hero** - Hero parts on scroll like a curtain.
+* **Scroll-Pinned Hero** - Hero stays pinned while content scrolls behind.
+
+### Navigation & Menus
+* **Mac OS Dock Magnification** - Edge nav, icons scale fluidly on hover.
+* **Magnetic Button** - Pulls toward cursor.
+* **Gooey Menu** - Sub-items detach like viscous liquid.
+* **Dynamic Island** - Morphing pill for status / alerts.
+* **Contextual Radial Menu** - Circular menu expanding at click point.
+* **Floating Speed Dial** - FAB springing into curved secondary actions.
+* **Mega Menu Reveal** - Full-screen dropdown, stagger-fade content.
+
+### Layout & Grids
+* **Bento Grid** - Asymmetric tile grouping (Apple Control Center).
+* **Masonry Layout** - Staggered grid, no fixed row height.
+* **Chroma Grid** - Borders / tiles with subtle animating gradients.
+* **Split-Screen Scroll** - Two halves sliding in opposite directions.
+* **Sticky-Stack Sections** - Sections that pin and stack on scroll.
+
+### Cards & Containers
+* **Parallax Tilt Card** - 3D tilt tracking mouse coordinates.
+* **Spotlight Border Card** - Borders illuminate under cursor.
+* **Glassmorphism Panel** - Frosted glass with inner refraction.
+* **Holographic Foil Card** - Iridescent rainbow shift on hover.
+* **Tinder Swipe Stack** - Physical card stack, swipe-away.
+* **Morphing Modal** - Button expands into its own dialog.
+
+### Scroll Animations
+* **Sticky Scroll Stack** - Cards stick and physically stack.
+* **Horizontal Scroll Hijack** - Vertical scroll → horizontal pan.
+* **Locomotive / Sequence Scroll** - Video / 3D sequence tied to scrollbar.
+* **Zoom Parallax** - Central background image zooming on scroll.
+* **Scroll Progress Path** - SVG line drawing along scroll.
+* **Liquid Swipe Transition** - Page transition like viscous liquid.
+
+### Galleries & Media
+* **Dome Gallery** - 3D panoramic gallery.
+* **Coverflow Carousel** - 3D carousel with angled edges.
+* **Drag-to-Pan Grid** - Boundless draggable canvas.
+* **Accordion Image Slider** - Narrow strips expanding on hover.
+* **Hover Image Trail** - Mouse leaves popping image trail.
+* **Glitch Effect Image** - RGB-channel shift on hover.
+
+### Typography & Text
+* **Kinetic Marquee** - Endless text bands reversing on scroll.
+* **Text Mask Reveal** - Massive type as transparent window to video.
+* **Text Scramble Effect** - Matrix-style decoding on load / hover.
+* **Circular Text Path** - Text curving along spinning circle.
+* **Gradient Stroke Animation** - Outlined text with running gradient.
+* **Kinetic Typography Grid** - Letters dodging the cursor.
+
+### Micro-Interactions & Effects
+* **Particle Explosion Button** - CTA shatters into particles on success.
+* **Liquid Pull-to-Refresh** - Reload indicator like detaching droplets.
+* **Skeleton Shimmer** - Shifting light reflection across placeholders.
+* **Directional Hover-Aware Button** - Fill enters from cursor's exact side.
+* **Ripple Click Effect** - Wave from click coordinates.
+* **Animated SVG Line Drawing** - Vectors drawing themselves in real time.
+* **Mesh Gradient Background** - Organic lava-lamp blobs.
+* **Lens Blur Depth** - Background UI blurred to focus foreground action.
+
+### Animation Library Choice
+* **Motion (`motion/react`)** - default for UI / Bento / state-change motion.
+* **GSAP + ScrollTrigger** - for full-page scrolltelling and scroll hijacks. Isolate in dedicated leaf components with `useEffect` cleanup.
+* **Three.js / WebGL** - for canvas backgrounds and 3D scenes. Same isolation rule.
+* **NEVER mix GSAP / Three.js with Motion in the same component tree.** They fight over the same frames.
+
+---
+
+## 11. REDESIGN PROTOCOL
+
+This skill handles **greenfield builds AND redesigns**. Misclassifying the mode is the single biggest source of bad redesign output.
+
+### 11.A Detect the Mode (first action)
+* **Greenfield** - no existing site, or full overhaul approved. Dial baseline from Section 1.
+* **Redesign - Preserve** - modernise without breaking the brand. Audit first, extract brand tokens, evolve gradually.
+* **Redesign - Overhaul** - new visual language on top of existing content. Treat as greenfield for visuals; preserve content and IA.
+
+If ambiguous, ask **once**: *"Should this redesign preserve the existing brand, or are we starting visually from scratch?"*
+
+### 11.B Audit Before Touching
+Document the current state before proposing changes:
+* **Brand tokens** - primary / accent colors, type stack, logo treatment, radii.
+* **Information architecture** - page tree, primary nav, key conversion paths.
+* **Content blocks** - what exists, what's doing work, what's filler.
+* **Patterns to preserve** - signature interactions, recognisable hero, copy voice.
+* **Patterns to retire** - AI-slop tells, broken layouts, dead links, generic stock imagery, perf traps.
+* **Dial reading of the existing site** - infer current `DESIGN_VARIANCE` / `MOTION_INTENSITY` / `VISUAL_DENSITY`. That's your starting point, not the baseline.
+* **SEO baseline** - current ranking pages, meta titles, structured data, OG cards. **SEO migration is the #1 redesign risk.**
+
+### 11.C Preservation Rules
+* **Do not change information architecture** unless asked. Keep page slugs, anchor IDs, primary nav labels stable for SEO and muscle memory.
+* **Extract brand colors before applying Section 4.2.** A brand that is already purple stays purple - apply the LILA RULE's override.
+* **Preserve copy voice** unless asked for a rewrite. Visual modernisation ≠ content rewrite.
+* **Honor existing accessibility wins.** Do not regress focus states, alt text, keyboard nav, contrast.
+* **Respect existing analytics events.** Do not rename buttons, form fields, section IDs that downstream tracking depends on.
+
+### 11.D Modernisation Levers (priority order)
+Apply in order - stop when the brief is satisfied:
+1. **Typography refresh** - biggest visual lift per unit of risk.
+2. **Spacing & rhythm** - increase section padding, fix vertical rhythm.
+3. **Color recalibration** - desaturate, unify neutrals, keep brand accent.
+4. **Motion layer** - add `MOTION_INTENSITY`-appropriate micro-interactions to existing components.
+5. **Hero & key-section recomposition** - restructure top-of-funnel using Section 10 vocabulary.
+6. **Full block replacement** - only when the existing block is unsalvageable.
+
+### 11.E Decision Tree: Targeted Evolution vs Full Redesign
+* IA, content, and SEO sound → **targeted evolution** (Levers 1-4). ~70% of value at ~40% of risk.
+* Visual debt is structural (broken IA, no design system, broken mobile) → **full redesign** with strict content preservation.
+* Brand itself is changing → **greenfield**.
+
+### 11.F What Never Changes Silently
+Never modify without explicit user approval:
+* URL structure / route slugs.
+* Primary nav labels.
+* Form field names or order (breaks analytics + autofill).
+* Brand logo or wordmark.
+* Existing legal / consent / cookie copy.
+
+---
+
+## 12. THE BLOCK LIBRARY (Contract - Implementations Land Here Iteratively)
+
+The Reference Vocabulary (Section 10) names patterns. The Block Library implements them with real props, real motion specs, and real code sketches.
+
+**Status:** schema defined here. Blocks will be added iteratively. Do not freelance new blocks without following this schema.
+
+### 12.A File Location
+```
+skills/taste-skill/blocks/
+ hero/
+ asymmetric-split.md
+ editorial-manifesto.md
+ kinetic-type.md
+ ...
+ feature/
+ bento-grid.md
+ sticky-scroll-stack.md
+ zig-zag.md
+ ...
+ social-proof/
+ pricing/
+ cta/
+ footer/
+ navigation/
+ portfolio/
+ transition/
+```
+
+### 12.B Required Frontmatter
+```yaml
+---
+name: asymmetric-split-hero
+category: hero
+dial_compatibility:
+ variance: [6, 10]
+ motion: [3, 10]
+ density: [2, 5]
+when_to_use: "Landing pages with one strong asset and one strong message. Default hero for SaaS, agency, premium consumer."
+not_for: "Editorial / manifesto launches where the message IS the design."
+stack: ["react", "next", "tailwind", "motion"]
+---
+```
+
+### 12.C Required Body Sections
+1. **Visual sketch** - short ASCII or description of the layout.
+2. **Props API** - the component's interface.
+3. **Code sketch** - minimal working implementation (Server Component default, Client island for motion).
+4. **Mobile fallback** - explicit collapse rules for `< 768px`.
+5. **Motion variants** - one variant per `MOTION_INTENSITY` band (1-3, 4-7, 8-10). Reduced-motion fallback explicit.
+6. **Dark-mode notes** - token strategy specific to this block.
+7. **Anti-patterns** - common ways this block goes wrong.
+8. **References** - links to real examples in production.
+
+### 12.D Block-Library Discipline
+* One block per file. No multi-block files.
+* Every block must work standalone (drop it into a page, it renders).
+* Every block must pass the Pre-Flight Check (Section 14).
+* Blocks that depend on a design system from Section 2.A live under `blocks//--.md` (e.g. `feature/bento-grid--material.md`).
+
+---
+
+## 13. OUT OF SCOPE
+
+This skill is NOT for:
+* Dashboards / dense product UI / admin panels (use Fluent, Carbon, Atlassian, or Polaris from Section 2.A).
+* Data tables (use TanStack Table or AG Grid).
+* Multi-step forms / wizards (use Form-specific patterns; this skill won't make them better).
+* Code editors (use Monaco / CodeMirror with their official skinning).
+* Native mobile (use Apple HIG / Material directly).
+* Realtime collab UIs (presence, cursors, OT-aware - different problem class).
+
+If the brief is one of the above, **say so explicitly**, point to the right tool, and only apply this skill's marketing-page / about-page / landing-page parts to the surfaces where they apply.
+
+---
+
+## 14. FINAL PRE-FLIGHT CHECK
+
+Run this matrix before outputting code. This is the last filter.
+
+**THIS IS NOT OPTIONAL. Run every box. If any box fails, the output is not done.**
+
+- [ ] **Brief inference** declared (Section 0.B one-liner)?
+- [ ] **Dial values** explicit and reasoned from the brief, not silently using baseline?
+- [ ] **Design system** chosen from Section 2 if applicable, or aesthetic labeled honestly?
+- [ ] **Redesign mode** detected and audit performed (if applicable, Section 11)?
+- [ ] **ZERO em-dashes (`—`) anywhere on the page.** Headlines, eyebrows, pills, body, quotes, attribution, captions, buttons, alt text. Zero. (Section 9.G - non-negotiable.)
+- [ ] **Page Theme Lock**: ONE theme (light, dark, or auto) for the whole page. No section flips to inverted mode mid-page (Section 4.11)?
+- [ ] **Color Consistency Lock**: one accent color used identically across all sections (Section 4.2)?
+- [ ] **Shape Consistency Lock**: one corner-radius system applied consistently (Section 4.4)?
+- [ ] **Button Contrast Check**: every CTA text is readable against its background (no white-on-white, WCAG AA 4.5:1)?
+- [ ] **CTA Button Wrap**: no CTA label wraps to 2+ lines at desktop?
+- [ ] **Form Contrast Check**: form inputs, placeholders, focus rings, labels all pass WCAG AA against the section background?
+- [ ] **Serif discipline**: if a serif is used, it is NOT Fraunces or Instrument_Serif (or it is, with explicit brand justification)? Different serif from your previous project?
+- [ ] **Premium-consumer palette check**: if the brief is premium-consumer (cookware / wellness / artisan / luxury), the palette is NOT the AI-default beige+brass+oxblood+espresso family? Different family from your previous premium-consumer project?
+- [ ] **Italic descender clearance**: every italic word with `y g j p q` has `leading-[1.1]` min + `pb-1` reserve?
+- [ ] **Hero fits the viewport**: headline ≤ 2 lines, subtext ≤ 20 words AND ≤ 4 lines, CTA visible without scroll, font scale planned around image?
+- [ ] **Hero top padding**: max `pt-24` at desktop, hero content does not float halfway down the viewport?
+- [ ] **Hero stack discipline**: max 4 text elements in hero (eyebrow OR brand strip, headline, subtext, CTAs)? No tiny tagline below CTAs, no trust micro-strip in hero?
+- [ ] **EYEBROW COUNT (mechanical)**: count instances of `uppercase tracking` micro-labels above section headlines across all components. Count ≤ ceil(sectionCount / 3)? Hero counts as 1.
+- [ ] **Split-Header Ban**: no "left big headline + right small explainer paragraph" pattern as a section header (vertical stack instead)?
+- [ ] **Zigzag Alternation Cap**: no 3+ consecutive sections with the same image+text-split layout?
+- [ ] **No Duplicate CTA Intent**: no two CTAs with the same intent ("Get in touch" + "Let's talk" both on page = Fail)?
+- [ ] **Logo wall = logo only**: no industry / category labels printed below logos?
+- [ ] **Bento Background Diversity**: at least 2-3 bento cells have real visual variation (image, gradient, pattern), not all white-on-white text cards?
+- [ ] **"Used by / Trusted by" logo wall** lives UNDER the hero, not inside it, uses REAL SVG logos (Simple Icons / devicon) or generated SVG marks, NOT plain text wordmarks?
+- [ ] **Copy Self-Audit**: every visible string re-read, no grammatically-broken or AI-hallucinated phrases ("free on its past" type) shipped?
+- [ ] **Motion motivated**: every animation can be justified in one sentence (hierarchy / storytelling / feedback / state transition), no GSAP-for-show?
+- [ ] **Marquee max-one-per-page**: no two horizontal marquees on the same page?
+- [ ] **Navigation on ONE line** at desktop, height ≤ 80px?
+- [ ] **Section-Layout-Repetition** check: no two sections share the same layout family (at least 4 different families across 8 sections)?
+- [ ] **Bento has rhythm AND exact cell count** (N items → N cells, no empty cells in middle or at end)?
+- [ ] **Long lists use the right UI component** (not default `` with `divide-y` for > 5 items - see Section 4.9 alternatives)?
+- [ ] **Real images used** (gen-tool first, then Picsum-seed, then explicit placeholder slots) - NO div-based fake screenshots, NO hand-rolled decorative SVGs, NO pure-text minimalism?
+- [ ] **No pills/labels overlaid on images** (no `Plate · Brand`, no `Field notes - journal`)?
+- [ ] **No photo-credit captions as decoration** (`Field study no. 12 · Ines Caetano`)?
+- [ ] **No version footers** (`v1.4.2`, `Build 0048`) on marketing pages?
+- [ ] **No micro-meta-sentences** under eyebrows ("Each of these is a feature we ship today...")?
+- [ ] **No decoration text strip at hero bottom** (`BRAND. MOTION. SPATIAL.`)?
+- [ ] **No floating top-right sub-text** in section headings?
+- [ ] **No scoring/progress bars with filled background tracks** as comparison visuals?
+- [ ] **No locale / city-name / time / weather strips** unless brief is genuinely globally-distributed or place-focused?
+- [ ] **No scroll cues** (`Scroll`, `↓ scroll`, `Scroll to explore`)?
+- [ ] **No version labels in hero** (V0.6, BETA, INVITE-ONLY) unless the brief is a launch?
+- [ ] **No section-numbering eyebrows** (`00 / INDEX`, `001 · Capabilities`, `06 · how it works`)?
+- [ ] **No decorative dots** (zero by default, only for real semantic state)?
+- [ ] **No `border-t` + `border-b` on every row** of long lists / spec tables?
+- [ ] **Content density** sane: no 20-row data tables, no fake-precise specs without justification, ≤ 25-word sub-paragraphs by default?
+- [ ] **Quotes ≤ 3 lines** of body, attribution clean (no em-dash)?
+- [ ] **Motion claimed = motion shown**: if `MOTION_INTENSITY > 4`, page actually animates, not just claimed?
+- [ ] **GSAP sticky-stack / horizontal-pan** implemented per Section 5.A / 5.B canonical skeleton (`start: "top top"`, `pin: true`, correct scrub)?
+- [ ] **No `window.addEventListener('scroll')`** - using Motion `useScroll()` / ScrollTrigger / IntersectionObserver / CSS scroll-driven animations only?
+- [ ] **Reduced motion** wrapped for everything `MOTION_INTENSITY > 3`?
+- [ ] **Dark mode** tokens defined and tested in both modes?
+- [ ] **Mobile collapse** explicit (`w-full`, `px-4`, `max-w-7xl mx-auto`) for high-variance layouts?
+- [ ] **Viewport stability**: `min-h-[100dvh]`, never `h-screen`?
+- [ ] **`useEffect` animations** have strict cleanup functions?
+- [ ] **Empty / loading / error** states provided?
+- [ ] **Cards omitted** in favor of spacing where possible?
+- [ ] **Icons** from an allowed library only (Phosphor / HugeIcons / Radix / Tabler), no hand-rolled SVG paths?
+- [ ] **Motion** isolated in client-leaf components with `'use client'` at the top, memoized?
+- [ ] **No AI Tells** from Section 9 (Inter as default, AI-purple, three-equal cards, Jane Doe, Acme, "Quietly in use at")?
+- [ ] **Core Web Vitals** plausibly hit (LCP < 2.5s, INP < 200ms, CLS < 0.1)?
+- [ ] **One design system** per project (no Material + shadcn mixed)?
+
+If a single checkbox cannot be honestly ticked, the page is not done. Fix it before delivering.
+
+---
+
+# APPENDICES - Real Source-Backed Reference Material
+
+The sections below are vendored reference content. They give the agent real install commands, real canonical doc links, and real working starter snippets for each design system named in Section 2. Use them to ground decisions in production reality, not training-data fiction.
+
+## Appendix A - Install Commands per Design System
+
+```bash
+# Material Web (Material 3)
+npm install @material/web
+
+# Fluent UI React (v9)
+npm install @fluentui/react-components
+
+# Fluent UI Web Components (framework-free)
+npm install @fluentui/web-components @fluentui/tokens
+
+# IBM Carbon
+npm install @carbon/react @carbon/styles
+
+# Radix Themes
+npm install @radix-ui/themes
+
+# shadcn/ui (open code, owned components)
+npx shadcn@latest init
+npx shadcn@latest add button card badge separator input
+
+# Primer CSS (GitHub product/devtool UI)
+npm install --save @primer/css
+
+# Primer Brand (GitHub marketing UI)
+npm install @primer/react-brand
+
+# GOV.UK Frontend
+npm install govuk-frontend
+
+# USWDS (US Web Design System)
+npm install uswds
+
+# Atlassian Design System (Atlaskit)
+yarn add @atlaskit/css-reset @atlaskit/tokens @atlaskit/button @atlaskit/badge @atlaskit/section-message @atlaskit/card
+
+# Bootstrap 5.3
+npm install bootstrap
+
+# Shopify Polaris Web Components (Shopify apps only)
+# Add this to your app HTML head:
+#
+#
+```
+
+## Appendix B - Canonical Sources (read these before reinventing)
+
+### Material Web
+- https://github.com/material-components/material-web
+- https://material-web.dev/theming/material-theming/
+- https://m3.material.io/develop/web
+
+### Fluent UI
+- https://fluent2.microsoft.design/get-started/develop
+- https://fluent2.microsoft.design/components/web/react/
+- https://github.com/microsoft/fluentui
+- https://learn.microsoft.com/en-us/fluent-ui/web-components/
+
+### Carbon
+- https://carbondesignsystem.com/
+- https://github.com/carbon-design-system/carbon
+- https://carbondesignsystem.com/developing/react-tutorial/overview/
+- https://carbondesignsystem.com/developing/web-components-tutorial/overview/
+
+### Shopify Polaris
+- https://shopify.dev/docs/api/app-home/web-components
+- https://github.com/Shopify/polaris-react
+- https://polaris-react.shopify.com/components
+
+### Atlassian
+- https://atlassian.design/get-started/develop
+- https://atlassian.design/components/button/examples
+- https://atlaskit.atlassian.com/packages/design-system/button/example/disabled
+- https://atlassian.design/tokens/design-tokens
+
+### Primer
+- https://primer.style/
+- https://github.com/primer/css
+- https://github.com/primer/brand
+
+### GOV.UK
+- https://design-system.service.gov.uk/components/button/
+- https://design-system.service.gov.uk/styles/layout/
+- https://github.com/alphagov/govuk-frontend
+
+### USWDS
+- https://designsystem.digital.gov/documentation/developers/
+- https://designsystem.digital.gov/components/button/
+- https://designsystem.digital.gov/components/card/
+- https://github.com/uswds/uswds
+
+### Bootstrap
+- https://getbootstrap.com/docs/5.3/layout/grid/
+- https://getbootstrap.com/docs/5.3/components/card/
+
+### Tailwind
+- https://tailwindcss.com/docs/dark-mode
+- https://tailwindcss.com/blog/tailwindcss-v4
+
+### Radix
+- https://www.radix-ui.com/themes/docs/components/theme
+- https://www.radix-ui.com/themes/docs/components/card
+- https://github.com/radix-ui/themes
+
+### shadcn/ui
+- https://ui.shadcn.com/docs
+- https://ui.shadcn.com/docs/components/card
+- https://github.com/shadcn-ui/ui
+
+### Native CSS / W3C standards
+- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/backdrop-filter
+- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-color-scheme
+- https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion
+- https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Grid_layout
+- https://developer.mozilla.org/en-US/docs/Web/CSS/Guides/Scroll-driven_animations
+- https://drafts.csswg.org/scroll-animations-1/
+
+### Apple Liquid Glass (Apple platforms only)
+- https://developer.apple.com/design/human-interface-guidelines/materials
+- https://developer.apple.com/documentation/TechnologyOverviews/liquid-glass
+- https://developer.apple.com/documentation/TechnologyOverviews/adopting-liquid-glass
+- https://developer.apple.com/documentation/SwiftUI/Material
+
+---
+
+## Appendix C - Apple Liquid Glass: Honest Web Approximation
+
+Do **not** treat random CSS snippets as official Apple Liquid Glass.
+
+### What is official
+Apple documents Liquid Glass inside Apple's Human Interface Guidelines and Developer Documentation for **Apple platforms**. It is a dynamic material used across Apple platform UI. Apple's native implementation belongs to Apple platform APIs and system components, **not a public web CSS package**.
+
+Relevant official docs:
+- Apple Human Interface Guidelines → Materials
+- Apple Developer Documentation → Liquid Glass
+- Apple Developer Documentation → Adopting Liquid Glass
+- SwiftUI → Material
+
+### What is NOT official
+There is no `liquid-glass.css` from Apple for normal websites.
+
+A web approximation can use:
+- `backdrop-filter`
+- transparent backgrounds
+- layered borders
+- highlight overlays
+- gradients
+- motion
+- strong contrast fallbacks
+
+But that is **web glassmorphism / frosted-glass approximation**, not official Apple Liquid Glass. Label it as such in comments.
+
+### Safer web approximation skeleton
+
+```css
+.liquid-glass-web-approx {
+ position: relative;
+ isolation: isolate;
+ overflow: hidden;
+ border-radius: 999px;
+ border: 1px solid rgb(255 255 255 / .32);
+ background:
+ linear-gradient(135deg, rgb(255 255 255 / .30), rgb(255 255 255 / .08)),
+ rgb(255 255 255 / .12);
+ backdrop-filter: blur(24px) saturate(180%) contrast(1.05);
+ -webkit-backdrop-filter: blur(24px) saturate(180%) contrast(1.05);
+ box-shadow:
+ inset 0 1px 0 rgb(255 255 255 / .48),
+ inset 0 -1px 0 rgb(255 255 255 / .12),
+ 0 18px 60px rgb(0 0 0 / .18);
+}
+
+.liquid-glass-web-approx::before {
+ content: "";
+ position: absolute;
+ inset: 0;
+ z-index: -1;
+ border-radius: inherit;
+ background:
+ radial-gradient(circle at 20% 0%, rgb(255 255 255 / .55), transparent 34%),
+ linear-gradient(90deg, rgb(255 255 255 / .18), transparent 42%, rgb(255 255 255 / .14));
+ pointer-events: none;
+}
+
+.liquid-glass-web-approx::after {
+ content: "";
+ position: absolute;
+ inset: 1px;
+ border-radius: inherit;
+ border: 1px solid rgb(255 255 255 / .14);
+ pointer-events: none;
+}
+
+@media (prefers-color-scheme: dark) {
+ .liquid-glass-web-approx {
+ border-color: rgb(255 255 255 / .18);
+ background:
+ linear-gradient(135deg, rgb(255 255 255 / .16), rgb(255 255 255 / .04)),
+ rgb(15 23 42 / .42);
+ box-shadow:
+ inset 0 1px 0 rgb(255 255 255 / .22),
+ 0 18px 60px rgb(0 0 0 / .42);
+ }
+}
+
+@media (prefers-reduced-transparency: reduce) {
+ .liquid-glass-web-approx {
+ background: rgb(255 255 255 / .96);
+ backdrop-filter: none;
+ -webkit-backdrop-filter: none;
+ }
+}
+```
+
+**Important:** `prefers-reduced-transparency` has uneven browser support; test it. Always provide enough contrast even without blur.
+
+---
+
+**End of appendices.** Install commands above are reality anchors. The Apple Liquid Glass skeleton is a labeled approximation, not an Apple-issued package. For canonical docs per design system, consult the system's official docs (links in Section 2 plus Appendix B).
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 330c48c..fdfb826 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -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 {
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index ffd567b..0ef33b5 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -108,8 +108,12 @@ function App() {
className="btn-icon"
onClick={() => setShowConfig((v) => !v)}
title="设置"
+ aria-label="设置"
>
- ⚙️
+
+
+
+
@@ -161,8 +165,11 @@ function App() {
)}
{!isConnected && !stream && (