diff --git a/.claude/settings.local.json b/.claude/settings.local.json
new file mode 100644
index 0000000000000000000000000000000000000000..6411d62089f6c209508e98ed8a0817bbd27ec9b0
--- /dev/null
+++ b/.claude/settings.local.json
@@ -0,0 +1,35 @@
+{
+ "permissions": {
+ "allow": [
+ "Bash(claude mcp add:*)",
+ "mcp__figma__get_screenshot",
+ "mcp__figma__get_metadata",
+ "mcp__figma__get_design_context",
+ "mcp__figma__get_variable_defs",
+ "Bash(npm create:*)",
+ "Bash(dir:*)",
+ "Bash(npm install)",
+ "Bash(npm run dev:*)",
+ "Bash(timeout /t 3)",
+ "Bash(ping:*)",
+ "Bash(nul)",
+ "mcp__figma-desktop__get_screenshot",
+ "mcp__figma-desktop__get_metadata",
+ "mcp__figma-desktop__get_design_context",
+ "Bash(npm run build:*)",
+ "Bash(curl:*)",
+ "Bash(if not exist \"src\\assets\" mkdir \"src\\assets\")",
+ "WebFetch(domain:localhost)",
+ "Bash(del \"C:\\HuggingFace\\Huggy thumbnail crafter\\src\\data\\layouts.ts\")",
+ "Bash(unzip:*)",
+ "WebSearch",
+ "Bash(if not exist \"src\\assets\\layouts\" mkdir \"src\\assets\\layouts\")",
+ "Bash(if not exist \"src\\assets\\layouts\\thumbnails\" mkdir \"src\\assets\\layouts\\thumbnails\")",
+ "Bash(if not exist \"public\\assets\\layouts\\huggys\" mkdir \"public\\assets\\layouts\\huggys\")",
+ "Bash(git init:*)",
+ "Bash(git add:*)"
+ ],
+ "deny": [],
+ "ask": []
+ }
+}
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000000000000000000000000000000000000..f2c1ca32a67c712fbd7381c0f2211adde560fb18
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+dist/**/*.png filter=lfs diff=lfs merge=lfs -text
+public/**/*.png filter=lfs diff=lfs merge=lfs -text
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..67462ac45444f26f7715561844bb2033c34d153c
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+
+# Windows reserved names
+nul
diff --git a/PROJECT_PLAN.md b/PROJECT_PLAN.md
new file mode 100644
index 0000000000000000000000000000000000000000..ceb77a09c2b284930e385cfbe1a42d855b528e58
--- /dev/null
+++ b/PROJECT_PLAN.md
@@ -0,0 +1,955 @@
+# HF Thumbnail Crafter - Project Plan
+
+## Project Overview
+A web-based thumbnail creator for HuggingFace users to craft quick thumbnails with pre-made layouts and visual assets (Huggys). The project will be hosted on HuggingFace Spaces as a static site.
+
+## Design Resources
+- **Figma File:** https://www.figma.com/design/b7rA38IJxS0sK8ppXE8Gjb/HF-Thumbnail-Crafter?node-id=91-4463&t=oTLFdnJ8jDG93HCw-1
+- **Figma MCP Connected:** Yes (http://127.0.0.1:3845/mcp)
+
+## Tech Stack Decisions
+
+### Core Framework
+- **Build Tool:** Vite
+- **Framework:** React 18
+- **Language:** TypeScript
+- **Canvas Library:** react-konva + Konva.js
+- **Styling:** Tailwind CSS (Figma exports already in Tailwind format)
+- **Icons:** Lucide React (placeholder until custom icons ready)
+- **Fonts:** Inter (default), IBM Plex Mono
+- **Deployment:** HuggingFace Spaces (Static Site)
+
+### Why These Choices?
+- **React + Konva:** Excellent integration via react-konva library
+- **Vite:** Fast, modern, lightweight - perfect for HF Spaces static deployment
+- **TypeScript:** Type safety for complex canvas state management
+- **Tailwind CSS:** Figma MCP exports Tailwind classes - zero conversion work needed, fast development
+
+## Key Requirements
+
+### 1. Floating Sidebar (Left)
+Four buttons with icons and labels:
+1. **Layout** - Load pre-designed layout templates
+2. **Huggy** - Add visual assets from HuggingFace dataset
+3. **Image** - Upload local images or drag-and-drop
+4. **Text** - Add text objects to canvas
+
+**Design specs:**
+- Width: 87px
+- Background: #f8f9fa
+- Border radius: 10px
+- Spacing: 15px between buttons
+- Position: Fixed left, floating
+
+### 2. Canvas Area (Center)
+
+#### Canvas Header
+**Left side - Background Color Selector:**
+- Label: "Background color:"
+- Two options: Light (white) and Dark (purple/dark)
+- Toggle between backgrounds
+
+**Right side - Canvas Size Selector:**
+- Label: "Size:"
+- Three options:
+ 1. **1200×675** (default, X icon)
+ 2. **LinkedIn size** (LinkedIn icon)
+ 3. **HF custom size** (HF icon)
+
+#### Canvas
+- Default size: 1200×675px
+- Background: Switchable (light/dark)
+- Supports multiple object types: rectangles, images, text, Huggys
+
+**Canvas Object Interactions:**
+- ✅ Drag to reposition
+- ✅ Proportional scaling (maintain aspect ratio)
+- ✅ Rotate objects
+- ✅ Layer ordering (bring to front/send to back)
+- ✅ Delete objects (keyboard + button)
+- Selection with bounding box and transformer handles
+
+### 3. Export Button (Top Right)
+- Position: Fixed top-right
+- Shows: Download icon + editable filename + ".png"
+- Default filename: "thumbnail_name"
+- Format: PNG only
+- Functionality: Export canvas as PNG using Konva's toDataURL
+
+### 4. Screen Background
+- Dotted pattern wallpaper
+- Base color: #f8f9fa with dot pattern overlay
+
+## Feature Details
+
+### Layout Feature
+**How it works:**
+1. Click "Layout" button → Shows layout selector menu
+2. Menu displays thumbnails of 6 pre-designed layouts:
+ - Serious Collab
+ - Fun Collab
+ - Sandwich
+ - Docs
+ - 1:2 (two variations)
+3. Click layout → Loads objects (shapes, text, images) onto canvas
+4. User edits from there
+
+**Layout Implementation Workflow:**
+1. Designer creates layouts in Figma (for all 3 canvas sizes)
+2. Developer extracts layout specs using Figma MCP
+3. Convert to JSON format:
+```json
+{
+ "id": "serious-collab",
+ "name": "Serious Collab",
+ "canvasSize": "1200x675",
+ "objects": [
+ {
+ "type": "rect",
+ "x": 100,
+ "y": 50,
+ "width": 200,
+ "height": 150,
+ "fill": "#000000",
+ "rotation": 0,
+ "zIndex": 1
+ },
+ {
+ "type": "text",
+ "x": 150,
+ "y": 300,
+ "text": "Title Here",
+ "fontSize": 100,
+ "fontFamily": "Inter",
+ "fill": "#000000",
+ "bold": false,
+ "italic": false,
+ "rotation": 0,
+ "zIndex": 2
+ }
+ ]
+}
+```
+4. Store layouts in `/src/data/layouts.ts`
+5. Future layouts follow same process
+
+### Huggy Feature
+**Current status:** Visual assets will be stored in HuggingFace dataset (future)
+
+**Functionality:**
+1. Click "Huggy" button → Opens Huggy selector menu
+2. Menu includes:
+ - Search bar at top ("Search Huggy")
+ - Grid of Huggy thumbnails (3+ rows visible)
+ - Scroll for more Huggys
+3. Example Huggys from Figma:
+ - IDEFICS Huggy
+ - Robot Huggy
+ - Computer Vision Huggy
+ - SF Meetup Huggy
+ - Transformer Agent Huggy
+4. Click Huggy → Adds to canvas center as draggable image
+5. Huggy behaves like any canvas object (drag, scale, rotate, delete)
+
+**Implementation notes:**
+- Phase 1: Use exported Huggy images from Figma as placeholders
+- Phase 2: Connect to HuggingFace dataset API
+- Search filters Huggys by name
+
+### Image Upload Feature
+**Functionality:**
+1. Click "Image" button OR drag-and-drop image onto canvas
+2. Drag hint shown: "Drag n drop your image anywhere to upload"
+3. Supported formats: PNG, JPG, WebP
+4. Image appears in canvas center
+5. User can drag, scale, rotate, layer-order, delete
+
+**Implementation:**
+- Hidden file input triggered by button click
+- Drag-over visual feedback on canvas area
+- Load image as Konva Image node
+
+### Text Feature
+**Basic functionality:**
+1. Click "Text" button → Adds default text object to canvas center
+2. Default text: "Pretty Short Title"
+3. Default style: Inter, 100px, black, normal weight
+
+**Editing modes:**
+1. **Click once** → Select text object (shows transformer)
+2. **Double-click** → Enter inline edit mode (temporary textarea overlay)
+3. **Select text** → Shows floating text toolbar below canvas
+
+**Text toolbar appears when:**
+- Text object is selected on canvas
+- "Add Text" button is clicked
+
+### Text Toolbar (Floating)
+**Position:** Below canvas, horizontally centered
+
+**Controls (left to right):**
+1. **Font Family Dropdown**
+ - Options: Inter (default), IBM Plex Mono
+ - Shows current font name
+ - Dropdown arrow icon
+
+2. **Divider line**
+
+3. **Color Picker**
+ - Button shows current color swatch
+ - Click → Opens color picker popover (matches Figma design)
+ - Picker includes:
+ - Gradient palette area
+ - Hue slider
+ - Saturation slider
+ - Hex input field
+ - Format dropdown (Hex)
+
+4. **Bold Button**
+ - Icon: Bold "B"
+ - Toggle on/off
+ - Active state styling
+
+5. **Italic Button**
+ - Icon: Italic "I"
+ - Toggle on/off
+ - Active state styling
+
+**Design specs:**
+- Background: Dark (#2b2d31 or similar)
+- Height: 44px
+- Padding: 2px border, 2px inner padding
+- Border radius: 5px
+- Toolbar width: ~288px
+
+## Development Phases
+
+### Phase 1: Project Setup ✅
+- [x] Initialize Vite + React + TypeScript
+- [x] Install dependencies: react-konva, konva, lucide-react
+- [x] Create folder structure:
+ ```
+ /src
+ /components
+ /Sidebar
+ /Canvas
+ /CanvasHeader
+ /ExportButton
+ /LayoutSelector
+ /HuggyMenu
+ /TextToolbar
+ /types
+ canvas.types.ts
+ layout.types.ts
+ /utils
+ export.utils.ts
+ canvas.utils.ts
+ /data
+ layouts.ts
+ huggys.ts
+ /assets
+ /icons (placeholder until custom icons ready)
+ /huggy-images
+ ```
+- [x] Set up base configuration (tsconfig, vite.config)
+
+### Phase 2: Core UI Structure ✅ (Fully Complete)
+**Components:**
+- ✅ `App.tsx` - Main layout with dotted background
+- ✅ `Sidebar.tsx` - Floating sidebar with 4 buttons and custom Figma icons
+- ✅ `CanvasContainer.tsx` - Wrapper for canvas area with smooth transitions
+- ✅ `ExportButton.tsx` - Top-right export button
+
+**Icons:**
+- ✅ `IconLayout.tsx` - Custom layout icon with default and selected states
+- ✅ `IconText.tsx` - Custom text icon with default and selected states
+- ✅ `IconImage.tsx` - Custom image icon with default and selected states
+- ✅ `IconHuggy.tsx` - Custom Huggy mascot icon
+
+**Design implementation:**
+- ✅ Extract styles from Figma
+- ✅ Create dotted background pattern
+- ✅ Position all major UI elements
+- ✅ Responsive container sizing
+- ✅ Replace Lucide icons with custom Figma-exported sidebar icons
+
+### Phase 3: Canvas Foundation ✅
+**Setup:**
+- ✅ Create `Canvas.tsx` with Konva Stage and Layer
+- ✅ Implement canvas state management:
+ ```typescript
+ interface CanvasObject {
+ id: string;
+ type: 'rect' | 'image' | 'text' | 'huggy';
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ rotation: number;
+ zIndex: number;
+ // type-specific properties
+ }
+ ```
+- ✅ Set up three canvas sizes with switching
+- ✅ Create base object renderers
+
+### Phase 4: Canvas Header ✅ (Fully Complete)
+**Components:**
+- ✅ `BgColorSelector.tsx` - Light/dark toggle with hover states
+- ✅ `CanvasSizeSelector.tsx` - Three size options with animated expansion
+- ✅ `CanvasHeader.tsx` - Main container component with smooth transitions
+
+**Functionality:**
+- ✅ Background color state management (light/dark toggle)
+- ✅ Canvas size switching (1200×675, LinkedIn, HF)
+- ✅ Active state styling for selected options
+- ✅ Hover states with subtle `#f0f2f4` background on all buttons
+- ✅ Dimension text at 80% opacity for better visual hierarchy
+- ✅ Smooth 150ms ease-in-out transitions for all state changes
+- ✅ Canvas dimensions animate smoothly (150ms), background color instant
+- ✅ Header position adjusts smoothly when canvas size changes
+- ✅ Animated button expansion with text slide-in and fade-in effects
+- ✅ Icon-only display for unselected options, icon + label for selected
+- ✅ Integrated into App.tsx above Canvas component
+
+**Animations:**
+- ✅ Button width expansion/contraction when selecting canvas size
+- ✅ Text fade-in/fade-out (0 → 0.8 opacity)
+- ✅ Text slide-in effect (translateX animation)
+- ✅ All animations synchronized at 150ms ease-in-out
+
+### Phase 5: Layout Feature
+**Components:**
+- `LayoutSelector.tsx` - Floating menu component (matching Figma LayoutOptionsList design)
+- Layout thumbnail components (4 layouts: Serious Collab, Fun Collab, Sandwich, Docs)
+- Note: Only 4 unique layouts, NOT 6. Each layout has 3 size variations (1200×675, LinkedIn, HF)
+
+**Data structure:**
+- Define TypeScript interfaces for layouts
+- Create layout data for all 4 layouts × 3 canvas sizes (12 total variants)
+- Logo placeholder object type with Konva blur filter support (blur radius: 11.415px)
+- Export all placeholder assets as PNG (Huggys, logo placeholders, decorative elements)
+- Text placeholders remain as editable text objects
+
+**Font additions:**
+- Add Bison Bold font for Sandwich layout title text
+- Update TextObject fontFamily type to include 'Bison'
+
+**Implementation details:**
+- Extract layout specs from Figma using MCP
+- Convert to JSON format with object positions, sizes, rotations
+- Store in `/src/data/layouts.ts`
+- Implement layout loading logic to populate canvas
+
+**Future enhancement (Post-Phase 5):**
+- **Asset Swap Feature:** When hovering over logo placeholder or Huggy objects, show a small swap button
+- Clicking swap button allows users to replace with their own image from local storage
+- This will be implemented in a future phase after core layout functionality is complete
+
+**Collaboration checkpoint:**
+- Designer provides Figma layout specs
+- Developer extracts and converts to JSON
+- Test layout loading on all canvas sizes
+
+### Phase 6: Canvas Interactions ✅ (Partial - Core Complete)
+**Features implemented:**
+- ✅ Object selection with Transformer
+- ✅ Drag functionality (Konva draggable)
+- ✅ Proportional scaling (keepRatio enabled)
+- ✅ Rotation handles
+- ✅ Delete functionality (keyboard Delete/Backspace)
+- ✅ Transformer styling (blue border, white-filled corner handles)
+
+**Features to implement:**
+- ⚠️ Layer ordering controls (floating LayerContainer)
+- ⚠️ Delete button UI
+- ⚠️ Multi-select support (optional)
+
+**LayerContainer Component:**
+- **When:** Only appears when an object is selected
+- **What:** Dynamically visualizes ALL canvas objects as stacked layer icons
+- **Selected state:** Selected object's layer shown in blue
+- **Position:** Floats near the right side of the selected object
+- **Interactions:**
+ - Drag layers up/down to reorder in stack
+ - Click a layer to bring that object to top of clicked layer
+- **Visual:** Vertical stack showing layer hierarchy
+
+**UI elements:**
+- LayerContainer (floating mini layers panel)
+- Delete button
+- Transformer styling
+
+### Phase 7: Huggy Feature
+**Components:**
+- `HuggyMenu.tsx` - Floating menu with search
+- `HuggyGrid.tsx` - Grid display of Huggys
+- `HuggyCard.tsx` - Individual Huggy thumbnail
+
+**Implementation:**
+- Phase 1: Use exported Huggy images from Figma
+- Create Huggy data structure:
+ ```typescript
+ interface Huggy {
+ id: string;
+ name: string;
+ thumbnail: string;
+ fullImage: string;
+ tags?: string[];
+ }
+ ```
+- Search/filter functionality
+- Add Huggy to canvas as Konva Image
+
+**Future:** Connect to HuggingFace dataset API
+
+### Phase 8: Image Upload
+**Features:**
+- File input button (hidden, triggered by sidebar)
+- Drag-and-drop zone (entire canvas area)
+- Visual drag-over feedback
+- Image loading and processing
+- Add to canvas as Konva Image node
+
+**Error handling:**
+- File type validation
+- File size limits
+- Loading states
+
+### Phase 9: Text Feature ✅ (Fully Complete)
+**Components:**
+- Text object renderer in Canvas
+- Inline text editor (textarea overlay)
+- Text hint box in Sidebar
+
+**Functionality:**
+- ✅ Add default text on button click (68px default font)
+- ✅ Keyboard shortcut: Press 'T' to activate text creation mode
+- ✅ Single-click selection with transformer/bounding box
+- ✅ Double-click inline editing with cursor at exact click position
+- ✅ Text object transformation (drag, scale, rotate)
+- ✅ Font size scales proportionally during transformation
+- ✅ Font loading (Inter, IBM Plex Mono)
+- ✅ Two-stage text editing system:
+ - **Stage 1:** Auto-growing text box on initial creation
+ - **Stage 2:** Fixed-size box with dynamic font scaling to fit
+- ✅ Real-time font size adaptation while typing
+- ✅ Live bounding box resize while editing (shrinks/grows with content)
+- ✅ Delete key context awareness (deletes characters while editing, deletes object when selected)
+- ✅ Text hint box positioned next to Text button
+
+**Bug Fixes Completed (2025-11-20):**
+1. ✅ **Critical bounding box visibility issue**
+ - Refs were passed as objects instead of callback functions
+ - Fixed with callback ref pattern to properly store Konva nodes
+2. ✅ **Duplicate/transparent text during editing**
+ - Fixed by setting text opacity to 0 during editing
+3. ✅ **Delete key context awareness**
+ - Delete key only removes object when NOT editing
+4. ✅ **Text scaling on transform**
+ - Font size now scales proportionally with text box
+5. ✅ **Live bounding box resize**
+ - Box shrinks/grows in real-time while typing
+6. ✅ **Cursor position on double-click**
+ - Cursor appears at exact click position instead of selecting all
+7. ✅ **Text hint box positioning**
+ - Hint box aligned with Text button at same vertical level
+
+**Phase 9 Complete:** All core functionality and UX refinements implemented.
+
+**Next:** Phase 10 (Text Toolbar) - Will be implemented after other core features
+
+### Phase 10: Text Toolbar
+**Components:**
+- `TextToolbar.tsx` - Main toolbar container
+- `FontFamilyDropdown.tsx`
+- `ColorPicker.tsx` - Full color picker UI
+- `BoldButton.tsx`
+- `ItalicButton.tsx`
+
+**Functionality:**
+- Show/hide based on text selection
+- Apply styles to selected text object
+- Color picker popover positioning
+- Real-time preview of changes
+
+**Design fidelity:**
+- Match Figma design exactly
+- Smooth animations
+- Proper z-index layering
+
+### Phase 11: Export Feature
+**Functionality:**
+- Editable filename input
+- PNG generation using Konva's `stage.toDataURL()`
+- Download trigger
+- Loading state during export
+
+**Technical details:**
+- Set proper canvas dimensions
+- Handle high-DPI displays (pixelRatio)
+- File naming conventions
+- Browser download API
+
+### Phase 12: Polish & Deploy
+**Tasks:**
+- Keyboard shortcuts (Delete, Undo)
+- Loading states for all async operations
+- Error boundaries
+- Performance optimization:
+ - Lazy load Huggy images
+ - Debounce canvas updates
+ - Optimize re-renders
+- Accessibility improvements
+- Create README.md for HF Spaces
+- Test on multiple browsers
+- Deploy to HuggingFace Spaces
+
+**HF Spaces setup:**
+- Create `README.md` with metadata:
+ ```yaml
+ ---
+ title: HF Thumbnail Crafter
+ emoji: 🎨
+ colorFrom: blue
+ colorTo: purple
+ sdk: static
+ pinned: false
+ ---
+ ```
+- Configure build output
+- Test deployment
+
+## Important Notes
+
+### State Management
+**No persistence:** Canvas state is lost on page refresh (as per requirements)
+- Use React state (useState/useReducer) for canvas objects
+- No localStorage/database needed
+
+### Icon System
+**Current:** Use Lucide React as placeholder
+**Future:** Replace with custom icons from Figma
+- Export SVGs from Figma
+- Create React icon component wrapper
+- Place in `/src/assets/icons/`
+
+### Huggy Assets
+**Current:** Export from Figma and place in `/src/assets/huggy-images/`
+**Future:** Fetch from HuggingFace dataset
+- API integration in Phase 7
+- Caching strategy for performance
+
+### Canvas Sizes Reference
+1. **Default (X):** 1200×675px
+2. **LinkedIn:** TBD (standard LinkedIn post size)
+3. **HF Custom:** TBD (to be defined)
+
+## Context for Future Sessions
+
+### What's been decided:
+✅ Tech stack: Vite + React + TypeScript + Konva
+✅ Canvas library: react-konva
+✅ No state persistence (refresh loses work)
+✅ PNG export only
+✅ Three canvas sizes with switcher
+✅ Text fonts: Inter and IBM Plex Mono
+✅ Deployment: HuggingFace Spaces
+
+### What needs clarification:
+⚠️ LinkedIn and HF canvas sizes (exact dimensions)
+⚠️ Complete icon set from Figma (using Lucide temporarily)
+⚠️ HuggingFace dataset API endpoint for Huggys
+⚠️ Layout specs for all 6 layouts (will extract in Phase 5)
+
+### Session resumption checklist:
+1. Review this PROJECT_PLAN.md
+2. Check current phase status in todo list
+3. Verify Figma MCP connection: `claude mcp add --transport http figma-desktop http://127.0.0.1:3845/mcp`
+4. Continue from last completed phase
+
+## Contact & Resources
+- **Figma:** https://www.figma.com/design/b7rA38IJxS0sK8ppXE8Gjb/HF-Thumbnail-Crafter
+- **HuggingFace Spaces Docs:** https://huggingface.co/docs/hub/spaces
+- **Konva Docs:** https://konvajs.org/
+- **react-konva Docs:** https://konvajs.org/docs/react/
+
+---
+
+## Implementation Status
+
+### Completed Phases:
+- ✅ **Phase 1:** Project Setup
+- ✅ **Phase 2:** Core UI Structure (Fully Complete - Including custom Figma sidebar icons)
+- ✅ **Phase 3:** Canvas Foundation
+- ✅ **Phase 4:** Canvas Header (Fully Complete - Background color & size selectors with hover states, smooth transitions, and animated button expansion)
+- ✅ **Phase 6:** Canvas Interactions (Partial - Core Complete)
+- ✅ **Phase 9:** Text Feature (Fully Complete)
+
+### Current Phase:
+**Phase 7:** Huggy Feature (Completed) ✅
+**Next:** Phase 8 (Image Upload)
+
+**Progress (2025-11-22):**
+- ✅ Fixed sidebar icons with correct selected states from Figma icons page
+ - Text, Layout, and Image icons now have proper default (gray) and selected (white) states
+ - All icons saved to `src/assets/icons/` and using local files
+- ✅ Integrated Bison Bold font
+ - Downloaded and extracted to `public/fonts/`
+ - Added @font-face declaration in `src/index.css`
+ - Updated TypeScript types to include 'Bison' in fontFamily union type
+- ✅ Updated PROJECT_PLAN.md with asset swap feature documentation (Post-Phase 5)
+- ✅ Confirmed 4 layouts × 3 sizes = 12 total layout variants to implement
+- ✅ Created LogoPlaceholderObject TypeScript interface with blur support
+- ✅ Extended CanvasObject union type to include LogoPlaceholderObject
+- ✅ Implemented logo placeholder renderer with Konva blur filter (11.415px radius)
+- ✅ Extracted layout specifications from Figma for all 4 layouts
+ - Sandwich layout: gradient background, logo placeholder, singing Huggy, divider
+ - Docs layout: gradient background, logo placeholder, partial Huggy, divider
+ - Serious Collab: solid gray background (#d9d9d9)
+ - Fun Collab: solid white background (#ffffff)
+- ✅ Created `/src/data/layouts.ts` with layout definitions and helper functions
+- ✅ Built LayoutSelector component matching Figma design
+ - Floating menu positioned next to sidebar
+ - 4 layout options with thumbnails
+ - Clean hover states
+- ✅ Integrated LayoutSelector into Sidebar component
+- ✅ Implemented layout loading logic in App.tsx
+ - Applies layout background (solid or gradient)
+ - Loads all layout objects onto canvas
+ - Resets selection after loading
+
+**Critical Fixes (2025-11-22 - Afternoon):**
+- ✅ Fixed LayoutSelector component to match Figma 2×2 grid design
+ - Was using vertical list, now uses proper 2×2 grid layout
+ - Width: 233px, proper padding and spacing
+ - Each layout button: 96.5px × 47.291px thumbnail area
+- ✅ Extracted correct layout specifications from Figma Layouts page (node 219:1155)
+ - **Serious Collab**: HF logo image, logo placeholder with blur, "Logo" text, divider
+ - **Fun Collab**: "Pretty Short Title" text, 2 Huggy images, rotated logo placeholder with "Logo" text
+ - **Sandwich**: "Pretty short title" (Bison font), "supportive description", Singing Huggy image
+ - **Docs**: Masked Huggy image, "Transformers" title, "Documentation" subtitle
+- ✅ Removed background property from Layout interface
+ - Layouts now only contain objects, as intended
+ - Background color is controlled separately by canvas settings
+- ✅ Fixed App.tsx handleSelectLayout to not apply background colors
+ - Layouts only load objects onto canvas
+ - Users control background independently
+
+**Final Implementation (2025-11-22 - Evening):**
+- ✅ User manually exported all layout assets to `public/assets/layouts/`:
+ - Layout thumbnails: sCollab_thumbnail.png, fCollab_thumbnail.png, sandwitch_thumbnail.png, docs_thumbnail.png
+ - Logo placeholders: logo_placehoder.png (single PNG with blur + text combined)
+ - HF logos: HF logo.png, docsHFLogo.png
+ - Huggy assets: fCollab_huggy_asset.png, fCollab_huggy_hand_asset.png, snadwithc_huggy_asset.png
+- ✅ Updated all layouts.ts with correct asset paths and positions from Figma
+- ✅ Fixed text rendering issue by setting `isFixedSize: true` on all layout text objects
+- ✅ Logo placeholder now uses single image approach (no separate blur filter)
+- ✅ Cleaned up empty folders (huggys/, logos/)
+- ✅ All 4 layouts ready for testing in browser
+
+**Polish and Fixes (2025-11-22 - Late Evening):**
+- ✅ Fixed Docs layout text centering issue
+ - Both "Transformers" and "Documentation" texts were positioned by left edge
+ - Updated to use center-point positioning with `offsetX` property
+ - "Transformers" text: x=600 (canvas center) with offsetX=321.5 (width/2)
+ - "Documentation" text: x=600 (canvas center) with offsetX=210.5 (width/2)
+- ✅ Added SVG support for canvas objects
+ - Konva.Image component natively supports SVG files
+ - Updated Serious Collab layout to use SVG x-icon (collabX.svg)
+ - Changed divider rect to image object with proper positioning
+- ✅ Implemented undo/redo system
+ - Added history management to `useCanvasState` hook
+ - Tracks up to 50 previous canvas states
+ - Keyboard shortcuts: Ctrl+Z (undo), Ctrl+Shift+Z (redo)
+ - Also supports Cmd+Z/Cmd+Shift+Z on Mac
+ - Automatically deselects objects when undoing/redoing
+ - Does not interfere with text editing (shortcuts disabled while editing)
+ - Covers: adding/deleting objects, moving/transforming, editing text, loading layouts
+ - Excludes: background color changes, canvas size changes (view settings)
+
+**Phase 5 Status:** ✅ COMPLETE - All Features Implemented & Tested
+
+**Additional Refinements (2025-11-23):**
+- ✅ Fixed layout positioning issues across all 4 layouts
+ - Huggy assets repositioned to properly overlap titles (Sandwich y: 160→130, Fun Collab y: 81→60)
+ - Text bounding boxes tightened to reduce extra space
+ - Added verticalAlign="top" to text objects to minimize bottom padding
+ - Fixed "supportive description" horizontal centering (x=600, offsetX=375, align='center')
+- ✅ Improved font loading reliability
+ - Explicitly preload Bison, Inter, and IBM Plex Mono fonts before first render
+ - Added 50ms delay after document.fonts.ready for browser sync
+ - Changed Bison font-display from 'swap' to 'block' to prevent fallback flash
+ - Fixes "Documentation" and "Pretty short title" positioning on first load
+- ✅ Layout switch prompt for user-added objects
+ - Added `isFromLayout` flag to track object origins
+ - Prompts user to keep or discard custom objects when switching layouts
+ - Only triggers when NEW user-added objects exist (not for edited layout objects)
+- ✅ Z-index and rendering order fixes
+ - Reordered layout objects arrays to ensure correct visual layering
+ - Huggy assets now properly overlap text placeholders as designed
+- ✅ Text object dimensions optimized
+ - Sandwich title: 194px→170px height
+ - Sandwich description: 107px→95px height, 1015px→750px width
+ - Fun Collab title: 140px→120px height
+ - Docs title: 111px→110px height
+ - Docs subtitle: 56px→55px height
+
+**Known Limitations:**
+- Text bounding boxes have inherent font metrics spacing (ascent/descent)
+- With `isFixedSize: true`, bounding boxes can't dynamically shrink to exact text dimensions
+- `verticalAlign="top"` minimizes visible bottom padding but doesn't eliminate font metrics spacing
+
+**Advanced Interaction Features (2025-11-23 - Latest):**
+- ✅ **Arrow key movement with Shift modifier**
+ - Press arrow keys to move selected object(s) 1px in any direction
+ - Hold Shift while pressing arrow keys to move 10px (faster positioning)
+ - Works with both single and multiple selections
+ - Disabled during text editing to prevent interference
+ - Records in undo/redo history
+- ✅ **Multi-select with drag-to-select box**
+ - Click and drag on empty canvas to create selection box (>5px threshold)
+ - Blue semi-transparent box with dashed border (rgba(63, 174, 230, 0.1))
+ - Automatically selects all objects that intersect with the selection box
+ - Visual feedback during drag operation
+ - **Deselection on click**: Single click on empty canvas (no drag) deselects all objects
+ - Distinguishes between click and drag to prevent accidental deselection
+- ✅ **Shift+Click to add/remove from selection**
+ - Click object normally: selects only that object (clears previous selection)
+ - Shift+Click object: adds object to current selection
+ - Shift+Click selected object: removes it from selection
+ - Works seamlessly with drag-to-select functionality
+ - All selected objects show transformer handles
+- ✅ **Responsive layout repositioning on canvas size change**
+ - When canvas size changes (1200×675 ↔ 1200×627 ↔ 1280×720), all objects scale proportionally
+ - **Maintains aspect ratios**: Uses uniform scale factor (min of scaleX/scaleY) for width/height to prevent distortion
+ - Positions scale based on their respective canvas dimension ratios (scaleX for x, scaleY for y)
+ - Font sizes scale uniformly to maintain readability
+ - Prevents layout breakage and asset distortion when switching between size presets
+ - Recorded in undo/redo history
+
+**Multi-Select Implementation Details:**
+- Changed `selectedId: string | null` to `selectedIds: string[]` in state management
+- Updated all selection-related functions to handle arrays
+- Transformer component now supports multiple nodes simultaneously
+- Arrow key movement and delete operations work with multiple selected objects
+- Undo/redo system handles multi-select operations correctly
+
+**Refinements (2025-11-23 - Final):**
+- ✅ **Fixed responsive scaling to maintain aspect ratios** (App.tsx:155-167)
+ - Changed from independent scaleX/scaleY to uniform scale factor
+ - Prevents asset distortion when switching canvas sizes
+ - Width and height now scale with `Math.min(scaleX, scaleY)`
+ - Positions still scale independently (x with scaleX, y with scaleY) for proper placement
+- ✅ **Fixed deselection on empty canvas click** (Canvas.tsx:400-449)
+ - Added 5px drag threshold to distinguish between click and drag
+ - Single click on empty canvas now properly deselects all objects
+ - Drag-to-select still works as expected with visual feedback
+ - Empty selection box (no intersecting objects) also deselects all
+- ✅ **Added outside canvas click deselection** (App.tsx:178-192, CanvasContainer.tsx:18)
+ - Clicking anywhere outside the canvas container deselects all objects
+ - Uses global mousedown event listener on document
+ - Targets `.canvas-container` className for boundary detection
+ - Only triggers deselection when objects are currently selected (performance optimization)
+
+### Next Steps:
+1. ✅ Complete Phase 5: Layout Feature (DONE)
+2. ✅ Complete Phase 7: Huggy Feature (DONE)
+3. Complete Phase 8: Image Upload
+4. Complete Phase 10: Text Toolbar
+5. Complete Phase 11: Export Feature
+6. Complete Phase 12: Polish & Deploy
+
+---
+
+**Last Updated:** 2025-11-24
+
+**Recent Improvements (2025-11-24):**
+- ✅ **Sidebar Deselection Enhancement**
+ - Clicking the same sidebar button now toggles it off (deselects)
+ - Clicking anywhere outside sidebar components deselects active button
+ - Improves UX by allowing users to close menus without navigating away
+ - **Fixed:** Canvas clicks in text creation mode now properly preserved
+- ✅ **Huggy Menu UX Improvements & Fixes**
+ - **Infinite scroll fixed:**
+ - Initial display increased to 12 Huggys (from 6) to ensure scrollbar appears
+ - Added overflow check that auto-loads more content if container isn't full
+ - Scroll event triggers at 80% to load 6 more items at a time
+ - **Height constraint:** Maximum 4 rows visible (430px max-height) to prevent excessive menu length
+ - **Default scrollbar:** Removed custom scrollbar styling, using native OS scrollbars
+ - **Browser tooltip only:** Removed custom hover overlay, using native browser tooltip (title attribute)
+ - Transparent background on Huggy thumbnails (removed white bg)
+ - Auto-selection: Newly added Huggys are immediately selected on canvas
+ - Unified grid spacing: 5px gap and padding for balanced layout
+- ✅ **Text Creation Bug Fix**
+ - **Fixed:** Text creation mode (button click or 'T' key) now works correctly
+ - Issue was click-outside-sidebar handler deselecting activeButton before canvas click event fired
+ - Solution: Added exception for canvas clicks when in text creation mode
+ - Text creation mode preserved when clicking on canvas to add text
+- ✅ **Undo/Redo System Fixed & Re-enabled** 🎉
+ - **Fixed:** Complete rewrite of history management system
+ - **Root causes identified and resolved:**
+ - Race condition: State updates and history recording competing
+ - Missing state change detection: Now only records when state actually changes
+ - Flag management: Changed from `isUndoRedo` to `isApplyingHistory` with proper async handling
+ - **Improvements:**
+ - Added `setTimeout` to ensure state updates complete before recording
+ - Duplicate state prevention: Compares JSON strings before adding to history
+ - Proper flag reset after undo/redo operations
+ - 50 steps of history (configurable)
+ - **Keyboard shortcuts re-enabled:**
+ - Ctrl+Z / Cmd+Z: Undo
+ - Ctrl+Shift+Z / Cmd+Shift+Z: Redo
+ - Disabled during text editing to prevent interference
+ - **Memory trade-off:** 50 steps ≈ 250KB (negligible for modern browsers)
+ - Can undo all the way back to empty canvas (expected behavior)
+- ✅ **Huggy Menu Scrollbar Refinement**
+ - Reduced scrollbar width from default to 6px (thinner, less intrusive)
+ - Transparent track background for cleaner look
+ - Gray thumb (#b8b8b8) with darker hover state (#999999)
+ - Firefox support with `scrollbar-width: thin`
+- ✅ **Feature Backlog Updated**
+ - Hover to show object bounding box (future enhancement)
+ - Dynamic canvas corner radius & drop shadow on hover (future enhancement)
+
+**Current Status:**
+- Phase 5 (Layout Feature): ✅ COMPLETE WITH KNOWN ISSUES
+ - All 4 layouts implemented with correct specifications
+ - Layout positioning refined for proper Huggy overlap
+ - Text centering and alignment issues resolved
+ - Font loading reliability improved for consistent first-load rendering
+ - SVG support added and tested
+ - Layout switch prompts implemented for user-added objects
+ - Text bounding boxes optimized (known limitation: font metrics spacing)
+ - Arrow key movement with Shift modifier (1px/10px)
+ - Multi-select with drag-to-select box
+ - Shift+Click to add/remove objects from selection
+ - All assets exported and integrated
+- Phase 6 (Canvas Interactions): ✅ ENHANCED
+ - Multi-select functionality fully implemented
+ - Arrow key positioning for precise control
+- Phase 7 (Huggy Feature): ✅ FULLY COMPLETE WITH HUGGINGFACE DATASET
+ - **HuggingFace Dataset Integration:** All 44 Huggy assets loaded from `https://huggingface.co/datasets/Chunte/Huggy`
+ - **26 Modern Huggies** + **18 Outlined Huggies** (PNG only, GIFs excluded)
+ - **Infinite Scroll Loading:** Display 12 Huggys initially, auto-load more when scrolling to 80%
+ - Automatic overflow detection: loads more if content doesn't fill container
+ - **Native Scrollbar:** Uses default OS scrollbar styling
+ - **Search Functionality:** Filter by name, category (Modern/Outlined), and tags
+ - Built HuggyMenu component with search bar and loading states
+ - **Browser Tooltip:** Native title attribute shows Huggy name on hover
+ - Summary footer showing "X of Y Huggys"
+ - Added click handler to insert Huggys onto canvas as draggable images (200×200px default)
+ - **Auto-Selection:** Newly added Huggys are automatically selected on canvas for immediate editing
+ - Integrated HuggyMenu into Sidebar with toggle functionality
+ - Users can drag, scale, rotate, and delete Huggys like any canvas object
+ - **Transparent Background:** Huggy thumbnails display without white background for cleaner look
+ - **Unified Grid Spacing:** 5px gap between Huggy thumbnails with 5px padding for balanced layout
+ - **Future Assets:** Automatically loads new Huggys when added to the dataset
+- All previous phases (1-4, 9) fully functional
+- Dev server running cleanly at http://localhost:3000
+
+## Known Issues (To Be Fixed Later)
+
+### 1. Responsive Layout Scaling Issue ⚠️
+**Problem:** When switching between different canvas sizes, layout assets progressively reduce in size with each change. This cumulative scaling is unwanted behavior.
+
+**Impact:** Layout objects become unusably small after multiple canvas size switches.
+
+**Temporary Solution:** Remove the responsive layout repositioning feature (App.tsx:155-167) until a proper fix is implemented.
+
+**Root Cause:** The scaling logic applies transformation to already-transformed objects, causing cumulative scale reduction.
+
+**Future Fix:** Implement absolute positioning system or store original dimensions to calculate scale from baseline rather than current state.
+
+---
+
+**Note:** This issue is documented for future fixes and does not block progression to other phases.
+
+---
+
+## Feature Backlog
+
+### Huggy Menu "Expand" Feature ⏸️
+**Status:** Backlogged for future implementation
+
+**Original Requirement:**
+- Add an "Expand" button in the Huggy menu
+- Allow users to see more Huggys at once (e.g., 12 or 18 instead of 6)
+- Toggle between compressed and expanded views
+
+**Current Workaround:**
+- Users can click "Load More" button to progressively load additional Huggys
+- Scroll to browse all 44 Huggys
+
+**Why Backlogged:**
+- Current "Load More" functionality provides similar UX
+- Prioritizing core features (Image Upload, Export) first
+- Can be added as a quality-of-life enhancement later
+
+**Future Implementation Notes:**
+- Add "Expand View" toggle button in menu header
+- Options: Show 6 (default) | 12 (expanded) | 18 (full expanded)
+- Maintain scroll position when toggling
+- Consider performance with many images loaded simultaneously
+
+---
+
+### Hover to Show Object Bounding Box ⏸️
+**Status:** Backlogged for future implementation
+
+**Original Requirement:**
+- When hovering over any canvas object, display its bounding box
+- Visual feedback to show interactive areas before clicking
+- Helps users identify object boundaries
+
+**Implementation Details:**
+- Add hover state detection to CanvasObject component
+- Show subtle bounding box outline on hover (different from selection transformer)
+- Use lighter color or dashed border to distinguish from selection state
+- Should not interfere with drag operations or transformer handles
+
+**Why Backlogged:**
+- Nice-to-have UX enhancement, not critical for core functionality
+- Current selection system (click to select) works adequately
+- Prioritizing core features (Image Upload, Export) first
+
+**Future Implementation Notes:**
+- Consider performance impact with many objects on canvas
+- Ensure hover state doesn't conflict with selection/transformer states
+- May need throttling for smooth hover detection
+
+---
+
+### Dynamic Canvas Corner Radius & Drop Shadow ⏸️
+**Status:** Backlogged for future implementation
+
+**Original Requirement:**
+- **Cursor IN canvas area:**
+ - Corner radius: 0px (sharp corners)
+ - Drop shadow: Increased intensity (more obvious)
+- **Cursor OUTSIDE canvas area:**
+ - Corner radius: 10px (rounded corners)
+ - Drop shadow: Original subtle value
+
+**Visual Effect:**
+- Creates a "focus mode" when user is working on the canvas
+- Sharp corners give more workspace feel when actively editing
+- Rounded corners provide softer appearance when not in use
+
+**Implementation Details:**
+- Track cursor position with mouseEnter/mouseLeave events on canvas container
+- CSS transition for smooth corner radius and shadow changes
+- Canvas component style updates based on hover state
+- Transition duration: ~200ms for smooth effect
+
+**Why Backlogged:**
+- Polish/aesthetic feature, not functional requirement
+- May cause visual distraction during active editing
+- Prioritizing core features (Image Upload, Export) first
+
+**Future Implementation Notes:**
+- Test with various canvas sizes to ensure effect works well
+- Consider if drop shadow change affects perceived canvas position
+- May need to adjust for different screen sizes/resolutions
+- Original drop shadow value: TBD (measure current implementation)
+- Increased drop shadow value: TBD (design decision needed)
diff --git a/SESSION_LOGS.md b/SESSION_LOGS.md
new file mode 100644
index 0000000000000000000000000000000000000000..0270d54bd7ffb8daac0295b0899219df289331a7
--- /dev/null
+++ b/SESSION_LOGS.md
@@ -0,0 +1,331 @@
+# HF Thumbnail Crafter - Session Logs
+
+## Session 2025-11-21: Canvas Header Polish & Sidebar Icon Replacement
+
+### Session Summary
+Completed Phase 4 (Canvas Header) with polished hover states, smooth transitions, and animated button expansions. Replaced all Lucide sidebar icons with custom Figma-exported icons featuring proper default and selected states.
+
+### Changes Made
+
+#### 1. Canvas Header Hover States ✅
+**Components Modified:** `BgColorSelector.tsx`, `CanvasSizeSelector.tsx`
+
+**Removed hover animations:**
+- Removed text expansion animation on hover from CanvasSizeSelector
+- Text labels now only appear when option is selected
+- Added subtle `#f0f2f4` hover background to both components
+
+**Updated transitions:**
+- Changed from `0.2s` to `150ms ease-in-out` for consistency
+- Applied to both background color and canvas size selectors
+
+#### 2. Dimension Text Opacity ✅
+**Component Modified:** `CanvasSizeSelector.tsx`
+- Set dimension text (e.g., "1200x675") to **80% opacity** for better visual hierarchy
+- Improves readability while maintaining design aesthetic
+
+#### 3. Canvas Area Transitions ✅
+**Components Modified:** `Canvas.tsx`, `CanvasContainer.tsx`, `CanvasHeader.tsx`
+
+**Canvas transitions:**
+- Width and height animate smoothly with `150ms ease-in-out`
+- Background color changes instantly (no transition) as requested
+- Overflow hidden prevents layout shift during animation
+
+**Container transitions:**
+- Added `all 0.15s ease-in-out` transition to canvas container
+- Header position adjusts smoothly when canvas size changes
+
+#### 4. Animated Button Expansion ✅
+**Component Modified:** `CanvasSizeSelector.tsx`
+
+**Three-part animation system:**
+1. **Width expansion/contraction:**
+ - Unselected: `minWidth: '38px'` (icon only)
+ - Selected: Expands to fit icon + text
+ - Transition: `150ms ease-in-out`
+
+2. **Text fade-in/fade-out:**
+ - Unselected: `opacity: 0`
+ - Selected: `opacity: 0.8`
+ - Transition: `150ms ease-in-out`
+
+3. **Text slide-in effect:**
+ - Unselected: `translateX(-10px)`
+ - Selected: `translateX(0)`
+ - Transition: `150ms ease-in-out`
+
+**Implementation details:**
+- Text span always rendered (not conditional) to enable smooth transitions
+- Button `justifyContent` switches between `center` (unselected) and `flex-start` (selected)
+- Padding adjusts dynamically: `9px` unselected, `10px` selected
+- All animations synchronized for smooth, professional effect
+
+#### 5. Sidebar Icon Replacement ✅
+**New Icon Components Created:**
+- `IconLayout.tsx` - Layout/grid icon with default (#545865) and selected (white) states
+- `IconText.tsx` - Text "T" icon with default and selected states
+- `IconImage.tsx` - Image/photo icon with default and selected states
+- `IconHuggy.tsx` - Huggy mascot icon (single state as requested)
+
+**Component Modified:** `Sidebar.tsx`
+- Removed Lucide React imports (`Layers`, `Smile`, `Image`, `Type`)
+- Replaced with custom Figma-exported icon components
+- Icons loaded from localhost Figma MCP server URLs
+
+**Icon Implementation Pattern:**
+```typescript
+interface IconProps {
+ selected?: boolean;
+}
+
+export default function Icon({ selected = false }: IconProps) {
+ const imgDefault = "http://localhost:3845/assets/[hash].svg";
+ const imgSelected = "http://localhost:3845/assets/[hash].svg";
+
+ return (
+
+

+
+ );
+}
+```
+
+**Huggy Icon Fix:**
+- Initially missed `display: 'contents'` CSS property on Face container
+- This property makes container transparent in layout, allowing children to position correctly
+- Fixed by adding `display: 'contents'` to Face container div
+
+### Files Modified
+- `src/components/CanvasHeader/BgColorSelector.tsx`
+- `src/components/CanvasHeader/CanvasSizeSelector.tsx`
+- `src/components/CanvasHeader/CanvasHeader.tsx`
+- `src/components/Canvas/Canvas.tsx`
+- `src/components/Canvas/CanvasContainer.tsx`
+- `src/components/Sidebar/Sidebar.tsx`
+- `src/components/Icons/IconLayout.tsx` (created)
+- `src/components/Icons/IconText.tsx` (created)
+- `src/components/Icons/IconImage.tsx` (created)
+- `src/components/Icons/IconHuggy.tsx` (created)
+- `PROJECT_PLAN.md` (updated)
+
+### Test Results
+✅ Build successful without errors
+✅ Dev server running at http://localhost:3001/
+✅ All hover states working smoothly
+✅ Canvas size changes animate beautifully
+✅ Background color changes instantly
+✅ Button expansion animations synchronized perfectly
+✅ All sidebar icons display correctly with proper states
+✅ Huggy mascot icon renders correctly
+
+### Phase Status
+**Phase 2: Core UI Structure** - ✅ Fully Complete (with custom Figma icons)
+**Phase 4: Canvas Header** - ✅ Fully Complete (with polished animations and transitions)
+
+### Next Session
+Ready to proceed with **Phase 5: Layout Feature** - Pre-designed layout templates with floating menu component.
+
+---
+
+## Session 2025-11-20: Text Editing Bug Fixes
+
+### Session Summary
+Fixed critical text editing issues and improved the text feature implementation. The main focus was resolving the missing bounding box problem and improving text editing behavior.
+
+### Issues Addressed
+
+#### 1. Delete Key Behavior ✅
+**Problem:** When editing text, pressing Delete/Backspace would delete the entire text object instead of individual characters.
+
+**Solution:** Added context-aware delete key handling in `App.tsx`:
+```typescript
+// Check if user is editing text
+const isEditingText = objects.some(obj => obj.type === 'text' && obj.isEditing);
+
+// Delete selected object (only when NOT editing text)
+if ((e.key === 'Delete' || e.key === 'Backspace') && selectedId && !isEditingText) {
+ e.preventDefault();
+ deleteSelected();
+}
+```
+
+#### 2. Missing Bounding Box (Critical) ✅
+**Problem:** Transformer/bounding box not appearing when text objects were selected. Console showed `Node: undefined` repeatedly.
+
+**Root Cause:** In `Canvas.tsx` line ~286, refs were passed as objects instead of callback functions:
+```typescript
+// BROKEN:
+shapeRef={{ current: shapeRefs.current.get(obj.id) || null }}
+```
+This created new objects on each render but never actually stored the node references.
+
+**Solution:** Implemented callback ref pattern:
+```typescript
+// FIXED:
+shapeRef={(node: Konva.Node | null) => {
+ if (node) {
+ shapeRefs.current.set(obj.id, node);
+ } else {
+ shapeRefs.current.delete(obj.id);
+ }
+}}
+```
+
+**Additional Changes:**
+- Updated type definitions in all components to accept both callback and RefObject refs:
+ ```typescript
+ shapeRef?: ((node: Konva.Node | null) => void) | React.RefObject;
+ ```
+- Updated `TextEditable.tsx`, `CanvasObject.tsx`, and `ImageRenderer` to handle both ref patterns
+
+#### 3. Duplicate/Transparent Text During Editing ✅
+**Problem:** Half-transparent text showing behind textarea during editing, creating visual duplicates.
+
+**Solution:** Changed text opacity to 0 during editing in `TextEditable.tsx`:
+```typescript
+
+```
+
+#### 4. Textarea Rendering ✅
+**Problem:** Initial attempt to render textarea inside Konva tree caused errors ("Konva has no node with the type textarea").
+
+**Solution:** Moved textarea rendering completely outside Konva tree in `Canvas.tsx`:
+```typescript
+{editingText && (
+
+)}
+```
+
+### TypeScript Fixes
+Fixed multiple TypeScript compilation errors:
+- Removed unused imports (`useState`, `getCanvasDimensions`, `KonvaText`)
+- Removed unused variables (`editingTextId`, `setEditingTextId`)
+- Removed `onTextChange` prop (no longer needed, handled directly in Canvas.tsx)
+- Fixed union type handling in `useCanvasState.ts` using `DistributiveOmit` type
+- Fixed type assertion in `updateSelected` function
+
+### Files Modified
+- `src/App.tsx` - Delete key behavior
+- `src/components/Canvas/Canvas.tsx` - Callback refs, textarea rendering, transformer updates
+- `src/components/Canvas/CanvasObject.tsx` - Ref handling, removed unused props
+- `src/components/Canvas/TextEditable.tsx` - Opacity handling, ref pattern
+- `src/hooks/useCanvasState.ts` - DistributiveOmit type, type assertions
+- `src/types/canvas.types.ts` - (already had `isEditing` and `isFixedSize` properties)
+
+### Test Results
+✅ Build successful without errors
+✅ Dev server running at http://localhost:3000/
+✅ Bounding box now visible when selecting text objects
+✅ Delete key works correctly in both contexts
+✅ No duplicate/transparent text during editing
+✅ Real-time font size adaptation working
+✅ Text position remains stable between edit/view modes
+
+### Current Feature Status
+**Phase 9: Text Feature** is now **Core Complete**:
+- ✅ Text creation with button and 'T' keyboard shortcut
+- ✅ Stage 1: Auto-growing text box on initial creation (68px default font)
+- ✅ Stage 2: Fixed-size box with dynamic font scaling after first transform
+- ✅ Real-time font size adaptation while typing
+- ✅ Transformer/bounding box with blue border and white-filled corner handles
+- ✅ Drag, scale, rotate functionality
+- ✅ Double-click to edit
+- ✅ Context-aware delete key
+- ⚠️ Text toolbar (Phase 10) - Still TODO
+
+### Next Session
+Start with **Phase 4: Canvas Header** - Implement background color selector and canvas size selector controls.
+
+---
+
+## Session 2025-11-20 (Continued): Additional Text Feature Refinements
+
+### Additional Issues Fixed
+
+#### 5. Text Scaling on Transform ✅
+**Problem:** When scaling up a text object, the font size would revert to default after releasing the mouse.
+
+**Root Cause:** The `fontSize` property wasn't being updated during transformation - only width/height were changing.
+
+**Solution:** Modified `handleTransformEnd` in Canvas.tsx:
+```typescript
+// Handle text objects: scale fontSize and mark as fixed size
+if (obj.type === 'text') {
+ // Use the smaller scale factor to maintain readability
+ const scaleFactor = Math.min(scaleX, scaleY);
+ const newFontSize = Math.max(10, obj.fontSize * scaleFactor);
+
+ return {
+ ...updated,
+ fontSize: newFontSize,
+ isFixedSize: true
+ };
+}
+```
+
+**Behavior:** Font size now scales proportionally with the text box during transformation (minimum 10px).
+
+#### 6. Live Bounding Box Resize While Editing ✅
+**Problem:** When deleting letters, the bounding box didn't shrink to hug the content, leaving empty space.
+
+**Solution:** Modified `handleTextareaChange` in Canvas.tsx (lines 166-168):
+```typescript
+// Always auto-grow/shrink box while editing (live resize)
+const newWidth = Math.max(100, tempText.width() + 20);
+const newHeight = Math.max(40, tempText.height() + 10);
+```
+
+**Behavior:** Text box now dynamically resizes in real-time as you type or delete characters.
+
+#### 7. Cursor Position on Double-Click ✅
+**Problem:** When double-clicking text to edit, all text was selected instead of placing cursor at click position.
+
+**Solution:**
+1. Added `calculateCursorPosition` helper function in Canvas.tsx (lines 19-55)
+2. Updated `handleDoubleClick` in TextEditable.tsx to capture click coordinates
+3. Modified textarea focus logic to set cursor at calculated position
+
+**Behavior:** Cursor now appears at the character closest to where you clicked.
+
+#### 8. Text Hint Box Positioning ✅
+**Problem:** Text hint box appeared in the middle-right of sidebar instead of next to the Text button.
+
+**Solution:** Repositioned hint box in Sidebar.tsx:
+- Moved `relative` positioning to inner sidebar container
+- Positioned hint box absolutely with `left-[calc(100%+4px)] bottom-[5px]`
+- Added 4px gap between sidebar and hint box
+
+**Behavior:** Hint box now appears directly to the right of the Text button at the same vertical level.
+
+### Files Modified
+- `src/components/Canvas/Canvas.tsx` - Text scaling, live resize, cursor positioning
+- `src/components/Canvas/TextEditable.tsx` - Capture click coordinates for cursor
+- `src/components/Canvas/CanvasObject.tsx` - Updated type signatures for cursor positioning
+- `src/components/Sidebar/Sidebar.tsx` - Hint box positioning
+
+### Test Results
+✅ Font size scales proportionally when transforming text objects
+✅ Bounding box shrinks/grows live while editing
+✅ Cursor appears at exact click position on double-click
+✅ Text hint box aligned with Text button
+✅ All buttons in sidebar maintain consistent width
+
+### Phase 9 Status
+**Phase 9: Text Feature** is now **Fully Complete** with all refinements:
+- ✅ All core functionality working
+- ✅ All edge cases handled
+- ✅ All UX improvements implemented
+
+### Next Session
+Ready to proceed with **Phase 4: Canvas Header** - Implement background color selector and canvas size selector controls.
+
+---
diff --git a/index.html b/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..cfca0156d8916da9a35d1919adadc2346e721737
--- /dev/null
+++ b/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+ HF Thumbnail Crafter
+
+
+
+
+
+
+
+
+
+
+
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..13c80b170521a24fe4882eea226ce7b54da5e444
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3191 @@
+{
+ "name": "hf-thumbnail-crafter",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "hf-thumbnail-crafter",
+ "version": "0.1.0",
+ "dependencies": {
+ "konva": "^9.3.6",
+ "lucide-react": "^0.344.0",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-konva": "^18.2.10"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "autoprefixer": "^10.4.17",
+ "postcss": "^8.4.35",
+ "tailwindcss": "^3.4.1",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.2"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz",
+ "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz",
+ "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.27.2",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "@babel/traverse": "^7.28.3"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz",
+ "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.4"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz",
+ "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz",
+ "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz",
+ "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz",
+ "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz",
+ "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz",
+ "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz",
+ "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz",
+ "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz",
+ "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz",
+ "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz",
+ "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz",
+ "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz",
+ "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz",
+ "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz",
+ "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz",
+ "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz",
+ "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz",
+ "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz",
+ "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz",
+ "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz",
+ "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/react": {
+ "version": "18.3.27",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz",
+ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/prop-types": "*",
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^18.0.0"
+ }
+ },
+ "node_modules/@types/react-reconciler": {
+ "version": "0.28.9",
+ "resolved": "https://registry.npmjs.org/@types/react-reconciler/-/react-reconciler-0.28.9.tgz",
+ "integrity": "sha512-HHM3nxyUZ3zAylX8ZEyrDNd2XZOnQ0D5XfunJF5FLQnZbHHYq4UWvW1QfelQNXv1ICNkwYhfxjwfnqivYB6bFg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.4.22",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.22.tgz",
+ "integrity": "sha512-ARe0v/t9gO28Bznv6GgqARmVqcWOV3mfgUPn9becPHMiD3o9BwlRgaeccZnwTpZ7Zwqrm+c1sUSsMxIzQzc8Xg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.27.0",
+ "caniuse-lite": "^1.0.30001754",
+ "fraction.js": "^5.3.4",
+ "normalize-range": "^0.1.2",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.8.29",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.29.tgz",
+ "integrity": "sha512-sXdt2elaVnhpDNRDz+1BDx1JQoJRuNk7oVlAlbGiFkLikHCAQiccexF/9e91zVi6RCgqspl04aP+6Cnl9zRLrA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.js"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
+ "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.0",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.0.tgz",
+ "integrity": "sha512-tbydkR/CxfMwelN0vwdP/pLkDwyAASZ+VfWm4EOwlB6SWhx1sYnWLqo8N5j0rAzPfzfRaxt0mM/4wPU/Su84RQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.8.25",
+ "caniuse-lite": "^1.0.30001754",
+ "electron-to-chromium": "^1.5.249",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.1.4"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001756",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001756.tgz",
+ "integrity": "sha512-4HnCNKbMLkLdhJz3TToeVWHSnfJvPaq6vu/eRP0Ahub/07n484XHhBF5AJoSGHdVrS8tKFauUQz8Bp9P7LVx7A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.256",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.256.tgz",
+ "integrity": "sha512-uqYq1IQhpXXLX+HgiXdyOZml7spy4xfy42yPxcCCRjswp0fYM2X+JwCON07lqnpLEGVCj739B7Yr+FngmHBMEQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fastq": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz",
+ "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/its-fine": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/its-fine/-/its-fine-1.2.5.tgz",
+ "integrity": "sha512-fXtDA0X0t0eBYAGLVM5YsgJGsJ5jEmqZEPrGbzdf5awjv0xE7nqv3TVnvtUF060Tkes15DbDAKW/I48vsb6SyA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/konva": {
+ "version": "9.3.22",
+ "resolved": "https://registry.npmjs.org/konva/-/konva-9.3.22.tgz",
+ "integrity": "sha512-yQI5d1bmELlD/fowuyfOp9ff+oamg26WOCkyqUyc+nczD/lhRa3EvD2MZOoc4c1293TAubW9n34fSQLgSeEgSw==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/lavrton"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/konva"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/lavrton"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lucide-react": {
+ "version": "0.344.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.344.0.tgz",
+ "integrity": "sha512-6YyBnn91GB45VuVT96bYCOKElbJzUHqp65vX8cDcu55MQL9T969v4dhGClpljamuI/+KMO9P6w9Acq1CVQGvIQ==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
+ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.27",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
+ "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/normalize-range": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz",
+ "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-scurry/node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.6",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
+ "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-konva": {
+ "version": "18.2.14",
+ "resolved": "https://registry.npmjs.org/react-konva/-/react-konva-18.2.14.tgz",
+ "integrity": "sha512-lBDe/5fTgquMdg1AHI0B16YZdAOvEhWMBWuo12ioyY0icdxcz9Cf12j86fsCJCHdnvjUOlZeC0f5q+siyHbD4Q==",
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/lavrton"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/konva"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/lavrton"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@types/react-reconciler": "^0.28.2",
+ "its-fine": "^1.1.1",
+ "react-reconciler": "~0.29.0",
+ "scheduler": "^0.23.0"
+ },
+ "peerDependencies": {
+ "konva": "^8.0.1 || ^7.2.5 || ^9.0.0 || ^10.0.0",
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/react-reconciler": {
+ "version": "0.29.2",
+ "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.2.tgz",
+ "integrity": "sha512-zZQqIiYgDCTP/f1N/mAR10nJGrPD2ZR+jDSEsKWJHYC7Cm2wodlwbR3upZRdC3cjIjSlTLNVyO7Iu0Yy7t2AYg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.53.3",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz",
+ "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.3",
+ "@rollup/rollup-android-arm64": "4.53.3",
+ "@rollup/rollup-darwin-arm64": "4.53.3",
+ "@rollup/rollup-darwin-x64": "4.53.3",
+ "@rollup/rollup-freebsd-arm64": "4.53.3",
+ "@rollup/rollup-freebsd-x64": "4.53.3",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.3",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.3",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.3",
+ "@rollup/rollup-linux-arm64-musl": "4.53.3",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.3",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.3",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.3",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-gnu": "4.53.3",
+ "@rollup/rollup-linux-x64-musl": "4.53.3",
+ "@rollup/rollup-openharmony-arm64": "4.53.3",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.3",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.3",
+ "@rollup/rollup-win32-x64-gnu": "4.53.3",
+ "@rollup/rollup-win32-x64-msvc": "4.53.3",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "7.1.2",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz",
+ "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.0",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
+ "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "glob": "^10.3.10",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.18",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.18.tgz",
+ "integrity": "sha512-6A2rnmW5xZMdw11LYjhcI5846rt9pbLSabY5XPxo+XWdxwZaFEn47Go4NzFiHu9sNNmr/kXivP1vStfvMaK1GQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz",
+ "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..bb996717b2b4107b0f053ee660e995420acb2d52
--- /dev/null
+++ b/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "hf-thumbnail-crafter",
+ "private": true,
+ "version": "0.1.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "react-konva": "^18.2.10",
+ "konva": "^9.3.6",
+ "lucide-react": "^0.344.0"
+ },
+ "devDependencies": {
+ "@types/react": "^18.3.3",
+ "@types/react-dom": "^18.3.0",
+ "@vitejs/plugin-react": "^4.3.1",
+ "typescript": "^5.5.3",
+ "vite": "^5.4.2",
+ "tailwindcss": "^3.4.1",
+ "postcss": "^8.4.35",
+ "autoprefixer": "^10.4.17"
+ }
+}
diff --git a/postcss.config.js b/postcss.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..2e7af2b7f1a6f391da1631d93968a9d487ba977d
--- /dev/null
+++ b/postcss.config.js
@@ -0,0 +1,6 @@
+export default {
+ plugins: {
+ tailwindcss: {},
+ autoprefixer: {},
+ },
+}
diff --git a/public/assets/layouts/HF logo.png b/public/assets/layouts/HF logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6114a7fb3d8a5a50b1b7cf1e07c26ed8337941d
--- /dev/null
+++ b/public/assets/layouts/HF logo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5f411ff4ff03623cb191fd29b0fdc1caf8d0727d5359a513f791149a5d60089d
+size 24170
diff --git a/public/assets/layouts/collabX.svg b/public/assets/layouts/collabX.svg
new file mode 100644
index 0000000000000000000000000000000000000000..efaa0c186d9b58f07c45dcf12964295e6f59a96a
--- /dev/null
+++ b/public/assets/layouts/collabX.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/assets/layouts/docsHFLogo.png b/public/assets/layouts/docsHFLogo.png
new file mode 100644
index 0000000000000000000000000000000000000000..aff198572d2372c25b0c44e8603949f46d2ceef0
--- /dev/null
+++ b/public/assets/layouts/docsHFLogo.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d316c53de9eec1aa1fdb5a1ded737b1ec7a0a784875010f4aba12051dae71ba3
+size 81972
diff --git a/public/assets/layouts/docs_thumbnail.png b/public/assets/layouts/docs_thumbnail.png
new file mode 100644
index 0000000000000000000000000000000000000000..083f1aed7d1d46b47d8e186ad0ffa55a8f32a26a
--- /dev/null
+++ b/public/assets/layouts/docs_thumbnail.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cb8dd0d975672aa19fc021757fc786ddeae32f113dc85f9d8b6d97ecad16fa0c
+size 5691
diff --git a/public/assets/layouts/fCollab_huggy_asset.png b/public/assets/layouts/fCollab_huggy_asset.png
new file mode 100644
index 0000000000000000000000000000000000000000..06906037e343e41ed78f23e84abb44114c862b5c
--- /dev/null
+++ b/public/assets/layouts/fCollab_huggy_asset.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cf1abed7a8e5ac98d2cd8285b976ebc96663710b73c634e879dfe96a6e494511
+size 260187
diff --git a/public/assets/layouts/fCollab_huggy_hand_asset.png b/public/assets/layouts/fCollab_huggy_hand_asset.png
new file mode 100644
index 0000000000000000000000000000000000000000..c377e17f2c9abc56e147dbf6337fc02bccef6c7b
--- /dev/null
+++ b/public/assets/layouts/fCollab_huggy_hand_asset.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:29ee391a131cf7f657b73a66f5181e3aea7614ebd313ac5b8f84a38f2f1d0989
+size 45477
diff --git a/public/assets/layouts/fCollab_thumbnail.png b/public/assets/layouts/fCollab_thumbnail.png
new file mode 100644
index 0000000000000000000000000000000000000000..cca2fb812afa418bd6cd34360fd41225d1d2851c
--- /dev/null
+++ b/public/assets/layouts/fCollab_thumbnail.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a5c7abaa9c25ff8b5dd4f2b69d131f890ccb166614658f7edf29844fae5da934
+size 5615
diff --git a/public/assets/layouts/logo_placehoder.png b/public/assets/layouts/logo_placehoder.png
new file mode 100644
index 0000000000000000000000000000000000000000..5bc622de7482b9269ede77d47814aa2674d70ac2
--- /dev/null
+++ b/public/assets/layouts/logo_placehoder.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0c0df67b939b843f779ea72026f4de1ce1f4a2cab5c0df1555a5689c934a6402
+size 177740
diff --git a/public/assets/layouts/sCollab_thumbnail.png b/public/assets/layouts/sCollab_thumbnail.png
new file mode 100644
index 0000000000000000000000000000000000000000..e6fb78bcdffdc2389c2f07e0d68f98944062afba
--- /dev/null
+++ b/public/assets/layouts/sCollab_thumbnail.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:c74c951bed7a2e3da996e4cd71cf6c178a1ec2b030d52c96989e60f9781e4474
+size 4905
diff --git a/public/assets/layouts/sandwitch_thumbnail.png b/public/assets/layouts/sandwitch_thumbnail.png
new file mode 100644
index 0000000000000000000000000000000000000000..8562dc0667a262faac34b7dedf0e68701470ff63
--- /dev/null
+++ b/public/assets/layouts/sandwitch_thumbnail.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:b6bbdf806c9dc28194cbf2ff011366ee748fbabb9b5756bf24bf71430fe298c3
+size 6625
diff --git a/public/assets/layouts/snadwithc_huggy_asset.png b/public/assets/layouts/snadwithc_huggy_asset.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d91f3198f3401e2161696d5cfbda411e4a772d7
--- /dev/null
+++ b/public/assets/layouts/snadwithc_huggy_asset.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:3d5b4ca3a240a4128de6eec5d78601aa210b57d926457de594310520c9c05f11
+size 32910
diff --git a/public/fonts/Bison-Bold(PersonalUse).ttf b/public/fonts/Bison-Bold(PersonalUse).ttf
new file mode 100644
index 0000000000000000000000000000000000000000..90499bcfbc8cb3bf5e8ce82485a523ef4d8f8dd1
Binary files /dev/null and b/public/fonts/Bison-Bold(PersonalUse).ttf differ
diff --git a/src/App.tsx b/src/App.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..b0e0d431a687190538f4201c5c00cb52abe812a6
--- /dev/null
+++ b/src/App.tsx
@@ -0,0 +1,375 @@
+import { useEffect, useState } from 'react';
+import Sidebar from './components/Sidebar/Sidebar';
+import ExportButton from './components/ExportButton/ExportButton';
+import CanvasContainer from './components/Canvas/CanvasContainer';
+import Canvas from './components/Canvas/Canvas';
+import CanvasHeader from './components/CanvasHeader/CanvasHeader';
+import { useCanvasState } from './hooks/useCanvasState';
+import { LayoutType } from './types/canvas.types';
+import { getLayoutById } from './data/layouts';
+import { getCanvasDimensions } from './utils/canvas.utils';
+import { Huggy } from './data/huggys';
+
+function App() {
+ const {
+ objects,
+ selectedIds,
+ canvasSize,
+ bgColor,
+ setObjects,
+ setSelectedIds,
+ setCanvasSize,
+ setBgColor,
+ addObject,
+ deleteSelected,
+ undo,
+ redo
+ } = useCanvasState();
+
+ const [textCreationMode, setTextCreationMode] = useState(false);
+ const [activeButton, setActiveButton] = useState<'layout' | 'huggy' | 'image' | 'text' | null>(null);
+
+ const handleLayoutClick = () => {
+ console.log('Layout clicked');
+ // Toggle off if already active, otherwise activate
+ setActiveButton(activeButton === 'layout' ? null : 'layout');
+ setTextCreationMode(false);
+ };
+
+ const handleHuggyClick = () => {
+ console.log('Huggy clicked');
+ // Toggle off if already active, otherwise activate
+ setActiveButton(activeButton === 'huggy' ? null : 'huggy');
+ setTextCreationMode(false);
+ };
+
+ const handleImageClick = () => {
+ console.log('Image clicked');
+ // Toggle off if already active, otherwise activate
+ setActiveButton(activeButton === 'image' ? null : 'image');
+ setTextCreationMode(false);
+ };
+
+ const handleTextClick = () => {
+ // Toggle off if already active, otherwise activate text creation mode
+ if (activeButton === 'text') {
+ setTextCreationMode(false);
+ setActiveButton(null);
+ } else {
+ setTextCreationMode(true);
+ setActiveButton('text');
+ }
+ };
+
+ const handleSelectLayout = (layoutId: LayoutType) => {
+ const layout = getLayoutById(layoutId);
+ if (!layout) return;
+
+ // Check if there are user-added objects (not from layout)
+ const hasUserObjects = objects.some(obj => !obj.isFromLayout);
+
+ if (hasUserObjects) {
+ // Prompt user to choose
+ const keepEdits = window.confirm(
+ 'You have custom objects on the canvas. Do you want to keep them with the new layout?\n\n' +
+ 'Click OK to keep your objects.\n' +
+ 'Click Cancel to load a fresh layout (your objects will be removed).'
+ );
+
+ if (keepEdits) {
+ // Keep existing objects and add new layout objects
+ const layoutObjects = layout.objects.map(obj => ({
+ ...obj,
+ id: `${obj.id}-${Date.now()}`,
+ isFromLayout: true
+ }));
+ setObjects([...objects, ...layoutObjects]);
+ } else {
+ // Replace all with fresh layout
+ const layoutObjects = layout.objects.map(obj => ({
+ ...obj,
+ id: `${obj.id}-${Date.now()}`,
+ isFromLayout: true
+ }));
+ setObjects(layoutObjects);
+ }
+ } else {
+ // No user objects, just load the layout normally
+ const layoutObjects = layout.objects.map(obj => ({
+ ...obj,
+ id: `${obj.id}-${Date.now()}`,
+ isFromLayout: true
+ }));
+ setObjects(layoutObjects);
+ }
+
+ setSelectedIds([]);
+ setActiveButton(null);
+ };
+
+ const handleSelectHuggy = (huggy: Huggy) => {
+ // Get canvas dimensions to center the Huggy
+ const dimensions = getCanvasDimensions(canvasSize);
+ const huggySize = 200; // Default Huggy size (can be scaled by user later)
+
+ // Add Huggy image to the center of the canvas
+ addObject({
+ type: 'image',
+ x: dimensions.width / 2 - huggySize / 2,
+ y: dimensions.height / 2 - huggySize / 2,
+ width: huggySize,
+ height: huggySize,
+ src: huggy.thumbnail,
+ rotation: 0,
+ isFromLayout: false
+ });
+
+ // Close the Huggy menu
+ setActiveButton(null);
+ };
+
+ const handleTextCreate = (x: number, y: number) => {
+ // Create text object at clicked position with default 68px font
+ addObject({
+ type: 'text',
+ x: x,
+ y: y,
+ width: 100, // Initial small width, will grow with text
+ height: 80, // Initial height for 68px font
+ rotation: 0,
+ text: '',
+ fontSize: 68,
+ fontFamily: 'Inter',
+ fill: '#000000',
+ bold: false,
+ italic: false,
+ isFixedSize: false, // Start in Stage 1 (auto-growing mode)
+ isEditing: false
+ });
+
+ // Deactivate text creation mode after creating text
+ setTextCreationMode(false);
+ setActiveButton(null);
+ };
+
+ const handleExport = (filename: string) => {
+ console.log('Export:', filename);
+ // Export functionality will be implemented in Phase 11
+ };
+
+ // DISABLED: Handle responsive layout repositioning when canvas size changes
+ // This feature caused cumulative scaling issues - see Known Issues in PROJECT_PLAN.md
+ // TODO: Re-implement with absolute positioning system
+ // useEffect(() => {
+ // const prevSize = previousCanvasSizeRef.current;
+ // const currentSize = canvasSize;
+
+ // // Skip if this is the initial render
+ // if (prevSize === currentSize) {
+ // return;
+ // }
+
+ // // Skip if there are no objects to scale
+ // if (objects.length === 0) {
+ // previousCanvasSizeRef.current = currentSize;
+ // return;
+ // }
+
+ // // Get dimensions for previous and current canvas sizes
+ // const prevDimensions = getCanvasDimensions(prevSize);
+ // const currentDimensions = getCanvasDimensions(currentSize);
+
+ // // Calculate scale factors for positions
+ // const scaleX = currentDimensions.width / prevDimensions.width;
+ // const scaleY = currentDimensions.height / prevDimensions.height;
+
+ // // Use uniform scale factor to maintain aspect ratios
+ // // We use the minimum to ensure everything fits within the canvas
+ // const uniformScale = Math.min(scaleX, scaleY);
+
+ // // Scale all objects proportionally
+ // const scaledObjects = objects.map(obj => ({
+ // ...obj,
+ // x: obj.x * scaleX,
+ // y: obj.y * scaleY,
+ // width: obj.width * uniformScale,
+ // height: obj.height * uniformScale,
+ // // Scale fontSize for text objects with uniform scale
+ // ...(obj.type === 'text' && { fontSize: obj.fontSize * uniformScale }),
+ // // Scale offsetX and offsetY if they exist
+ // ...(obj.offsetX !== undefined && { offsetX: obj.offsetX * scaleX }),
+ // ...(obj.offsetY !== undefined && { offsetY: obj.offsetY * scaleY })
+ // }));
+
+ // setObjects(scaledObjects);
+ // previousCanvasSizeRef.current = currentSize;
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // }, [canvasSize]);
+
+ // Handle clicks outside canvas to deselect
+ useEffect(() => {
+ const handleClickOutside = (e: MouseEvent) => {
+ // Find the canvas container element
+ const canvasContainer = document.querySelector('.canvas-container');
+
+ // If click is outside canvas container and there are selected objects, deselect
+ if (canvasContainer && !canvasContainer.contains(e.target as Node) && selectedIds.length > 0) {
+ setSelectedIds([]);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => document.removeEventListener('mousedown', handleClickOutside);
+ }, [selectedIds, setSelectedIds]);
+
+ // Handle clicks outside sidebar to deselect active button
+ useEffect(() => {
+ const handleClickOutsideSidebar = (e: MouseEvent) => {
+ // Only process if there's an active button
+ if (!activeButton) return;
+
+ // Find all sidebar-related elements (sidebar + menus + hints)
+ const sidebar = document.querySelector('.sidebar-container');
+ const layoutSelector = document.querySelector('.layout-selector');
+ const huggyMenu = document.querySelector('.huggy-menu');
+ const textHint = document.querySelector('.text-hint');
+ const canvasContainer = document.querySelector('.canvas-container');
+
+ // Check if click is inside canvas (allow text creation mode to work)
+ const isInsideCanvas = canvasContainer && canvasContainer.contains(e.target as Node);
+ if (isInsideCanvas && textCreationMode) {
+ // Don't deselect if clicking on canvas in text creation mode
+ return;
+ }
+
+ // Check if click is outside all sidebar components
+ const isOutsideSidebar = sidebar && !sidebar.contains(e.target as Node);
+ const isOutsideLayoutSelector = !layoutSelector || !layoutSelector.contains(e.target as Node);
+ const isOutsideHuggyMenu = !huggyMenu || !huggyMenu.contains(e.target as Node);
+ const isOutsideTextHint = !textHint || !textHint.contains(e.target as Node);
+
+ // If click is outside all sidebar components, deselect
+ if (isOutsideSidebar && isOutsideLayoutSelector && isOutsideHuggyMenu && isOutsideTextHint) {
+ setActiveButton(null);
+ setTextCreationMode(false);
+ }
+ };
+
+ document.addEventListener('mousedown', handleClickOutsideSidebar);
+ return () => document.removeEventListener('mousedown', handleClickOutsideSidebar);
+ }, [activeButton, textCreationMode]);
+
+ // Handle keyboard shortcuts
+ useEffect(() => {
+ const handleKeyDown = (e: KeyboardEvent) => {
+ // Check if user is editing text
+ const isEditingText = objects.some(obj => obj.type === 'text' && obj.isEditing);
+
+ // Undo with Ctrl+Z (or Cmd+Z on Mac)
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey && !isEditingText) {
+ e.preventDefault();
+ undo();
+ return;
+ }
+
+ // Redo with Ctrl+Shift+Z (or Cmd+Shift+Z on Mac)
+ if ((e.ctrlKey || e.metaKey) && e.key === 'z' && e.shiftKey && !isEditingText) {
+ e.preventDefault();
+ redo();
+ return;
+ }
+
+ // Arrow key movement (only when NOT editing text and has selection)
+ if (!isEditingText && selectedIds.length > 0 && ['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
+ e.preventDefault();
+ const moveDistance = e.shiftKey ? 10 : 1;
+
+ const updatedObjects = objects.map(obj => {
+ if (selectedIds.includes(obj.id)) {
+ let newX = obj.x;
+ let newY = obj.y;
+
+ switch (e.key) {
+ case 'ArrowUp':
+ newY -= moveDistance;
+ break;
+ case 'ArrowDown':
+ newY += moveDistance;
+ break;
+ case 'ArrowLeft':
+ newX -= moveDistance;
+ break;
+ case 'ArrowRight':
+ newX += moveDistance;
+ break;
+ }
+
+ return { ...obj, x: newX, y: newY };
+ }
+ return obj;
+ });
+
+ setObjects(updatedObjects);
+ return;
+ }
+
+ // Delete selected objects (only when NOT editing text)
+ if ((e.key === 'Delete' || e.key === 'Backspace') && selectedIds.length > 0 && !isEditingText) {
+ e.preventDefault();
+ deleteSelected();
+ }
+
+ // Activate text creation mode with 'T' key
+ if (e.key === 't' || e.key === 'T') {
+ // Don't activate if user is typing in an input or textarea
+ const target = e.target as HTMLElement;
+ if (target.tagName !== 'INPUT' && target.tagName !== 'TEXTAREA') {
+ handleTextClick();
+ }
+ }
+ };
+
+ window.addEventListener('keydown', handleKeyDown);
+ return () => window.removeEventListener('keydown', handleKeyDown);
+ }, [selectedIds, deleteSelected, objects, setObjects, undo, redo]);
+
+ return (
+
+ {/* Sidebar */}
+
+
+ {/* Export Button */}
+
+
+ {/* Canvas Container */}
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/assets/icons/huggy-body.svg b/src/assets/icons/huggy-body.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6c5f6c0f19354d537a5e3139baed5b4d017c88ee
--- /dev/null
+++ b/src/assets/icons/huggy-body.svg
@@ -0,0 +1,7 @@
+
diff --git a/src/assets/icons/huggy-eyes.svg b/src/assets/icons/huggy-eyes.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6c0d7ec8c111f83574a87c9dedbb707ab985b3d6
--- /dev/null
+++ b/src/assets/icons/huggy-eyes.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/huggy-hands.svg b/src/assets/icons/huggy-hands.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6dd0079709c9f412f5c686f28470b624c575831a
--- /dev/null
+++ b/src/assets/icons/huggy-hands.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/huggy-mouth.svg b/src/assets/icons/huggy-mouth.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d76d5e939bb741d6876afb973ce87e2cd9077a59
--- /dev/null
+++ b/src/assets/icons/huggy-mouth.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/image-default.svg b/src/assets/icons/image-default.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d19582262b3b93c0b380ce10df5d90d39dcf7006
--- /dev/null
+++ b/src/assets/icons/image-default.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/image-selected.svg b/src/assets/icons/image-selected.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6f45944ff0501a102ae9c3a24e14769b42a02af9
--- /dev/null
+++ b/src/assets/icons/image-selected.svg
@@ -0,0 +1,6 @@
+
diff --git a/src/assets/icons/layout-default.svg b/src/assets/icons/layout-default.svg
new file mode 100644
index 0000000000000000000000000000000000000000..35ff0b824bfd7c00c7a232bbff996e10e014970a
--- /dev/null
+++ b/src/assets/icons/layout-default.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/layout-selected.svg b/src/assets/icons/layout-selected.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a21de9be574bca0804e3ce91b846f758358a59dd
--- /dev/null
+++ b/src/assets/icons/layout-selected.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/text-default.svg b/src/assets/icons/text-default.svg
new file mode 100644
index 0000000000000000000000000000000000000000..14daf9069f9625ec30286df82bafeec726286bf6
--- /dev/null
+++ b/src/assets/icons/text-default.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/assets/icons/text-selected.svg b/src/assets/icons/text-selected.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8c0940b84e86968355b1a1b27b52f116aeb7df38
--- /dev/null
+++ b/src/assets/icons/text-selected.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/components/Canvas/Canvas.tsx b/src/components/Canvas/Canvas.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6b8fc73e42931d7684837cb69aba9949808ffffb
--- /dev/null
+++ b/src/components/Canvas/Canvas.tsx
@@ -0,0 +1,595 @@
+import { useRef, useEffect, useState } from 'react';
+import { Stage, Layer, Transformer, Rect } from 'react-konva';
+import { CanvasObject, CanvasSize, CanvasBgColor, TextObject } from '../../types/canvas.types';
+import { getCanvasDimensions, sortByZIndex } from '../../utils/canvas.utils';
+import CanvasObjectRenderer from './CanvasObject';
+import Konva from 'konva';
+
+interface CanvasProps {
+ canvasSize: CanvasSize;
+ bgColor: CanvasBgColor;
+ objects: CanvasObject[];
+ selectedIds: string[];
+ onSelect: (ids: string[]) => void;
+ onObjectsChange: (objects: CanvasObject[]) => void;
+ textCreationMode?: boolean;
+ onTextCreate?: (x: number, y: number) => void;
+}
+
+// Helper function to calculate cursor position from click coordinates
+function calculateCursorPosition(text: string, clickX: number, fontSize: number, fontFamily: string, bold: boolean, italic: boolean): number {
+ if (!text || clickX <= 0) return 0;
+
+ try {
+ const tempText = new Konva.Text({
+ text: text,
+ fontSize: fontSize,
+ fontFamily: fontFamily,
+ fontStyle: `${bold ? 'bold' : 'normal'} ${italic ? 'italic' : ''}`
+ });
+
+ // Find the closest character position
+ let closestPos = 0;
+ let closestDist = Infinity;
+
+ for (let i = 0; i <= text.length; i++) {
+ const substr = text.substring(0, i);
+ tempText.text(substr);
+ const width = tempText.width();
+ const dist = Math.abs(width - clickX);
+
+ if (dist < closestDist) {
+ closestDist = dist;
+ closestPos = i;
+ }
+ }
+
+ tempText.destroy();
+ return closestPos;
+ } catch (error) {
+ console.error('Error calculating cursor position:', error);
+ return text.length; // Default to end
+ }
+}
+
+export default function Canvas({
+ canvasSize,
+ bgColor,
+ objects,
+ selectedIds,
+ onSelect,
+ onObjectsChange,
+ textCreationMode = false,
+ onTextCreate
+}: CanvasProps) {
+ const stageRef = useRef(null);
+ const transformerRef = useRef(null);
+ const shapeRefs = useRef