Files
spritesheet-generator/src/views/HomeView.vue
2026-01-02 04:38:42 +01:00

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>