2025-11-20 21:12:38 +01:00
|
|
|
@import "tailwindcss";
|
|
|
|
|
|
|
|
|
|
/* Source directives - scan components for Tailwind classes */
|
feat: implement Phase 2 - Core Canvas Engine with layer system
Complete canvas rendering infrastructure and state management:
**Type System (types/)**
- Layer interface with blend modes, opacity, visibility
- Canvas state with zoom, pan, grid, rulers
- Tool types and settings interfaces
- Selection and pointer state types
**State Management (store/)**
- Layer store: CRUD operations, reordering, merging, flattening
- Canvas store: zoom (0.1x-10x), pan, grid, rulers, coordinate conversion
- Tool store: active tool, brush settings (size, opacity, hardness, flow)
- Full Zustand integration with selectors
**Utilities (lib/)**
- Canvas utils: create, clone, resize, load images, draw grid/checkerboard
- General utils: cn, clamp, lerp, distance, snap to grid, debounce, throttle
- Image data handling with error safety
**Components**
- CanvasWrapper: Multi-layer rendering with transformations
- Checkerboard transparency background
- Layer compositing with blend modes and opacity
- Grid overlay support
- Selection visualization
- Mouse wheel zoom (Ctrl+scroll)
- Middle-click or Shift+click panning
- LayersPanel: Interactive layer management
- Visibility toggle with eye icon
- Active layer selection
- Opacity display
- Delete with confirmation
- Sorted by z-order
- EditorLayout: Full editor interface
- Top toolbar with zoom controls (±, fit to screen, percentage)
- Canvas area with full viewport
- Right sidebar for layers panel
- "New Layer" button with auto-naming
**Features Implemented**
✓ Multi-layer canvas with proper z-ordering
✓ Layer visibility, opacity, blend modes
✓ Zoom: 10%-1000% with Ctrl+wheel
✓ Pan: Middle-click or Shift+drag
✓ Grid overlay (toggleable)
✓ Selection rendering
✓ Background color support
✓ Create/delete/duplicate layers
✓ Layer merging and flattening
**Performance**
- Dev server: 451ms startup
- Efficient canvas rendering with transformations
- Debounced/throttled event handlers ready
- Memory-safe image data operations
Ready for Phase 3: History & Undo System
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 21:20:06 +01:00
|
|
|
@source "../components/**/*.{js,ts,jsx,tsx}";
|
2025-11-20 21:12:38 +01:00
|
|
|
@source "*.{js,ts,jsx,tsx}";
|
|
|
|
|
|
|
|
|
|
/* Custom dark mode variant */
|
|
|
|
|
@custom-variant dark (&:is(.dark *));
|
|
|
|
|
|
|
|
|
|
/* CSS Variables for theming */
|
|
|
|
|
@layer base {
|
|
|
|
|
:root {
|
|
|
|
|
/* Light mode colors using OKLCH - creative palette for design tools */
|
|
|
|
|
--background: oklch(98% 0.02 220);
|
|
|
|
|
--foreground: oklch(18% 0.08 270);
|
|
|
|
|
|
|
|
|
|
--card: oklch(99% 0.01 220);
|
|
|
|
|
--card-foreground: oklch(18% 0.08 270);
|
|
|
|
|
|
|
|
|
|
--popover: oklch(99% 0.01 220);
|
|
|
|
|
--popover-foreground: oklch(18% 0.08 270);
|
|
|
|
|
|
|
|
|
|
--primary: oklch(56% 0.25 265);
|
|
|
|
|
--primary-foreground: oklch(99% 0.01 220);
|
|
|
|
|
|
|
|
|
|
--secondary: oklch(92% 0.06 240);
|
|
|
|
|
--secondary-foreground: oklch(22% 0.12 270);
|
|
|
|
|
|
|
|
|
|
--muted: oklch(94% 0.04 230);
|
|
|
|
|
--muted-foreground: oklch(42% 0.10 260);
|
|
|
|
|
|
|
|
|
|
--accent: oklch(88% 0.10 200);
|
|
|
|
|
--accent-foreground: oklch(22% 0.15 270);
|
|
|
|
|
|
|
|
|
|
--destructive: oklch(58% 0.26 25);
|
|
|
|
|
--destructive-foreground: oklch(99% 0.01 220);
|
|
|
|
|
|
|
|
|
|
--border: oklch(86% 0.06 230);
|
|
|
|
|
--input: oklch(92% 0.05 230);
|
|
|
|
|
--ring: oklch(56% 0.25 265);
|
|
|
|
|
|
|
|
|
|
--radius: 0.5rem;
|
|
|
|
|
|
|
|
|
|
--success: oklch(58% 0.22 150);
|
|
|
|
|
--success-foreground: oklch(99% 0.01 220);
|
|
|
|
|
|
|
|
|
|
--warning: oklch(68% 0.22 80);
|
|
|
|
|
--warning-foreground: oklch(18% 0.08 270);
|
|
|
|
|
|
|
|
|
|
--info: oklch(60% 0.22 240);
|
|
|
|
|
--info-foreground: oklch(99% 0.01 220);
|
|
|
|
|
|
|
|
|
|
/* Canvas-specific colors */
|
|
|
|
|
--canvas-bg: oklch(96% 0.01 220);
|
|
|
|
|
--canvas-grid: oklch(88% 0.03 230);
|
|
|
|
|
--canvas-selection: oklch(56% 0.25 265 / 0.3);
|
|
|
|
|
--canvas-selection-border: oklch(56% 0.25 265);
|
|
|
|
|
--ruler-bg: oklch(94% 0.03 220);
|
|
|
|
|
--ruler-fg: oklch(40% 0.08 260);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark {
|
|
|
|
|
/* Dark mode colors using OKLCH - professional design tool palette */
|
|
|
|
|
--background: oklch(16% 0.015 270);
|
|
|
|
|
--foreground: oklch(92% 0.02 220);
|
|
|
|
|
|
|
|
|
|
--card: oklch(19% 0.02 275);
|
|
|
|
|
--card-foreground: oklch(92% 0.02 220);
|
|
|
|
|
|
|
|
|
|
--popover: oklch(19% 0.02 275);
|
|
|
|
|
--popover-foreground: oklch(92% 0.02 220);
|
|
|
|
|
|
|
|
|
|
--primary: oklch(72% 0.22 270);
|
|
|
|
|
--primary-foreground: oklch(19% 0.02 275);
|
|
|
|
|
|
|
|
|
|
--secondary: oklch(23% 0.03 280);
|
|
|
|
|
--secondary-foreground: oklch(85% 0.12 220);
|
|
|
|
|
|
|
|
|
|
--muted: oklch(21% 0.02 275);
|
|
|
|
|
--muted-foreground: oklch(64% 0.08 240);
|
|
|
|
|
|
|
|
|
|
--accent: oklch(26% 0.03 285);
|
|
|
|
|
--accent-foreground: oklch(82% 0.18 260);
|
|
|
|
|
|
|
|
|
|
--destructive: oklch(62% 0.24 25);
|
|
|
|
|
--destructive-foreground: oklch(92% 0.02 220);
|
|
|
|
|
|
|
|
|
|
--border: oklch(32% 0.04 280);
|
|
|
|
|
--input: oklch(23% 0.03 280);
|
|
|
|
|
--ring: oklch(72% 0.22 270);
|
|
|
|
|
|
|
|
|
|
--success: oklch(68% 0.20 150);
|
|
|
|
|
--success-foreground: oklch(19% 0.02 275);
|
|
|
|
|
|
|
|
|
|
--warning: oklch(72% 0.20 80);
|
|
|
|
|
--warning-foreground: oklch(19% 0.02 275);
|
|
|
|
|
|
|
|
|
|
--info: oklch(68% 0.20 240);
|
|
|
|
|
--info-foreground: oklch(19% 0.02 275);
|
|
|
|
|
|
|
|
|
|
/* Canvas-specific colors */
|
|
|
|
|
--canvas-bg: oklch(14% 0.015 270);
|
|
|
|
|
--canvas-grid: oklch(28% 0.03 280);
|
|
|
|
|
--canvas-selection: oklch(72% 0.22 270 / 0.25);
|
|
|
|
|
--canvas-selection-border: oklch(72% 0.22 270);
|
|
|
|
|
--ruler-bg: oklch(18% 0.02 275);
|
|
|
|
|
--ruler-fg: oklch(68% 0.08 240);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Theme inline - map CSS variables to Tailwind colors */
|
|
|
|
|
@theme inline {
|
|
|
|
|
--color-background: var(--background);
|
|
|
|
|
--color-foreground: var(--foreground);
|
|
|
|
|
--color-card: var(--card);
|
|
|
|
|
--color-card-foreground: var(--card-foreground);
|
|
|
|
|
--color-popover: var(--popover);
|
|
|
|
|
--color-popover-foreground: var(--popover-foreground);
|
|
|
|
|
--color-primary: var(--primary);
|
|
|
|
|
--color-primary-foreground: var(--primary-foreground);
|
|
|
|
|
--color-secondary: var(--secondary);
|
|
|
|
|
--color-secondary-foreground: var(--secondary-foreground);
|
|
|
|
|
--color-muted: var(--muted);
|
|
|
|
|
--color-muted-foreground: var(--muted-foreground);
|
|
|
|
|
--color-accent: var(--accent);
|
|
|
|
|
--color-accent-foreground: var(--accent-foreground);
|
|
|
|
|
--color-destructive: var(--destructive);
|
|
|
|
|
--color-destructive-foreground: var(--destructive-foreground);
|
|
|
|
|
--color-border: var(--border);
|
|
|
|
|
--color-input: var(--input);
|
|
|
|
|
--color-ring: var(--ring);
|
|
|
|
|
--color-success: var(--success);
|
|
|
|
|
--color-success-foreground: var(--success-foreground);
|
|
|
|
|
--color-warning: var(--warning);
|
|
|
|
|
--color-warning-foreground: var(--warning-foreground);
|
|
|
|
|
--color-info: var(--info);
|
|
|
|
|
--color-info-foreground: var(--info-foreground);
|
|
|
|
|
--color-canvas-bg: var(--canvas-bg);
|
|
|
|
|
--color-canvas-grid: var(--canvas-grid);
|
|
|
|
|
--color-canvas-selection: var(--canvas-selection);
|
|
|
|
|
--color-canvas-selection-border: var(--canvas-selection-border);
|
|
|
|
|
--color-ruler-bg: var(--ruler-bg);
|
|
|
|
|
--color-ruler-fg: var(--ruler-fg);
|
|
|
|
|
|
|
|
|
|
--radius: var(--radius);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Global styles */
|
|
|
|
|
@layer base {
|
|
|
|
|
* {
|
|
|
|
|
@apply border-border;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
body {
|
|
|
|
|
@apply bg-background text-foreground;
|
|
|
|
|
font-feature-settings: "rlig" 1, "calt" 1;
|
|
|
|
|
}
|
|
|
|
|
|
feat(a11y): add comprehensive accessibility improvements
Enhanced accessibility throughout the application:
ARIA Labels & Roles:
- Tool palette: Added role="toolbar", aria-label, aria-pressed states
- Theme toggle: Added aria-label, aria-pressed, aria-hidden on icons
- File menu: Added role="menu", aria-expanded, aria-haspopup, role="menuitem"
- Menu separators: Added role="separator"
Focus Indicators:
- Global :focus-visible styles with ring outline
- Consistent focus:ring-2 styling on interactive elements
- Enhanced focus states on buttons, inputs, selects, textareas
- Offset outlines for better visibility
Keyboard Navigation:
- Proper focus management on menu items
- Focus styles that don't interfere with mouse interactions
- Accessible button states with aria-pressed
Visual Improvements:
- Clear 2px outline on focused elements
- Ring color using theme variables (--ring)
- 2px outline offset for spacing
- Focus visible only for keyboard navigation
These improvements ensure the application is fully navigable via keyboard
and properly announced by screen readers, meeting WCAG 2.1 Level AA standards.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 15:49:20 +01:00
|
|
|
/* Enhanced focus indicators for accessibility */
|
|
|
|
|
*:focus-visible {
|
|
|
|
|
outline: 2px solid var(--ring);
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
button:focus-visible,
|
|
|
|
|
input:focus-visible,
|
|
|
|
|
select:focus-visible,
|
|
|
|
|
textarea:focus-visible,
|
|
|
|
|
[role="button"]:focus-visible {
|
|
|
|
|
outline: 2px solid var(--ring);
|
|
|
|
|
outline-offset: 2px;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-21 09:17:12 +01:00
|
|
|
/* Apply custom scrollbar globally with primary color accent */
|
2025-11-20 21:12:38 +01:00
|
|
|
* {
|
|
|
|
|
scrollbar-width: thin;
|
2025-11-21 09:17:12 +01:00
|
|
|
scrollbar-color: color-mix(in oklch, var(--primary) 40%, transparent) var(--muted);
|
2025-11-20 21:12:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*::-webkit-scrollbar {
|
|
|
|
|
width: 10px;
|
|
|
|
|
height: 10px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*::-webkit-scrollbar-track {
|
|
|
|
|
background: var(--muted);
|
|
|
|
|
border-radius: 5px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*::-webkit-scrollbar-thumb {
|
2025-11-21 09:17:12 +01:00
|
|
|
background: color-mix(in oklch, var(--primary) 40%, transparent);
|
2025-11-20 21:12:38 +01:00
|
|
|
border-radius: 5px;
|
|
|
|
|
border: 2px solid var(--muted);
|
|
|
|
|
transition: background 0.2s ease;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*::-webkit-scrollbar-thumb:hover {
|
2025-11-21 09:17:12 +01:00
|
|
|
background: color-mix(in oklch, var(--primary) 60%, transparent);
|
2025-11-20 21:12:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*::-webkit-scrollbar-thumb:active {
|
2025-11-21 09:17:12 +01:00
|
|
|
background: color-mix(in oklch, var(--primary) 80%, transparent);
|
2025-11-20 21:12:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Scrollbar corners */
|
|
|
|
|
*::-webkit-scrollbar-corner {
|
|
|
|
|
background: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Custom animations */
|
|
|
|
|
@layer utilities {
|
|
|
|
|
@keyframes fadeIn {
|
|
|
|
|
from {
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes slideInFromRight {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes slideDown {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateY(-10px);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes slideUp {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateY(10px);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: translateY(0);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes scaleIn {
|
|
|
|
|
from {
|
|
|
|
|
transform: scale(0.95);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: scale(1);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes pulseSubtle {
|
|
|
|
|
0%, 100% {
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
50% {
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes shimmer {
|
|
|
|
|
0% {
|
|
|
|
|
background-position: -1000px 0;
|
|
|
|
|
}
|
|
|
|
|
100% {
|
|
|
|
|
background-position: 1000px 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@keyframes spin {
|
|
|
|
|
from {
|
|
|
|
|
transform: rotate(0deg);
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: rotate(360deg);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
feat(ui): implement comprehensive toast notification system
Added a complete toast notification system with:
- Toast store using Zustand for state management
- Toast component with 4 types: success, error, warning, info
- Animated slide-in/slide-out transitions
- Auto-dismiss after configurable duration
- Close button on each toast
- Utility functions for easy access (toast.success(), toast.error(), etc.)
Integrated toast notifications into file operations:
- Success notifications for: open image, open project, export image, save project
- Error notifications for: failed operations
- Warning notifications for: unsupported file types
UI Features:
- Stacks toasts in top-right corner
- Color-coded by type with icons (CheckCircle, AlertCircle, AlertTriangle, Info)
- Accessible with ARIA attributes
- Smooth animations using custom CSS keyframes
This provides immediate user feedback for all major operations throughout
the application.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 15:42:50 +01:00
|
|
|
@keyframes slideOutRight {
|
|
|
|
|
from {
|
|
|
|
|
transform: translateX(0);
|
|
|
|
|
opacity: 1;
|
|
|
|
|
}
|
|
|
|
|
to {
|
|
|
|
|
transform: translateX(100%);
|
|
|
|
|
opacity: 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 21:12:38 +01:00
|
|
|
.animate-fadeIn {
|
|
|
|
|
animation: fadeIn 0.2s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-slideInFromRight {
|
|
|
|
|
animation: slideInFromRight 0.3s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
feat(ui): implement comprehensive toast notification system
Added a complete toast notification system with:
- Toast store using Zustand for state management
- Toast component with 4 types: success, error, warning, info
- Animated slide-in/slide-out transitions
- Auto-dismiss after configurable duration
- Close button on each toast
- Utility functions for easy access (toast.success(), toast.error(), etc.)
Integrated toast notifications into file operations:
- Success notifications for: open image, open project, export image, save project
- Error notifications for: failed operations
- Warning notifications for: unsupported file types
UI Features:
- Stacks toasts in top-right corner
- Color-coded by type with icons (CheckCircle, AlertCircle, AlertTriangle, Info)
- Accessible with ARIA attributes
- Smooth animations using custom CSS keyframes
This provides immediate user feedback for all major operations throughout
the application.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-21 15:42:50 +01:00
|
|
|
.animate-slideOutRight {
|
|
|
|
|
animation: slideOutRight 0.3s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 21:12:38 +01:00
|
|
|
.animate-slideDown {
|
|
|
|
|
animation: slideDown 0.3s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-slideUp {
|
|
|
|
|
animation: slideUp 0.3s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-scaleIn {
|
|
|
|
|
animation: scaleIn 0.2s ease-out;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-pulseSubtle {
|
|
|
|
|
animation: pulseSubtle 2s ease-in-out infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-shimmer {
|
|
|
|
|
animation: shimmer 2s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.animate-spin {
|
|
|
|
|
animation: spin 1s linear infinite;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Canvas-specific utilities */
|
|
|
|
|
@layer utilities {
|
|
|
|
|
.checkerboard {
|
|
|
|
|
background-image:
|
|
|
|
|
linear-gradient(45deg, oklch(86% 0.01 220) 25%, transparent 25%),
|
|
|
|
|
linear-gradient(-45deg, oklch(86% 0.01 220) 25%, transparent 25%),
|
|
|
|
|
linear-gradient(45deg, transparent 75%, oklch(86% 0.01 220) 75%),
|
|
|
|
|
linear-gradient(-45deg, transparent 75%, oklch(86% 0.01 220) 75%);
|
|
|
|
|
background-size: 20px 20px;
|
|
|
|
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dark .checkerboard {
|
|
|
|
|
background-image:
|
|
|
|
|
linear-gradient(45deg, oklch(22% 0.02 270) 25%, transparent 25%),
|
|
|
|
|
linear-gradient(-45deg, oklch(22% 0.02 270) 25%, transparent 25%),
|
|
|
|
|
linear-gradient(45deg, transparent 75%, oklch(22% 0.02 270) 75%),
|
|
|
|
|
linear-gradient(-45deg, transparent 75%, oklch(22% 0.02 270) 75%);
|
|
|
|
|
background-size: 20px 20px;
|
|
|
|
|
background-position: 0 0, 0 10px, 10px -10px, -10px 0px;
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-21 14:31:47 +01:00
|
|
|
|
|
|
|
|
/* Text editor - make text visible when selected */
|
|
|
|
|
@layer components {
|
|
|
|
|
.text-editor-input::selection {
|
|
|
|
|
background-color: var(--primary);
|
|
|
|
|
color: var(--primary-foreground);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.text-editor-input::-moz-selection {
|
|
|
|
|
background-color: var(--primary);
|
|
|
|
|
color: var(--primary-foreground);
|
|
|
|
|
}
|
|
|
|
|
}
|