218 lines
11 KiB
Vue
218 lines
11 KiB
Vue
<template>
|
|
<main class="flex flex-col h-full min-h-0 relative">
|
|
<!-- Welcome / Empty State -->
|
|
<div class="flex-1 flex flex-col items-center justify-center pb-12">
|
|
<div class="w-full flex flex-col gap-8 lg:gap-12 items-start">
|
|
<!-- Top Row: Upload Field & Video Side by Side -->
|
|
<div class="w-full grid grid-cols-1 lg:grid-cols-2 gap-6 lg:gap-8">
|
|
<!-- File Uploader Component -->
|
|
<div class="glass-panel p-2 rounded-2xl shadow-xl shadow-indigo-500/10 border border-indigo-50/50 dark:border-gray-700 h-full flex flex-col">
|
|
<file-uploader @upload-sprites="handleSpritesUpload" />
|
|
</div>
|
|
|
|
<!-- Video Showcase -->
|
|
<div class="rounded-2xl overflow-hidden shadow-2xl border border-gray-200 dark:border-gray-800 bg-gray-900 relative group h-full min-h-[300px] lg:min-h-0">
|
|
<video autoplay controls loop muted playsinline class="w-full h-full object-cover opacity-90 group-hover:opacity-100 transition-opacity duration-500">
|
|
<source :src="tutVideo" type="video/mp4" />
|
|
</video>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Section: Features -->
|
|
<div class="w-full grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
|
|
<!-- Hero Text -->
|
|
<div class="text-left">
|
|
<h1 class="text-4xl sm:text-5xl md:text-6xl font-extrabold tracking-tight mb-6 text-gray-900 dark:text-gray-50 leading-[1.1]">Welcome to <span class="text-indigo-600 dark:text-indigo-400">Spritesheet generator</span></h1>
|
|
<p class="text-lg text-gray-600 dark:text-gray-300 leading-relaxed">
|
|
Create spritesheets for your game development and animation projects with our completely free, open-source Spritesheet generator. This powerful online tool lets you upload individual sprite images and automatically arrange them into optimized sprite sheets with customizable layouts -
|
|
perfect for indie developers, animators, and studios of any size.
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Key Features Grid -->
|
|
<div class="space-y-6">
|
|
<h2 class="text-xl font-bold text-gray-900 dark:text-gray-100 flex items-center gap-2"><i class="fas fa-star text-yellow-500 text-sm"></i> Key features</h2>
|
|
|
|
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4 text-left">
|
|
<!-- Features List -->
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-edit text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Free sprite editor</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Edit, organize, and optimize directly in browser.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-magic text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Automatic generation</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Convert images into efficient sprite atlases.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-th text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Customizable layouts</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Adjust spacing for pixel-perfect results.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-play-circle text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Animation preview</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Test animations before exporting.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-gamepad text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Cross-platform</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Works with Unity, Godot, etc.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-globe text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">No installation</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Use our web-based tool instantly.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-layer-group text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Batch processing</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Upload/process multiple sprites.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card p-4 hover:border-indigo-500/30 transition-colors flex gap-4 items-start">
|
|
<div class="w-8 h-8 rounded-lg bg-indigo-100 dark:bg-indigo-900/30 flex items-center justify-center text-indigo-600 dark:text-indigo-400 shrink-0">
|
|
<i class="fas fa-file-export text-sm"></i>
|
|
</div>
|
|
<div>
|
|
<h3 class="font-bold text-sm text-gray-900 dark:text-gray-100 mb-0.5">Export options</h3>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 leading-snug">Download PNG, JSON, ZIP, or GIF.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modals -->
|
|
<SpritesheetSplitter :is-open="isSpritesheetSplitterOpen" :image-url="spritesheetImageUrl" :image-file="spritesheetImageFile" @close="closeSpritesheetSplitter" @split="handleSplitSpritesheet" />
|
|
</main>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref, toRef } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import FileUploader from '@/components/FileUploader.vue';
|
|
import SpritesheetSplitter from '@/components/SpritesheetSplitter.vue';
|
|
import { useHomeViewSEO } from './HomeView.seo';
|
|
import { useLayers } from '@/composables/useLayers';
|
|
import { useSettingsStore } from '@/stores/useSettingsStore';
|
|
import { useExportLayers } from '@/composables/useExportLayers';
|
|
import tutVideo from '@/assets/tut2.mp4';
|
|
import type { SpriteFile } from '@/types/sprites';
|
|
|
|
useHomeViewSEO();
|
|
const router = useRouter();
|
|
const settingsStore = useSettingsStore();
|
|
const { layers, columns, processImageFiles, activeLayerId } = useLayers();
|
|
|
|
const { importSpritesheetJSON } = useExportLayers(
|
|
layers,
|
|
columns,
|
|
toRef(settingsStore, 'negativeSpacingEnabled'),
|
|
activeLayerId,
|
|
toRef(settingsStore, 'backgroundColor'),
|
|
toRef(settingsStore, 'manualCellSizeEnabled'),
|
|
toRef(settingsStore, 'manualCellWidth'),
|
|
toRef(settingsStore, 'manualCellHeight')
|
|
);
|
|
|
|
const isSpritesheetSplitterOpen = ref(false);
|
|
const spritesheetImageUrl = ref('');
|
|
const spritesheetImageFile = ref<File | null>(null);
|
|
|
|
const closeSpritesheetSplitter = () => {
|
|
isSpritesheetSplitterOpen.value = false;
|
|
if (spritesheetImageUrl.value && spritesheetImageUrl.value.startsWith('blob:')) {
|
|
try {
|
|
URL.revokeObjectURL(spritesheetImageUrl.value);
|
|
} catch {}
|
|
}
|
|
spritesheetImageUrl.value = '';
|
|
spritesheetImageFile.value = null;
|
|
};
|
|
|
|
const handleSplitSpritesheet = (spriteFiles: SpriteFile[]) => {
|
|
processImageFiles(spriteFiles.map(s => s.file));
|
|
router.push('/editor');
|
|
};
|
|
|
|
const handleSpritesUpload = async (files: File[]) => {
|
|
const jsonFile = files.find(file => file.type === 'application/json' || file.name.endsWith('.json'));
|
|
|
|
if (jsonFile) {
|
|
try {
|
|
await importSpritesheetJSON(jsonFile);
|
|
router.push('/editor');
|
|
} catch (e) {
|
|
console.error(e);
|
|
alert('Failed to import JSON file');
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (files.length === 1 && files[0].type.startsWith('image/')) {
|
|
const file = files[0];
|
|
const reader = new FileReader();
|
|
reader.onload = e => {
|
|
const url = e.target?.result as string;
|
|
const img = new Image();
|
|
img.onload = () => {
|
|
if (confirm('This looks like it might be a spritesheet. Would you like to split it into individual sprites?')) {
|
|
spritesheetImageUrl.value = url;
|
|
spritesheetImageFile.value = file;
|
|
isSpritesheetSplitterOpen.value = true;
|
|
return;
|
|
}
|
|
processImageFiles([file]);
|
|
router.push('/editor');
|
|
};
|
|
img.src = url;
|
|
};
|
|
reader.readAsDataURL(file);
|
|
return;
|
|
}
|
|
|
|
processImageFiles(files);
|
|
router.push('/editor');
|
|
};
|
|
</script>
|
|
|
|
<style scoped></style>
|