fix padding

This commit is contained in:
2026-01-07 20:12:07 +02:00
parent 7dbba818db
commit b9da4dd56b
3 changed files with 865 additions and 121 deletions
+689
View File
@@ -0,0 +1,689 @@
# UI Design Improvement Guide for YvCode
> A comprehensive analysis and recommendations to modernize and enhance the user interface of YvCode - a lightweight code canvas for social media images.
---
## 📊 Current State Analysis
### Strengths
- ✅ Clean dark theme foundation with good contrast
- ✅ Modern glassmorphism effects on the toolbar
- ✅ Well-organized component structure (TopBar, Layers, Canvas, Inspector)
- ✅ Good use of subtle borders (`border-white/5`) and backgrounds (`bg-white/5`)
- ✅ Proper iconography with consistent stroke-based SVGs
- ✅ Keyboard shortcuts implemented throughout
### Areas for Improvement
- ⚠️ Limited visual hierarchy and depth
- ⚠️ Inconsistent spacing and component sizing
- ⚠️ Missing micro-interactions and animations
- ⚠️ Outdated form control styling
- ⚠️ No onboarding or empty states
- ⚠️ Missing visual feedback for user actions
---
## 🎨 Design System Recommendations
### 1. Color Palette Enhancement
**Current:** Basic neutral grays with blue accents
**Recommended:** Expanded semantic color palette
```css
/* Primary Colors */
--primary-50: #eff6ff;
--primary-100: #dbeafe;
--primary-400: #60a5fa;
--primary-500: #3b82f6;
--primary-600: #2563eb;
/* Surface Colors (for layered depth) */
--surface-0: #09090b; /* Deepest - sidebars */
--surface-1: #0f0f12; /* Base canvas area */
--surface-2: #18181b; /* Elevated cards */
--surface-3: #27272a; /* Interactive elements */
/* Semantic Colors */
--success: #22c55e;
--warning: #f59e0b;
--error: #ef4444;
--info: #06b6d4;
/* Glass Effects */
--glass-bg: rgba(255, 255, 255, 0.03);
--glass-border: rgba(255, 255, 255, 0.06);
--glass-highlight: rgba(255, 255, 255, 0.08);
```
### 2. Typography Scale
**Recommended Font System:**
```css
/* Headings */
--text-2xl: 1.5rem; /* Panel titles */
--text-xl: 1.25rem; /* Section headers */
--text-lg: 1.125rem; /* Subheadings */
/* Body */
--text-base: 0.875rem; /* Default UI text */
--text-sm: 0.8125rem; /* Secondary text */
--text-xs: 0.75rem; /* Labels, hints */
/* Line Heights */
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
```
### 3. Spacing System
Adopt an 8px grid system for consistent spacing:
```css
--space-1: 4px;
--space-2: 8px;
--space-3: 12px;
--space-4: 16px;
--space-5: 20px;
--space-6: 24px;
--space-8: 32px;
--space-10: 40px;
--space-12: 48px;
```
---
## 🧩 Component-Specific Improvements
### TopBar
**Current Issues:**
- Fixed positioning with `h-16` may feel cramped
- Logo and title area lacks breathing room
- Export dropdown uses hover instead of click
**Recommendations:**
1. **Increase Height & Visual Weight**
```tsx
// Change from h-16 to h-14 with better internal spacing
<div className="h-14 bg-surface-0/80 backdrop-blur-xl border-b border-white/[0.04]">
```
2. **Add Breadcrumb Navigation**
```tsx
<div className="flex items-center gap-2 text-sm">
<span className="text-neutral-500">Projects</span>
<ChevronRight className="w-3 h-3 text-neutral-600" />
<span className="text-white font-medium">{snap.meta.title}</span>
</div>
```
3. **Convert Export to Click-Triggered Dropdown with Animation**
- Use Radix UI or Headless UI for accessible dropdowns
- Add scale and fade animations on open/close
4. **Add Status Indicator**
```tsx
<div className="flex items-center gap-2">
<div className="w-1.5 h-1.5 rounded-full bg-green-500 animate-pulse" />
<span className="text-xs text-neutral-500">Auto-saved</span>
</div>
```
### Toolbar (Floating)
**Current Issues:**
- Good glassmorphism base but could be more refined
- Tool icons lack visual distinction when inactive
- Zoom controls feel disconnected
**Recommendations:**
1. **Enhanced Glass Effect**
```tsx
<div className="
absolute bottom-6 left-1/2 -translate-x-1/2
flex items-center gap-3 px-4 py-3
rounded-2xl
bg-gradient-to-b from-white/[0.08] to-white/[0.04]
backdrop-blur-2xl
border border-white/[0.08]
shadow-[0_8px_32px_rgba(0,0,0,0.4),_inset_0_1px_0_rgba(255,255,255,0.1)]
">
```
2. **Animated Tool Selection**
```tsx
// Add spring animation for tool selection indicator
<motion.div
layoutId="tool-indicator"
className="absolute inset-0 bg-primary-500 rounded-xl"
transition={{ type: "spring", stiffness: 500, damping: 30 }}
/>
```
3. **Tooltips with Shortcuts**
- Add tooltips that show on hover with keyboard shortcut badges
- Use consistent tooltip styling across the app
4. **Group Visual Separators**
- Use subtle vertical dividers with gradient fade
### Layers Panel
**Current Issues:**
- Minimal visual hierarchy
- Layer items lack drag handles or reordering indication
- Empty state is too plain
**Recommendations:**
1. **Add Drag & Drop Reordering**
- Use `@dnd-kit/sortable` for accessible drag-and-drop
- Add ghost element preview during drag
- Show insertion indicator line
2. **Enhanced Layer Item Design**
```tsx
<div className="
group relative
flex items-center gap-3 px-3 py-2.5
rounded-lg
bg-gradient-to-r from-transparent to-transparent
hover:from-white/[0.03] hover:to-transparent
border border-transparent
hover:border-white/[0.06]
cursor-pointer
transition-all duration-200
">
{/* Drag handle - visible on hover */}
<div className="
opacity-0 group-hover:opacity-100
cursor-grab active:cursor-grabbing
text-neutral-600 hover:text-neutral-400
">
<GripVertical className="w-3 h-3" />
</div>
{/* Layer thumbnail preview */}
<div className="w-8 h-8 rounded bg-white/5 flex items-center justify-center">
{/* Mini preview of element */}
</div>
{/* Content */}
<div className="flex-1 min-w-0">
<span className="text-sm text-neutral-200 truncate block">
{element.name}
</span>
<span className="text-xs text-neutral-500">
{element.type}
</span>
</div>
</div>
```
3. **Beautiful Empty State**
```tsx
<div className="flex flex-col items-center justify-center py-12 px-4">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-white/[0.06] to-white/[0.02] flex items-center justify-center mb-4">
<Layers className="w-8 h-8 text-neutral-600" />
</div>
<h4 className="text-sm font-medium text-neutral-400 mb-1">No layers yet</h4>
<p className="text-xs text-neutral-600 text-center">
Click on the canvas or use<br />
<kbd className="px-1.5 py-0.5 rounded bg-white/5 text-neutral-400">C</kbd>
<kbd className="px-1.5 py-0.5 rounded bg-white/5 text-neutral-400 mx-1">T</kbd>
<kbd className="px-1.5 py-0.5 rounded bg-white/5 text-neutral-400">A</kbd>
to add elements
</p>
</div>
```
### Inspector Panel
**Current Issues:**
- Dense information without clear sections
- Form controls need modernization
- No collapsible sections
**Recommendations:**
1. **Collapsible Accordion Sections**
```tsx
<Accordion type="multiple" defaultValue={['appearance', 'style']}>
<AccordionItem value="appearance">
<AccordionTrigger className="
flex items-center justify-between w-full py-3
text-xs font-semibold uppercase tracking-wider text-neutral-500
hover:text-neutral-300
">
Appearance
<ChevronDown className="w-4 h-4 transition-transform duration-200" />
</AccordionTrigger>
<AccordionContent>
{/* Content */}
</AccordionContent>
</AccordionItem>
</Accordion>
```
2. **Modern Input Fields**
```tsx
// Enhanced number input with stepper
<div className="
flex items-center
bg-white/[0.03] hover:bg-white/[0.05]
border border-white/[0.06] hover:border-white/[0.1]
rounded-lg
overflow-hidden
focus-within:ring-2 focus-within:ring-primary-500/30 focus-within:border-primary-500/50
transition-all
">
<input
type="number"
className="flex-1 bg-transparent px-3 py-2 text-sm text-white outline-none"
/>
<div className="flex flex-col border-l border-white/[0.06]">
<button className="px-2 py-1 hover:bg-white/[0.05]">
<ChevronUp className="w-3 h-3" />
</button>
<button className="px-2 py-1 hover:bg-white/[0.05] border-t border-white/[0.06]">
<ChevronDown className="w-3 h-3" />
</button>
</div>
</div>
```
3. **Enhanced Color Picker**
```tsx
// Modern color picker with swatches and opacity
<div className="space-y-3">
<div className="flex gap-2">
<div className="
relative w-10 h-10 rounded-lg overflow-hidden
border-2 border-white/10
shadow-lg shadow-black/20
">
{/* Checkerboard background for transparency */}
<div className="absolute inset-0 bg-checkerboard" />
<input type="color" className="absolute inset-0 w-full h-full cursor-pointer opacity-0" />
<div className="absolute inset-0" style={{ backgroundColor: color }} />
</div>
<div className="flex-1">
<input
type="text"
value={color}
className="w-full bg-white/[0.03] border border-white/[0.06] rounded-lg px-3 py-2 text-sm font-mono"
/>
</div>
</div>
{/* Recent colors */}
<div className="flex gap-1.5">
{recentColors.map(c => (
<button
key={c}
className="w-6 h-6 rounded-md border border-white/10 hover:scale-110 transition-transform"
style={{ backgroundColor: c }}
/>
))}
</div>
</div>
```
4. **Slider with Value Display**
```tsx
<div className="space-y-2">
<div className="flex justify-between">
<label className="text-xs font-medium text-neutral-500">Border Radius</label>
<span className="text-xs text-neutral-400 tabular-nums">{value}px</span>
</div>
<input
type="range"
className="
w-full h-1.5 rounded-full
bg-white/[0.06]
appearance-none
[&::-webkit-slider-thumb]:appearance-none
[&::-webkit-slider-thumb]:w-4
[&::-webkit-slider-thumb]:h-4
[&::-webkit-slider-thumb]:rounded-full
[&::-webkit-slider-thumb]:bg-white
[&::-webkit-slider-thumb]:shadow-lg
[&::-webkit-slider-thumb]:cursor-pointer
[&::-webkit-slider-thumb]:hover:scale-110
[&::-webkit-slider-thumb]:transition-transform
"
/>
</div>
```
### Canvas Area
**Current Issues:**
- Basic checkered or solid background
- No visual indicators for safe zones
- Selection handles could be more refined
**Recommendations:**
1. **Subtle Canvas Background Pattern**
```tsx
// Add subtle dot grid pattern to canvas workspace
<div className="
absolute inset-0
bg-[radial-gradient(circle_at_1px_1px,_rgba(255,255,255,0.03)_1px,_transparent_1px)]
bg-[size:24px_24px]
" />
```
2. **Enhanced Selection Handles**
```tsx
// Modern corner handles
<div className="
absolute w-3 h-3
bg-white
rounded-full
border-2 border-primary-500
shadow-lg shadow-primary-500/30
cursor-nwse-resize
hover:scale-125
transition-transform
" />
```
3. **Canvas Info Badge**
```tsx
// Show canvas size in corner
<div className="
absolute bottom-4 right-4
px-2 py-1
bg-black/50 backdrop-blur-sm
rounded-md
text-xs text-neutral-400
font-mono
">
{width} × {height}
</div>
```
---
## ✨ Micro-Interactions & Animations
### 1. Button Press Effects
```tsx
// Add satisfying press feedback
<button className="
transform
active:scale-95
transition-transform duration-75
">
```
### 2. Panel Transitions
```tsx
// Smooth panel content transitions
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -10 }}
transition={{ duration: 0.15 }}
>
```
### 3. Loading States
```tsx
// Skeleton loading for async operations
<div className="
animate-pulse
bg-gradient-to-r from-white/[0.03] via-white/[0.06] to-white/[0.03]
bg-[length:200%_100%]
animate-shimmer
" />
```
### 4. Success Feedback
```tsx
// Toast notification for exports
<div className="
fixed bottom-6 right-6
flex items-center gap-3
px-4 py-3
bg-green-500/10 backdrop-blur-xl
border border-green-500/20
rounded-xl
shadow-2xl
">
<CheckCircle className="w-5 h-5 text-green-400" />
<span className="text-sm text-green-200">Image exported successfully!</span>
</div>
```
---
## 🚀 Feature Enhancements
### 1. Onboarding Experience
**First-time user tooltip tour:**
- Highlight each major area with spotlight effect
- Short, contextual tips
- Skip option
```tsx
<div className="
absolute inset-0
bg-black/60 backdrop-blur-sm
z-50
">
{/* Spotlight cutout */}
<div className="
absolute
w-64 h-16
bg-transparent
rounded-2xl
ring-4 ring-primary-500
shadow-[0_0_0_9999px_rgba(0,0,0,0.6)]
" style={{ top: 0, left: 100 }}>
{/* Tooltip */}
<div className="absolute top-full mt-4 left-0 w-64 p-4 bg-surface-2 rounded-xl">
<h4 className="font-semibold text-white mb-1">Create & Export</h4>
<p className="text-sm text-neutral-400">Start a new project or export your creation in multiple formats.</p>
</div>
</div>
</div>
```
### 2. Command Palette (⌘K)
Add a spotlight-style command palette for power users:
```tsx
<div className="
fixed inset-0
flex items-start justify-center
pt-[20vh]
bg-black/50 backdrop-blur-sm
z-50
">
<div className="
w-full max-w-lg
bg-surface-1
border border-white/10
rounded-2xl
shadow-2xl
overflow-hidden
">
<input
type="text"
placeholder="Type a command or search..."
className="w-full px-5 py-4 bg-transparent text-white placeholder-neutral-500 outline-none"
/>
<div className="border-t border-white/5 max-h-80 overflow-auto">
{/* Command results */}
</div>
</div>
</div>
```
### 3. Quick Actions Contextual Menu
Right-click context menus with modern styling:
```tsx
<div className="
min-w-[200px]
bg-surface-2/95 backdrop-blur-xl
border border-white/10
rounded-xl
shadow-2xl
py-1
overflow-hidden
">
<button className="
w-full px-3 py-2
flex items-center gap-3
text-sm text-neutral-300
hover:bg-white/5 hover:text-white
transition-colors
">
<Copy className="w-4 h-4" />
Duplicate
<span className="ml-auto text-xs text-neutral-500">⌘D</span>
</button>
</div>
```
### 4. Preview Mode
Full-screen distraction-free preview:
```tsx
<div className="
fixed inset-0
bg-black
flex items-center justify-center
z-50
">
<button className="
absolute top-6 right-6
p-2
rounded-full
bg-white/10 hover:bg-white/20
text-white
transition-colors
">
<X className="w-5 h-5" />
</button>
{/* Canvas preview at actual size */}
<div className="shadow-2xl rounded-lg overflow-hidden">
{/* Rendered canvas */}
</div>
</div>
```
---
## 📱 Responsive Considerations
### Breakpoint Strategy
```css
/* Collapse side panels to bottom sheets on tablet */
@media (max-width: 1024px) {
.layers-panel,
.inspector-panel {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 40vh;
border-radius: 24px 24px 0 0;
transform: translateY(calc(100% - 48px));
transition: transform 0.3s ease;
}
.panel-open {
transform: translateY(0);
}
}
/* Hide panels completely on mobile - show as modals */
@media (max-width: 768px) {
.side-panels {
display: none;
}
}
```
---
## 🎯 Implementation Priority
### Phase 1: Quick Wins (1-2 days)
1. ✅ Update color palette and CSS variables
2. ✅ Enhance button/input hover states
3. ✅ Add micro-animations (scale, transitions)
4. ✅ Improve empty states
### Phase 2: Component Polish (3-5 days)
1. 🔲 Redesign Inspector panel with collapsible sections
2. 🔲 Enhance Layers panel with drag-and-drop
3. 🔲 Modernize all form controls
4. 🔲 Add toast notifications
### Phase 3: Advanced Features (1-2 weeks)
1. 🔲 Command palette (⌘K)
2. 🔲 Onboarding tour
3. 🔲 Context menus
4. 🔲 Preview mode
5. 🔲 Responsive adaptations
---
## 🛠️ Recommended Libraries
| Purpose | Library | Why |
|---------|---------|-----|
| Animations | `framer-motion` | Smooth, spring-based animations |
| UI Primitives | `@radix-ui/react-*` | Accessible, unstyled components |
| Drag & Drop | `@dnd-kit/core` | Modern, accessible DnD |
| Icons | `lucide-react` | Consistent, customizable icons |
| Tooltips | `@radix-ui/react-tooltip` | Accessible tooltips |
| Toasts | `sonner` | Beautiful toast notifications |
| Command Palette | `cmdk` | ⌘K style command menu |
---
## 📐 Visual Reference
### Inspiration Sources
- [Figma](https://figma.com) - Panel organization, inspector design
- [Linear](https://linear.app) - Command palette, animations
- [Raycast](https://raycast.com) - Glass effects, dark theme
- [Arc Browser](https://arc.net) - Sidebar design, animations
- [Vercel Dashboard](https://vercel.com) - Cards, buttons, clean typography
---
## Summary
The current YvCode UI has a solid foundation with its dark theme and component organization. The key improvements focus on:
1. **Visual Polish** - Enhanced glass effects, refined colors, better shadows
2. **Interaction Design** - Micro-animations, better feedback, modern controls
3. **Information Architecture** - Collapsible sections, better empty states, contextual help
4. **Power User Features** - Command palette, keyboard shortcuts display, context menus
Implementing these changes will transform YvCode into a professional-grade design tool that feels modern, responsive, and delightful to use.
+170 -109
View File
@@ -11,7 +11,9 @@ interface TopBarProps {
const TopBar: React.FC<TopBarProps> = ({ stageRef }) => {
const [showRecentSnaps, setShowRecentSnaps] = useState(false);
const [showExportMenu, setShowExportMenu] = useState(false);
const recentButtonRef = useRef<HTMLButtonElement>(null);
const exportButtonRef = useRef<HTMLButtonElement>(null);
const {
snap,
@@ -76,7 +78,9 @@ const TopBar: React.FC<TopBarProps> = ({ stageRef }) => {
link.download = `${snap.meta.title || 'canvas'}.${format}`;
link.href = dataUrl;
link.click();
}, [stageRef, snap.meta]);
setShowExportMenu(false);
addRecentSnap(snap);
}, [stageRef, snap, addRecentSnap]);
const handleExportJSON = useCallback(() => {
const json = exportSnap();
@@ -88,6 +92,7 @@ const TopBar: React.FC<TopBarProps> = ({ stageRef }) => {
link.click();
URL.revokeObjectURL(url);
setShowExportMenu(false);
// Save to recent snaps when exporting
addRecentSnap(snap);
}, [exportSnap, snap, addRecentSnap]);
@@ -120,81 +125,88 @@ const TopBar: React.FC<TopBarProps> = ({ stageRef }) => {
}, []);
return (
<div className="h-16 bg-[#09090b]/80 backdrop-blur-md border-b border-white/5 flex items-center justify-between px-6 z-40 fixed top-0 w-full">
{/* Left section: Logo & Actions */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-2 pr-4 border-r border-white/5">
<div className="w-8 h-8 rounded-xl bg-gradient-to-br from-blue-600 to-blue-700 shadow-lg shadow-blue-900/20 flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
<>
{/* Overlay for closing export menu */}
{showExportMenu && (
<div
className="fixed inset-0 z-40 bg-transparent"
onClick={() => setShowExportMenu(false)}
/>
)}
<div className="h-14 bg-[#09090b] border-b border-white/[0.08] flex items-center justify-between px-5 z-40 relative select-none">
{/* Left section: Logo & Actions */}
<div className="flex items-center gap-5">
<div className="flex items-center gap-3 pr-5 border-r border-white/[0.08]">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-blue-600 to-blue-700 shadow-lg shadow-blue-500/20 flex items-center justify-center">
<svg className="w-5 h-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2.5} d="M13 10V3L4 14h7v7l9-11h-7z" />
</svg>
</div>
<span className="font-bold text-base tracking-tight text-white">
YvCode
</span>
</div>
<span className="font-bold text-lg tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-white to-white/60">
YvCode
</span>
</div>
<div className="flex items-center gap-1">
<button
onClick={handleNewSnap}
className="p-2 rounded-lg text-neutral-400 hover:text-white hover:bg-white/5 transition-all active:scale-95"
title="New (⌘N)"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</button>
<button
onClick={handleImportJSON}
className="p-2 rounded-lg text-neutral-400 hover:text-white hover:bg-white/5 transition-all active:scale-95"
title="Open File (⌘O)"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z" />
</svg>
</button>
{/* Recent Snaps Button */}
<div className="relative">
<div className="flex items-center gap-1.5">
<button
ref={recentButtonRef}
onClick={toggleRecentSnaps}
className={`p-2 rounded-lg transition-all active:scale-95 ${
showRecentSnaps
? 'text-white bg-white/10'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
title="Recent Projects"
onClick={handleNewSnap}
className="w-8 h-8 flex items-center justify-center rounded-lg text-neutral-400 hover:text-white hover:bg-white/5 transition-all"
title="New (⌘N)"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</button>
<RecentSnapsDropdown
isOpen={showRecentSnaps}
onClose={() => setShowRecentSnaps(false)}
anchorRef={recentButtonRef as React.RefObject<HTMLElement>}
/>
<button
onClick={handleImportJSON}
className="w-8 h-8 flex items-center justify-center rounded-lg text-neutral-400 hover:text-white hover:bg-white/5 transition-all"
title="Open File (⌘O)"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 19a2 2 0 01-2-2V7a2 2 0 012-2h4l2 2h4a2 2 0 012 2v1M5 19h14a2 2 0 002-2v-5a2 2 0 00-2-2H9a2 2 0 00-2 2v5a2 2 0 01-2 2z" />
</svg>
</button>
<div className="relative">
<button
ref={recentButtonRef}
onClick={toggleRecentSnaps}
className={`w-8 h-8 flex items-center justify-center rounded-lg transition-all ${
showRecentSnaps
? 'text-white bg-white/10'
: 'text-neutral-400 hover:text-white hover:bg-white/5'
}`}
title="Recent Projects"
>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</button>
<RecentSnapsDropdown
isOpen={showRecentSnaps}
onClose={() => setShowRecentSnaps(false)}
anchorRef={recentButtonRef as React.RefObject<HTMLElement>}
/>
</div>
</div>
</div>
</div>
{/* Center section: Title & Tools */}
<div className="absolute left-1/2 -translate-x-1/2 flex items-center">
<div className="flex flex-col items-center">
{/* Center section: Title & Tools */}
<div className="absolute left-1/2 -translate-x-1/2 flex items-center gap-4">
<input
type="text"
value={snap.meta.title}
onChange={(e) => updateMeta({ title: e.target.value })}
className="bg-transparent text-sm font-medium text-center text-neutral-200 focus:text-white px-2 py-1 outline-none rounded hover:bg-white/5 focus:bg-white/10 transition-colors placeholder-neutral-600 w-48"
className="bg-transparent text-sm font-medium text-center text-neutral-200 focus:text-white px-3 py-1.5 outline-none rounded-md hover:bg-white/5 focus:bg-white/10 transition-colors placeholder-neutral-600 w-48 border border-transparent focus:border-white/10"
placeholder="Untitled Project"
/>
<div className="flex items-center gap-2 mt-0.5">
<span className="w-1.5 h-1.5 rounded-full bg-neutral-600" />
<div className="h-4 w-px bg-white/10" />
<div className="relative group/aspect">
<select
value={snap.meta.aspect}
onChange={handleAspectChange}
className="bg-transparent text-[10px] items-center uppercase tracking-wider font-semibold text-neutral-500 hover:text-neutral-300 outline-none cursor-pointer appearance-none text-center"
className="bg-transparent text-xs font-medium text-neutral-400 hover:text-white px-2 py-1 outline-none cursor-pointer appearance-none text-center transition-colors"
>
{ASPECT_RATIOS.map((ratio) => (
<option key={ratio.name} value={ratio.name}>
@@ -204,71 +216,120 @@ const TopBar: React.FC<TopBarProps> = ({ stageRef }) => {
</select>
</div>
</div>
</div>
{/* Right section: History & Export */}
<div className="flex items-center gap-3">
<div className="flex items-center gap-0.5 p-1 bg-white/5 rounded-lg border border-white/5">
<button
onClick={undo}
disabled={history.past.length === 0}
className="p-1.5 rounded text-neutral-400 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-30 disabled:hover:bg-transparent"
title="Undo (⌘Z)"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
</svg>
</button>
<button
onClick={redo}
disabled={history.future.length === 0}
className="p-1.5 rounded text-neutral-400 hover:text-white hover:bg-white/10 transition-colors disabled:opacity-30 disabled:hover:bg-transparent"
title="Redo (⇧⌘Z)"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 10h-10a8 8 0 00-8 8v2M21 10l-6 6m6-6l-6-6" />
</svg>
</button>
</div>
<div className="h-6 w-px bg-white/10" />
<div className="relative group">
<button
className="flex items-center gap-2 px-4 py-2 bg-white text-black hover:bg-blue-50 text-sm font-semibold rounded-lg shadow-lg shadow-white/5 transition-all active:scale-95"
>
<span>Export</span>
<svg className="w-4 h-4 text-neutral-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
<div className="absolute right-0 top-full mt-2 w-48 bg-[#09090b] border border-white/10 rounded-xl shadow-2xl p-1 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all z-50 transform origin-top-right">
<div className="px-3 py-2 text-xs font-semibold text-neutral-500 uppercase tracking-wider">Format</div>
{/* Right section: History & Export */}
<div className="flex items-center gap-4">
<div className="flex items-center gap-1">
<button
onClick={() => handleExportImage('png', 2)}
className="w-full text-left px-3 py-2 text-sm text-neutral-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors flex justify-between group/item"
onClick={undo}
disabled={history.past.length === 0}
className="w-8 h-8 flex items-center justify-center rounded-lg text-neutral-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:hover:bg-transparent active:scale-95"
title="Undo (⌘Z)"
>
<span>PNG Image</span>
<span className="bg-white/10 px-1.5 py-0.5 rounded text-[10px] text-neutral-400 group-hover/item:text-white">2x</span>
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6" />
</svg>
</button>
<button
onClick={() => handleExportImage('jpeg', 2)}
className="w-full text-left px-3 py-2 text-sm text-neutral-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
onClick={redo}
disabled={history.future.length === 0}
className="w-8 h-8 flex items-center justify-center rounded-lg text-neutral-400 hover:text-white hover:bg-white/10 transition-all disabled:opacity-30 disabled:hover:bg-transparent active:scale-95"
title="Redo (⇧⌘Z)"
>
JPEG Image
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 10h-10a8 8 0 00-8 8v2M21 10l-6 6m6-6l-6-6" />
</svg>
</button>
<div className="h-px bg-white/5 my-1" />
</div>
<div className="h-6 w-px bg-white/10" />
<div className="relative z-50">
<button
onClick={handleExportJSON}
className="w-full text-left px-3 py-2 text-sm text-neutral-300 hover:text-white hover:bg-white/10 rounded-lg transition-colors"
ref={exportButtonRef}
onClick={() => setShowExportMenu(!showExportMenu)}
className={`flex items-center gap-3 px-4 py-2 text-sm font-medium rounded-lg transition-all duration-200 border group ${
showExportMenu
? 'bg-blue-600/10 text-blue-400 border-blue-500/50 shadow-[0_0_20px_rgba(37,99,235,0.3)]'
: 'bg-white/5 text-neutral-300 hover:text-white hover:bg-white/10 border-white/5 hover:border-white/10'
}`}
>
Save Project JSON
<span>Export</span>
<div className={`p-0.5 rounded-md transition-all duration-200 ${showExportMenu ? 'bg-blue-500/20 text-blue-400' : 'bg-white/5 text-neutral-400 group-hover:text-white'}`}>
<svg className={`w-3.5 h-3.5 transition-transform duration-200 ${showExportMenu ? 'rotate-180' : ''}`} fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</button>
{showExportMenu && (
<div className="absolute right-0 top-full mt-3 w-72 bg-[#09090b]/95 backdrop-blur-2xl border border-white/10 rounded-xl shadow-2xl shadow-black/50 p-2 overflow-hidden animate-in fade-in zoom-in-95 duration-150 origin-top-right ring-1 ring-white/5 z-50">
<div className="px-3 py-2">
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest pl-1">Download Image</span>
</div>
<div className="grid grid-cols-1 gap-1.5 px-1">
<button
onClick={() => handleExportImage('png', 2)}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.08] border border-white/[0.02] hover:border-white/10 transition-all group active:scale-[0.98]"
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-blue-500/10 to-blue-600/10 border border-blue-500/20 text-blue-400 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<span className="font-bold text-[10px]">PNG</span>
</div>
<div className="flex flex-col items-start gap-0.5">
<span className="text-sm font-medium text-neutral-200 group-hover:text-white transition-colors">PNG Image</span>
<span className="text-[10px] text-neutral-500">High quality (2x)</span>
</div>
</div>
</button>
<button
onClick={() => handleExportImage('jpeg', 2)}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.08] border border-white/[0.02] hover:border-white/10 transition-all group active:scale-[0.98]"
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-purple-500/10 to-purple-600/10 border border-purple-500/20 text-purple-400 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<span className="font-bold text-[10px]">JPG</span>
</div>
<div className="flex flex-col items-start gap-0.5">
<span className="text-sm font-medium text-neutral-200 group-hover:text-white transition-colors">JPEG Image</span>
<span className="text-[10px] text-neutral-500">Standard quality</span>
</div>
</div>
</button>
</div>
<div className="h-px bg-gradient-to-r from-transparent via-white/10 to-transparent my-2" />
<div className="px-3 py-1.5">
<span className="text-[10px] font-bold text-neutral-500 uppercase tracking-widest pl-1">Project File</span>
</div>
<div className="px-1 pb-1">
<button
onClick={handleExportJSON}
className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/[0.03] hover:bg-white/[0.08] border border-white/[0.02] hover:border-white/10 transition-all group active:scale-[0.98]"
>
<div className="flex items-center gap-4">
<div className="w-10 h-10 rounded-lg bg-gradient-to-br from-yellow-500/10 to-yellow-600/10 border border-yellow-500/20 text-yellow-400 flex items-center justify-center group-hover:scale-110 transition-transform duration-300">
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" />
</svg>
</div>
<div className="flex flex-col items-start gap-0.5">
<span className="text-sm font-medium text-neutral-200 group-hover:text-white transition-colors">Save Project</span>
<span className="text-[10px] text-neutral-500">Edit later (.json)</span>
</div>
</div>
</button>
</div>
</div>
)}
</div>
</div>
</div>
</div>
</>
);
};
-6
View File
@@ -3,12 +3,6 @@
@import "tailwindcss";
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html, body, #root {
height: 100%;
width: 100%;