[FEAT] Improved UI

This commit is contained in:
2025-12-17 22:07:26 +01:00
parent b5e105b127
commit 784c95555f
3 changed files with 218 additions and 98 deletions

View File

@@ -29,102 +29,132 @@
</div> </div>
</Teleport> </Teleport>
<div class="space-y-6 w-full max-w-full overflow-hidden"> <div class="space-y-3 w-full max-w-full overflow-hidden">
<div class="bg-cyan-50 dark:bg-cyan-900/20 rounded-lg p-3 border border-cyan-100 dark:border-cyan-800/50 flex items-start gap-3"> <!-- Compact tip banner -->
<i class="fas fa-info-circle text-cyan-600 dark:text-cyan-400 mt-0.5 flex-shrink-0"></i> <div class="bg-cyan-50/80 dark:bg-cyan-900/20 rounded-md px-3 py-2 border border-cyan-100 dark:border-cyan-800/50 flex items-center gap-2">
<p class="text-sm text-cyan-800 dark:text-cyan-200"><span class="font-semibold">Tip:</span> Right-click any sprite to open the context menu for quick actions: add, replace, or remove sprites.</p> <i class="fas fa-lightbulb text-xs text-cyan-500 dark:text-cyan-400"></i>
<p class="text-xs text-cyan-700 dark:text-cyan-300"><span class="font-medium">Tip:</span> Right-click sprites for quick actions Hold Ctrl/Cmd to multi-select Delete key removes selection</p>
</div> </div>
<section class="w-full bg-white dark:bg-gray-800 rounded-xl border border-gray-200 dark:border-gray-700 p-1 shadow-sm"> <!-- Compact Toolbar -->
<div class="flex flex-wrap items-center gap-1"> <section class="w-full bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
<!-- Toggles Group --> <div class="flex flex-wrap items-center gap-0.5 px-1.5 py-1">
<div class="flex items-center gap-1 p-1"> <!-- Selection Tools -->
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Toggle Multi-Select Mode"> <div class="flex items-center">
<input id="multi-select-mode" type="checkbox" v-model="isMultiSelectMode" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" /> <Tooltip text="Select multiple sprites at once. Also works with Ctrl/Cmd+Click.">
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Multi-Select</span> <button
</label> @click="isMultiSelectMode = !isMultiSelectMode"
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Show Selection Borders"> :class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', isMultiSelectMode ? 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
<input id="show-active-border" type="checkbox" v-model="showActiveBorder" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Borders</span>
</label>
<div class="w-px h-4 bg-gray-200 dark:bg-gray-700 mx-1"></div>
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Pixel Perfect">
<input id="pixel-perfect" type="checkbox" v-model="settingsStore.pixelPerfect" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Pixel Perfect</span>
</label>
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Cell Swapping">
<input id="allow-cell-swap" type="checkbox" v-model="allowCellSwap" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Swap</span>
</label>
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Compare Sprites">
<input id="show-all-sprites" type="checkbox" v-model="showAllSprites" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Compare</span>
</label>
</div>
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div>
<!-- Spacing & Grid Group -->
<div class="flex items-center gap-1 p-1">
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Negative Spacing">
<input id="negative-spacing" type="checkbox" v-model="settingsStore.negativeSpacingEnabled" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Spacing</span>
</label>
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors" title="Checkerboard Background">
<input id="checkerboard" type="checkbox" v-model="settingsStore.checkerboardEnabled" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Grid</span>
</label>
</div>
<div class="w-px h-6 bg-gray-200 dark:bg-gray-700 mx-1"></div>
<!-- Background Color -->
<div class="flex items-center gap-2 px-3 py-2">
<label for="bg-color" class="text-sm font-medium text-gray-600 dark:text-gray-400">Bg:</label>
<div class="flex items-center gap-2">
<select
id="bg-color"
v-model="bgSelectValue"
class="px-2 py-1 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 dark:text-gray-200 text-sm focus:ring-2 focus:ring-blue-500 outline-none transition-all cursor-pointer hover:border-gray-400 dark:hover:border-gray-500"
> >
<option value="transparent">Transparent</option> <i class="fas fa-object-group mr-1"></i>Multi
</button>
</Tooltip>
<Tooltip text="Show blue borders around selected sprites for visibility.">
<button
@click="showActiveBorder = !showActiveBorder"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', showActiveBorder ? 'bg-blue-100 dark:bg-blue-900/40 text-blue-700 dark:text-blue-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-vector-square mr-1"></i>Borders
</button>
</Tooltip>
</div>
<div class="w-px h-4 bg-gray-200 dark:bg-gray-600 mx-1"></div>
<!-- Display Options -->
<div class="flex items-center">
<Tooltip text="Disable anti-aliasing for crisp pixel art rendering.">
<button
@click="settingsStore.pixelPerfect = !settingsStore.pixelPerfect"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', settingsStore.pixelPerfect ? 'bg-green-100 dark:bg-green-900/40 text-green-700 dark:text-green-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-th mr-1"></i>Pixel
</button>
</Tooltip>
<Tooltip text="Drag sprites between cells to swap their positions.">
<button
@click="allowCellSwap = !allowCellSwap"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', allowCellSwap ? 'bg-purple-100 dark:bg-purple-900/40 text-purple-700 dark:text-purple-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-exchange-alt mr-1"></i>Swap
</button>
</Tooltip>
<Tooltip text="Show ghost overlays of all sprites for alignment comparison.">
<button
@click="showAllSprites = !showAllSprites"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', showAllSprites ? 'bg-amber-100 dark:bg-amber-900/40 text-amber-700 dark:text-amber-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-clone mr-1"></i>Compare
</button>
</Tooltip>
</div>
<div class="w-px h-4 bg-gray-200 dark:bg-gray-600 mx-1"></div>
<!-- Canvas Options -->
<div class="flex items-center">
<Tooltip text="Add padding around sprites to prevent bleeding artifacts.">
<button
@click="settingsStore.negativeSpacingEnabled = !settingsStore.negativeSpacingEnabled"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', settingsStore.negativeSpacingEnabled ? 'bg-teal-100 dark:bg-teal-900/40 text-teal-700 dark:text-teal-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-compress-alt mr-1"></i>Spacing
</button>
</Tooltip>
<Tooltip text="Show checkerboard pattern to visualize transparent areas.">
<button
@click="settingsStore.checkerboardEnabled = !settingsStore.checkerboardEnabled"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', settingsStore.checkerboardEnabled ? 'bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-chess-board mr-1"></i>Grid
</button>
</Tooltip>
<Tooltip text="Display X,Y offset coordinates on each sprite.">
<button
@click="showOffsetLabels = !showOffsetLabels"
:class="['px-2 py-1 rounded text-xs font-medium transition-all cursor-pointer', showOffsetLabels ? 'bg-cyan-100 dark:bg-cyan-900/40 text-cyan-700 dark:text-cyan-300' : 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700']"
>
<i class="fas fa-tag mr-1"></i>Labels
</button>
</Tooltip>
</div>
<div class="w-px h-4 bg-gray-200 dark:bg-gray-600 mx-1"></div>
<!-- Background Color (Compact) -->
<div class="flex items-center gap-1.5 px-1">
<span class="text-[10px] font-medium text-gray-400 uppercase">Bg</span>
<select v-model="bgSelectValue" class="px-1.5 py-0.5 text-xs border border-gray-200 dark:border-gray-600 rounded bg-gray-50 dark:bg-gray-700 dark:text-gray-200 outline-none cursor-pointer hover:border-gray-300">
<option value="transparent">None</option>
<option value="#ffffff">White</option> <option value="#ffffff">White</option>
<option value="#000000">Black</option> <option value="#000000">Black</option>
<option value="#f9fafb">Light Gray</option> <option value="#f9fafb">Gray</option>
<option value="custom">Custom</option> <option value="custom">Pick</option>
</select> </select>
<div v-if="bgSelectValue === 'custom'" class="relative w-6 h-6 rounded-full overflow-hidden border border-gray-300 dark:border-gray-600 shadow-sm"> <div v-if="bgSelectValue === 'custom'" class="relative w-5 h-5 rounded overflow-hidden border border-gray-300 dark:border-gray-600">
<input type="color" v-model="customColor" @input="settingsStore.setBackgroundColor(customColor)" class="absolute -top-2 -left-2 w-10 h-10 cursor-pointer p-0 border-0" /> <input type="color" v-model="customColor" @input="settingsStore.setBackgroundColor(customColor)" class="absolute -top-1 -left-1 w-8 h-8 cursor-pointer p-0 border-0" />
</div>
</div> </div>
</div> </div>
<div class="flex-1"></div> <div class="flex-1"></div>
<!-- Zoom Controls --> <!-- Zoom Controls (Compact) -->
<div class="flex items-center bg-gray-100 dark:bg-gray-700 rounded-lg p-1 mr-1"> <div class="flex items-center bg-gray-100 dark:bg-gray-700 rounded px-1 py-0.5">
<button @click="zoomOut" class="p-1.5 hover:bg-white dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 rounded-md transition-all" title="Zoom Out"> <button @click="zoomOut" class="p-1 hover:bg-white dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 rounded transition-all cursor-pointer" title="Zoom Out">
<i class="fas fa-minus text-xs"></i> <i class="fas fa-search-minus text-[10px]"></i>
</button> </button>
<span class="px-2 text-xs font-mono text-gray-600 dark:text-gray-300 min-w-[3ch] text-center">{{ Math.round(zoom * 100) }}%</span> <span class="px-1.5 text-[10px] font-mono text-gray-500 dark:text-gray-400 min-w-[4ch] text-center">{{ Math.round(zoom * 100) }}%</span>
<button @click="zoomIn" class="p-1.5 hover:bg-white dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 rounded-md transition-all" title="Zoom In"> <button @click="zoomIn" class="p-1 hover:bg-white dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 rounded transition-all cursor-pointer" title="Zoom In">
<i class="fas fa-plus text-xs"></i> <i class="fas fa-search-plus text-[10px]"></i>
</button> </button>
<div class="w-px h-4 bg-gray-300 dark:bg-gray-600 mx-1"></div> <button @click="resetZoom" class="p-1 hover:bg-white dark:hover:bg-gray-600 text-gray-500 dark:text-gray-400 rounded transition-all cursor-pointer ml-0.5" title="Reset Zoom">
<button @click="resetZoom" class="p-1.5 hover:bg-white dark:hover:bg-gray-600 text-gray-600 dark:text-gray-300 rounded-md transition-all" title="Reset Zoom"> <i class="fas fa-compress-arrows-alt text-[10px]"></i>
<i class="fas fa-expand text-xs"></i>
</button> </button>
</div> </div>
<!-- Offset Labels Toggle -->
<label class="flex items-center gap-2 px-3 py-2 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer transition-colors border-l border-gray-100 dark:border-gray-700" title="Show Offset Labels">
<input id="show-offset-labels" type="checkbox" v-model="showOffsetLabels" class="w-4 h-4 rounded text-blue-600 focus:ring-blue-500 dark:bg-gray-700 dark:border-gray-600" />
<span class="text-sm font-medium text-gray-700 dark:text-gray-200">Labels</span>
</label>
</div> </div>
</section> </section>
<div class="relative bg-white dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 rounded-2xl shadow-lg overflow-auto max-h-[calc(100vh-400px)] min-h-[500px] w-full"> <div class="relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-xl shadow-sm overflow-auto max-h-[calc(100vh-340px)] min-h-[400px] w-full">
<div class="canvas-container touch-manipulation relative inline-block min-w-full"> <div class="canvas-container touch-manipulation relative inline-block min-w-full">
<div <div
ref="gridContainerRef" ref="gridContainerRef"
@@ -267,6 +297,7 @@
import { useZoom } from '@/composables/useZoom'; import { useZoom } from '@/composables/useZoom';
import { useDragSprite } from '@/composables/useDragSprite'; import { useDragSprite } from '@/composables/useDragSprite';
import { useFileDrop } from '@/composables/useFileDrop'; import { useFileDrop } from '@/composables/useFileDrop';
import Tooltip from '@/components/utilities/Tooltip.vue';
import type { Layer } from '@/types/sprites'; import type { Layer } from '@/types/sprites';

