|
|
|
|
@@ -1,137 +1,157 @@
|
|
|
|
|
<template>
|
|
|
|
|
<div class="spritesheet-preview w-full">
|
|
|
|
|
<!-- Controls Container -->
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4">
|
|
|
|
|
<div class="flex flex-col gap-4 sm:flex-row sm:flex-wrap">
|
|
|
|
|
<!-- Playback Controls -->
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<button @click="togglePlayback" class="flex items-center gap-1.5 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md transition-colors w-full cursor-pointer">
|
|
|
|
|
<span v-if="isPlaying" class="flex items-center gap-1.5">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
|
|
|
|
<path fill-rule="evenodd" d="M6.75 5.25a.75.75 0 01.75-.75H9a.75.75 0 01.75.75v13.5a.75.75 0 01-.75.75H7.5a.75.75 0 01-.75-.75V5.25zm7.5 0A.75.75 0 0115 4.5h1.5a.75.75 0 01.75.75v13.5a.75.75 0 01-.75.75H15a.75.75 0 01-.75-.75V5.25z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="hidden sm:inline">Pause</span>
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else class="flex items-center gap-1.5">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
|
|
|
|
<path fill-rule="evenodd" d="M4.5 5.653c0-1.426 1.529-2.33 2.779-1.643l11.54 6.348c1.295.712 1.295 2.573 0 3.285L7.28 19.991c-1.25.687-2.779-.217-2.779-1.643V5.653z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
<span class="hidden sm:inline">Play</span>
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
<!-- Main Layout: Canvas Left, Controls Right -->
|
|
|
|
|
<div class="flex flex-col lg:flex-row gap-4">
|
|
|
|
|
<!-- Canvas Area (Left/Main) -->
|
|
|
|
|
<div class="flex-1 min-w-0">
|
|
|
|
|
<div class="relative bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg overflow-auto min-h-[300px] sm:min-h-[520px] shadow-sm hover:shadow-md transition-shadow duration-200">
|
|
|
|
|
<canvas
|
|
|
|
|
ref="previewCanvasRef"
|
|
|
|
|
@mousedown="startDrag"
|
|
|
|
|
@mousemove="drag"
|
|
|
|
|
@mouseup="stopDrag"
|
|
|
|
|
@mouseleave="stopDrag"
|
|
|
|
|
@touchstart="handleTouchStart"
|
|
|
|
|
@touchmove="handleTouchMove"
|
|
|
|
|
@touchend="stopDrag"
|
|
|
|
|
class="block touch-manipulation"
|
|
|
|
|
:class="{ 'cursor-move': isDraggable }"
|
|
|
|
|
:style="{ transform: `scale(${zoom})`, transformOrigin: 'top left', ...(settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}) }"
|
|
|
|
|
>
|
|
|
|
|
</canvas>
|
|
|
|
|
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
|
|
|
<button @click="previousFrame" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 p-2 rounded-md transition-colors duration-200 w-full cursor-pointer" :disabled="isPlaying" :class="{ 'opacity-50 cursor-not-allowed': isPlaying }">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 mx-auto dark:text-gray-200">
|
|
|
|
|
<!-- Mobile zoom controls -->
|
|
|
|
|
<div class="absolute bottom-3 right-3 flex space-x-2 lg:hidden bg-white/80 dark:bg-gray-800/80 p-2 rounded-lg shadow-md">
|
|
|
|
|
<button @click="increaseZoom" class="p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors">
|
|
|
|
|
<i class="fas fa-plus"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="decreaseZoom" class="p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors">
|
|
|
|
|
<i class="fas fa-minus"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Controls Sidebar (Right) -->
|
|
|
|
|
<div class="lg:w-80 xl:w-96 flex-shrink-0">
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 p-4 space-y-4">
|
|
|
|
|
<!-- Playback Controls -->
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Playback</h3>
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<button @click="togglePlayback" class="flex items-center justify-center gap-1.5 bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md transition-colors cursor-pointer flex-1">
|
|
|
|
|
<span v-if="isPlaying" class="flex items-center gap-1.5">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
|
|
|
|
<path fill-rule="evenodd" d="M6.75 5.25a.75.75 0 01.75-.75H9a.75.75 0 01.75.75v13.5a.75.75 0 01-.75.75H7.5a.75.75 0 01-.75-.75V5.25zm7.5 0A.75.75 0 0115 4.5h1.5a.75.75 0 01.75.75v13.5a.75.75 0 01-.75.75H15a.75.75 0 01-.75-.75V5.25z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
Pause
|
|
|
|
|
</span>
|
|
|
|
|
<span v-else class="flex items-center gap-1.5">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5">
|
|
|
|
|
<path fill-rule="evenodd" d="M4.5 5.653c0-1.426 1.529-2.33 2.779-1.643l11.54 6.348c1.295.712 1.295 2.573 0 3.285L7.28 19.991c-1.25.687-2.779-.217-2.779-1.643V5.653z" clip-rule="evenodd" />
|
|
|
|
|
</svg>
|
|
|
|
|
Play
|
|
|
|
|
</span>
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="previousFrame" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 p-2 rounded-md transition-colors duration-200 cursor-pointer" :disabled="isPlaying" :class="{ 'opacity-50 cursor-not-allowed': isPlaying }">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 dark:text-gray-200">
|
|
|
|
|
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 01.75.75v12.06l4.72-4.72a.75.75 0 111.06 1.06l-6 6a.75.75 0 01-1.06 0l-6-6a.75.75 0 011.06-1.06l4.72 4.72V5.97a.75.75 0 01.75-.75z" clip-rule="evenodd" transform="rotate(90 12 12)" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="nextFrame" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 p-2 rounded-md transition-colors duration-200 w-full cursor-pointer" :disabled="isPlaying" :class="{ 'opacity-50 cursor-not-allowed': isPlaying }">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 mx-auto dark:text-gray-200">
|
|
|
|
|
<button @click="nextFrame" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 p-2 rounded-md transition-colors duration-200 cursor-pointer" :disabled="isPlaying" :class="{ 'opacity-50 cursor-not-allowed': isPlaying }">
|
|
|
|
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="w-5 h-5 dark:text-gray-200">
|
|
|
|
|
<path fill-rule="evenodd" d="M11.78 5.22a.75.75 0 01.75.75v12.06l4.72-4.72a.75.75 0 111.06 1.06l-6 6a.75.75 0 01-1.06 0l-6-6a.75.75 0 011.06-1.06l4.72 4.72V5.97a.75.75 0 01.75-.75z" clip-rule="evenodd" transform="rotate(-90 12 12)" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Checkboxes -->
|
|
|
|
|
<div class="flex flex-wrap gap-4 items-center">
|
|
|
|
|
<!-- Sliders -->
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Controls</h3>
|
|
|
|
|
|
|
|
|
|
<!-- Frame Navigation -->
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">Frame</span>
|
|
|
|
|
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">{{ visibleFrameNumber }}/{{ visibleFramesCount }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="range" min="0" :max="visibleFrames.length - 1" :value="visibleFrameIndex" @input="handleSliderInput" class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" :disabled="isPlaying" :class="{ 'opacity-50': isPlaying }" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- FPS Control -->
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">FPS</span>
|
|
|
|
|
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">{{ fps }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="range" min="1" max="60" v-model.number="fps" class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Zoom Control -->
|
|
|
|
|
<div class="space-y-1">
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">Zoom</span>
|
|
|
|
|
<span class="text-xs font-mono text-gray-700 dark:text-gray-300">{{ Math.round(zoom * 100) }}%</span>
|
|
|
|
|
</div>
|
|
|
|
|
<input type="range" min="0.5" max="5" step="0.1" v-model.number="zoom" class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Options -->
|
|
|
|
|
<div class="space-y-3">
|
|
|
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Options</h3>
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
|
|
|
<input type="checkbox" v-model="isDraggable" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" />
|
|
|
|
|
<span class="text-sm whitespace-nowrap dark:text-gray-200">Reposition</span>
|
|
|
|
|
<span class="text-sm dark:text-gray-200">Reposition</span>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
|
|
|
<input type="checkbox" v-model="showAllSprites" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" />
|
|
|
|
|
<span class="text-sm whitespace-nowrap dark:text-gray-200">Compare sprites</span>
|
|
|
|
|
<span class="text-sm dark:text-gray-200">Compare sprites</span>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="flex items-center gap-2 cursor-pointer">
|
|
|
|
|
<input type="checkbox" v-model="settingsStore.pixelPerfect" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" @change="drawPreviewCanvas" />
|
|
|
|
|
<span class="text-sm whitespace-nowrap dark:text-gray-200">Pixel perfect</span>
|
|
|
|
|
<span class="text-sm dark:text-gray-200">Pixel perfect</span>
|
|
|
|
|
</label>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<!-- Frame Controls -->
|
|
|
|
|
<div class="flex-1 min-w-[200px] space-y-6">
|
|
|
|
|
<!-- Frame Navigation -->
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-sm font-medium w-30 dark:text-gray-200">Frame {{ visibleFrameNumber }}/{{ visibleFramesCount }}</span>
|
|
|
|
|
<input type="range" min="0" :max="visibleFrames.length - 1" :value="visibleFrameIndex" @input="handleSliderInput" class="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" :disabled="isPlaying" :class="{ 'opacity-50': isPlaying }" />
|
|
|
|
|
|
|
|
|
|
<!-- Current frame offset display -->
|
|
|
|
|
<div v-if="props.sprites[currentFrameIndex]" class="p-2 bg-gray-100 dark:bg-gray-700 rounded-md border border-gray-200 dark:border-gray-600">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<span class="text-xs font-medium text-gray-600 dark:text-gray-400">Offset</span>
|
|
|
|
|
<span class="text-xs font-mono font-semibold text-cyan-600 dark:text-cyan-400">x: {{ props.sprites[currentFrameIndex].x }}, y: {{ props.sprites[currentFrameIndex].y }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- FPS Control -->
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-sm font-medium w-30 dark:text-gray-200">FPS: {{ fps }}</span>
|
|
|
|
|
<input type="range" min="1" max="60" v-model.number="fps" class="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" />
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Zoom Control -->
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-sm font-medium w-30 dark:text-gray-200">{{ Math.round(zoom * 100) }}%</span>
|
|
|
|
|
<input type="range" min="0.5" max="5" step="0.1" v-model.number="zoom" class="flex-1 h-2 bg-gray-200 dark:bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div v-if="showAllSprites" class="w-full mt-3">
|
|
|
|
|
<div class="flex items-center mb-2">
|
|
|
|
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-200">Select visible frames:</label>
|
|
|
|
|
<div class="ml-auto flex gap-2">
|
|
|
|
|
<button @click="showAllFrames" class="px-2 py-1 text-sm bg-blue-500 hover:bg-blue-600 text-white rounded-md transition-colors">Show All</button>
|
|
|
|
|
<button @click="hideAllFrames" class="px-2 py-1 text-sm bg-gray-500 hover:bg-gray-600 text-white rounded-md transition-colors">Hide All</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-full rounded-md border border-gray-300 dark:border-gray-600 shadow-sm focus-within:ring-1 focus-within:ring-blue-500 focus-within:border-blue-500 dark:bg-gray-800">
|
|
|
|
|
<div class="max-h-[200px] overflow-y-auto">
|
|
|
|
|
<div class="grid grid-cols-2 gap-2">
|
|
|
|
|
<div v-for="(sprite, index) in props.sprites" :key="sprite.id" class="flex items-center gap-3 px-3 py-2 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer" @click="toggleHiddenFrame(index)">
|
|
|
|
|
<input type="checkbox" :checked="!hiddenFrames.includes(index)" class="w-4 h-4 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" @click.stop @change="toggleHiddenFrame(index)" />
|
|
|
|
|
<div class="w-12 h-12 bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center overflow-hidden">
|
|
|
|
|
<img :src="sprite.img.src" class="max-w-full max-h-full object-contain" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
|
|
|
|
<!-- Frame Selection (when Compare sprites is enabled) -->
|
|
|
|
|
<div v-if="showAllSprites" class="space-y-2">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300 uppercase tracking-wide">Frames</h3>
|
|
|
|
|
<div class="flex gap-1">
|
|
|
|
|
<button @click="showAllFrames" class="px-2 py-1 text-xs bg-blue-500 hover:bg-blue-600 text-white rounded transition-colors">All</button>
|
|
|
|
|
<button @click="hideAllFrames" class="px-2 py-1 text-xs bg-gray-500 hover:bg-gray-600 text-white rounded transition-colors">None</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="rounded-md border border-gray-300 dark:border-gray-600 dark:bg-gray-800">
|
|
|
|
|
<div class="max-h-[180px] overflow-y-auto">
|
|
|
|
|
<div class="space-y-0.5 p-1">
|
|
|
|
|
<div v-for="(sprite, index) in props.sprites" :key="sprite.id" class="flex items-center gap-2 px-2 py-1.5 hover:bg-gray-50 dark:hover:bg-gray-700 cursor-pointer rounded" @click="toggleHiddenFrame(index)">
|
|
|
|
|
<input type="checkbox" :checked="!hiddenFrames.includes(index)" class="w-3.5 h-3.5 text-blue-500 focus:ring-blue-400 rounded border-gray-300 dark:border-gray-600 dark:bg-gray-700" @click.stop @change="toggleHiddenFrame(index)" />
|
|
|
|
|
<div class="w-8 h-8 bg-gray-100 dark:bg-gray-700 rounded flex items-center justify-center overflow-hidden flex-shrink-0">
|
|
|
|
|
<img :src="sprite.img.src" class="max-w-full max-h-full object-contain" :style="settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}" />
|
|
|
|
|
</div>
|
|
|
|
|
<span class="text-xs dark:text-gray-200">{{ index + 1 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="text-sm dark:text-gray-200">Frame {{ index + 1 }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- Current frame offset display -->
|
|
|
|
|
<div v-if="props.sprites[currentFrameIndex]" class="mt-3 p-2 bg-gray-100 dark:bg-gray-700 rounded-md border border-gray-200 dark:border-gray-600">
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Current Frame Offset:</span>
|
|
|
|
|
<span class="text-sm font-mono font-semibold text-cyan-600 dark:text-cyan-400">x: {{ props.sprites[currentFrameIndex].x }}, y: {{ props.sprites[currentFrameIndex].y }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="mt-3 relative bg-gray-50 dark:bg-gray-800 border border-gray-300 dark:border-gray-700 rounded-lg mb-4 sm:mb-6 overflow-auto min-h-[300px] sm:min-h-[520px] shadow-sm hover:shadow-md transition-shadow duration-200">
|
|
|
|
|
<canvas
|
|
|
|
|
ref="previewCanvasRef"
|
|
|
|
|
@mousedown="startDrag"
|
|
|
|
|
@mousemove="drag"
|
|
|
|
|
@mouseup="stopDrag"
|
|
|
|
|
@mouseleave="stopDrag"
|
|
|
|
|
@touchstart="handleTouchStart"
|
|
|
|
|
@touchmove="handleTouchMove"
|
|
|
|
|
@touchend="stopDrag"
|
|
|
|
|
class="block touch-manipulation"
|
|
|
|
|
:class="{ 'cursor-move': isDraggable }"
|
|
|
|
|
:style="{ transform: `scale(${zoom})`, transformOrigin: 'top left', ...(settingsStore.pixelPerfect ? { 'image-rendering': 'pixelated' } : {}) }"
|
|
|
|
|
>
|
|
|
|
|
</canvas>
|
|
|
|
|
|
|
|
|
|
<!-- Mobile zoom controls -->
|
|
|
|
|
<div class="absolute bottom-3 right-3 flex space-x-2 sm:hidden bg-white/80 dark:bg-gray-800/80 p-2 rounded-lg shadow-md">
|
|
|
|
|
<button @click="increaseZoom" class="p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors">
|
|
|
|
|
<i class="fas fa-plus"></i>
|
|
|
|
|
</button>
|
|
|
|
|
<button @click="decreaseZoom" class="p-2 bg-gray-100 hover:bg-gray-200 dark:bg-gray-700 dark:hover:bg-gray-600 text-gray-700 dark:text-gray-100 rounded-lg transition-colors">
|
|
|
|
|
<i class="fas fa-minus"></i>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|