View File

@@ -0,0 +1,79 @@
<template>
<div class="inline-block" @mouseenter="handleMouseEnter" @mouseleave="handleMouseLeave" @mousemove="handleMouseMove">
<slot />
<Teleport to="body">
<div v-if="show && text" ref="tooltipRef" class="fixed px-2.5 py-1.5 text-xs font-medium text-white bg-gray-900 dark:bg-gray-700 rounded-md shadow-lg whitespace-nowrap pointer-events-none" :style="tooltipStyle">
{{ text }}
</div>
</Teleport>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
defineProps<{
text: string;
}>();
const show = ref(false);
const mouseX = ref(0);
const mouseY = ref(0);
const tooltipRef = ref<HTMLElement | null>(null);
const handleMouseEnter = () => {
show.value = true;
};
const handleMouseLeave = () => {
show.value = false;
};
const handleMouseMove = (e: MouseEvent) => {
mouseX.value = e.clientX;
mouseY.value = e.clientY;
};
const tooltipStyle = computed(() => {
const offsetX = 12;
const offsetY = 16;
const padding = 8;
let x = mouseX.value + offsetX;
let y = mouseY.value + offsetY;
// Get tooltip dimensions (estimate if not mounted yet)
const tooltipWidth = tooltipRef.value?.offsetWidth || 200;
const tooltipHeight = tooltipRef.value?.offsetHeight || 30;
// Screen boundaries
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
// Adjust horizontal position if too close to right edge
if (x + tooltipWidth + padding > screenWidth) {
x = mouseX.value - tooltipWidth - offsetX;
}
// Adjust horizontal position if too close to left edge
if (x < padding) {
x = padding;
}
// Adjust vertical position if too close to bottom edge
if (y + tooltipHeight + padding > screenHeight) {
y = mouseY.value - tooltipHeight - offsetY;
}
// Adjust vertical position if too close to top edge
if (y < padding) {
y = padding;
}
return {
left: `${x}px`,
top: `${y}px`,
zIndex: 99999,
};
});
</script>

View File

@@ -90,7 +90,7 @@
Layers Layers
</h3> </h3>
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div> <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div>
<button @click="addLayer()" class="text-xs px-2 py-0.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded text-gray-600 dark:text-gray-300 transition-colors" title="Add new layer"> <button @click="addLayer()" class="text-xs px-2 py-0.5 bg-gray-200 dark:bg-gray-700 hover:bg-gray-300 dark:hover:bg-gray-600 rounded text-gray-600 dark:text-gray-300 transition-colors cursor-pointer" title="Add new layer">
<i class="fas fa-plus text-[9px]"></i> <i class="fas fa-plus text-[9px]"></i>
</button> </button>
</div> </div>
@@ -101,7 +101,7 @@
class="flex items-center gap-1.5 px-2 py-1.5 bg-white dark:bg-gray-800 border rounded-md transition-all text-sm" class="flex items-center gap-1.5 px-2 py-1.5 bg-white dark:bg-gray-800 border rounded-md transition-all text-sm"
:class="[layer.id === activeLayerId ? 'border-gray-700 ring-1 ring-gray-700 dark:border-gray-400 dark:ring-gray-400' : 'border-gray-200 dark:border-gray-700', !layer.visible ? 'opacity-40' : '']" :class="[layer.id === activeLayerId ? 'border-gray-700 ring-1 ring-gray-700 dark:border-gray-400 dark:ring-gray-400' : 'border-gray-200 dark:border-gray-700', !layer.visible ? 'opacity-40' : '']"
> >
<button @click.stop="layer.visible = !layer.visible" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" :title="layer.visible ? 'Hide' : 'Show'"> <button @click.stop="layer.visible = !layer.visible" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer" :title="layer.visible ? 'Hide' : 'Show'">
<i :class="[layer.visible ? 'fas fa-eye text-gray-600 dark:text-gray-300' : 'fas fa-eye-slash text-gray-400', 'text-xs']"></i> <i :class="[layer.visible ? 'fas fa-eye text-gray-600 dark:text-gray-300' : 'fas fa-eye-slash text-gray-400', 'text-xs']"></i>
</button> </button>
<input <input
@@ -119,16 +119,16 @@
{{ layer.name }}<span v-if="layer.sprites.length" class="ml-1 opacity-50">({{ layer.sprites.length }})</span> {{ layer.name }}<span v-if="layer.sprites.length" class="ml-1 opacity-50">({{ layer.sprites.length }})</span>
</button> </button>
<div class="flex items-center gap-0.5" v-if="editingLayerId !== layer.id"> <div class="flex items-center gap-0.5" v-if="editingLayerId !== layer.id">
<button @click="startEditingLayer(layer.id, layer.name)" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Rename"> <button @click="startEditingLayer(layer.id, layer.name)" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Rename">
<i class="fas fa-pen text-[9px] text-gray-400"></i> <i class="fas fa-pen text-[9px] text-gray-400"></i>
</button> </button>
<button @click="moveLayer(layer.id, 'up')" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Move up"> <button @click="moveLayer(layer.id, 'up')" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Move up">
<i class="fas fa-chevron-up text-[9px] text-gray-400"></i> <i class="fas fa-chevron-up text-[9px] text-gray-400"></i>
</button> </button>
<button @click="moveLayer(layer.id, 'down')" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors" title="Move down"> <button @click="moveLayer(layer.id, 'down')" class="p-1 rounded hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Move down">
<i class="fas fa-chevron-down text-[9px] text-gray-400"></i> <i class="fas fa-chevron-down text-[9px] text-gray-400"></i>
</button> </button>
<button v-if="layers.length > 1" @click="removeLayer(layer.id)" class="p-1 rounded hover:bg-red-50 dark:hover:bg-red-900/30 transition-colors" title="Delete"> <button v-if="layers.length > 1" @click="removeLayer(layer.id)" class="p-1 rounded hover:bg-red-50 dark:hover:bg-red-900/30 transition-colors cursor-pointer" title="Delete">
<i class="fas fa-trash text-[9px] text-red-400"></i> <i class="fas fa-trash text-[9px] text-red-400"></i>
</button> </button>
</div> </div>
@@ -177,22 +177,22 @@
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div> <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div>
</div> </div>
<div class="grid grid-cols-6 gap-1"> <div class="grid grid-cols-6 gap-1">
<button @click="alignSprites('left')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Align Left"> <button @click="alignSprites('left')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Align Left">
<i class="fas fa-arrow-left text-xs text-gray-500"></i> <i class="fas fa-arrow-left text-xs text-gray-500"></i>
</button> </button>
<button @click="alignSprites('center')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Center Horizontally"> <button @click="alignSprites('center')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Center Horizontally">
<i class="fas fa-arrows-left-right text-xs text-gray-500"></i> <i class="fas fa-arrows-left-right text-xs text-gray-500"></i>
</button> </button>
<button @click="alignSprites('right')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Align Right"> <button @click="alignSprites('right')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Align Right">
<i class="fas fa-arrow-right text-xs text-gray-500"></i> <i class="fas fa-arrow-right text-xs text-gray-500"></i>
</button> </button>
<button @click="alignSprites('top')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Align Top"> <button @click="alignSprites('top')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Align Top">
<i class="fas fa-arrow-up text-xs text-gray-500"></i> <i class="fas fa-arrow-up text-xs text-gray-500"></i>
</button> </button>
<button @click="alignSprites('middle')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Center Vertically"> <button @click="alignSprites('middle')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Center Vertically">
<i class="fas fa-arrows-up-down text-xs text-gray-500"></i> <i class="fas fa-arrows-up-down text-xs text-gray-500"></i>
</button> </button>
<button @click="alignSprites('bottom')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors" title="Align Bottom"> <button @click="alignSprites('bottom')" class="p-2 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors cursor-pointer" title="Align Bottom">
<i class="fas fa-arrow-down text-xs text-gray-500"></i> <i class="fas fa-arrow-down text-xs text-gray-500"></i>
</button> </button>
</div> </div>
@@ -208,26 +208,36 @@
<div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div> <div class="flex-1 h-px bg-gray-200 dark:bg-gray-700"></div>
</div> </div>
<div class="grid grid-cols-4 gap-1.5 mb-2"> <div class="grid grid-cols-4 gap-1.5 mb-2">
<button @click="downloadSpritesheet" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors" data-rybbit-event="download-spritesheet" title="Download as PNG image"> <button
@click="downloadSpritesheet"
class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors cursor-pointer"
data-rybbit-event="download-spritesheet"
title="Download as PNG image"
>
<i class="fas fa-image text-sm mb-0.5"></i> <i class="fas fa-image text-sm mb-0.5"></i>
<span class="text-[10px] font-medium">PNG</span> <span class="text-[10px] font-medium">PNG</span>
</button> </button>
<button @click="exportSpritesheetJSON" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors" data-rybbit-event="export-json" title="Export project data as JSON"> <button
@click="exportSpritesheetJSON"
class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors cursor-pointer"
data-rybbit-event="export-json"
title="Export project data as JSON"
>
<i class="fas fa-file-code text-sm mb-0.5"></i> <i class="fas fa-file-code text-sm mb-0.5"></i>
<span class="text-[10px] font-medium">JSON</span> <span class="text-[10px] font-medium">JSON</span>
</button> </button>
<button @click="openGifFpsModal" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors" data-rybbit-event="download-gif" title="Export as animated GIF"> <button @click="openGifFpsModal" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors cursor-pointer" data-rybbit-event="download-gif" title="Export as animated GIF">
<i class="fas fa-film text-sm mb-0.5"></i> <i class="fas fa-film text-sm mb-0.5"></i>
<span class="text-[10px] font-medium">GIF</span> <span class="text-[10px] font-medium">GIF</span>
</button> </button>
<button @click="downloadAsZip" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors" data-rybbit-event="download-zip" title="Download all sprites as ZIP"> <button @click="downloadAsZip" class="flex flex-col items-center justify-center p-2.5 bg-gray-700 hover:bg-gray-800 dark:bg-gray-600 dark:hover:bg-gray-500 rounded-lg text-white transition-colors cursor-pointer" data-rybbit-event="download-zip" title="Download all sprites as ZIP">
<i class="fas fa-file-archive text-sm mb-0.5"></i> <i class="fas fa-file-archive text-sm mb-0.5"></i>
<span class="text-[10px] font-medium">ZIP</span> <span class="text-[10px] font-medium">ZIP</span>
</button> </button>
</div> </div>
<button <button
@click="openShareModal" @click="openShareModal"
class="w-full flex items-center justify-center gap-2 p-2.5 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 rounded-lg text-white text-sm font-medium transition-all shadow-sm hover:shadow-md" class="w-full flex items-center justify-center gap-2 p-2.5 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 rounded-lg text-white text-sm font-medium transition-all shadow-sm hover:shadow-md cursor-pointer"
data-rybbit-event="share-spritesheet" data-rybbit-event="share-spritesheet"
title="Generate shareable link" title="Generate shareable link"
> >