Compare commits
5 Commits
54ef9121c7
...
80787719e6
| Author | SHA1 | Date | |
|---|---|---|---|
| 80787719e6 | |||
| b801cd4c99 | |||
| accea99408 | |||
| 09c77f5414 | |||
| d38ba85f4f |
15
index.html
15
index.html
@@ -2,16 +2,24 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Preconnect to external domains for performance -->
|
||||
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://cdnjs.cloudflare.com">
|
||||
<link rel="preconnect" href="https://a.adhd.sh" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://a.adhd.sh">
|
||||
<link rel="preconnect" href="https://pagead2.googlesyndication.com" crossorigin>
|
||||
<link rel="dns-prefetch" href="https://pagead2.googlesyndication.com">
|
||||
|
||||
<link rel="icon" type="image/png" href="/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="MyWebSite" />
|
||||
<meta name="apple-mobile-web-app-title" content="Spritesheet Generator" />
|
||||
<link rel="manifest" href="/site.webmanifest" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Primary Meta Tags -->
|
||||
<title>Spritesheet generator - Create Game Spritesheets Online</title>
|
||||
@@ -20,6 +28,7 @@
|
||||
<meta name="keywords" content="Spritesheet generator, sprite sheet maker, game development, pixel art, sprite animation, game assets, 2D game tools">
|
||||
<meta name="author" content="nu11ed">
|
||||
<meta name="robots" content="index, follow">
|
||||
<link rel="canonical" href="https://spritesheetgenerator.online/">
|
||||
|
||||
<!-- Open Graph / Facebook -->
|
||||
<meta property="og:type" content="website">
|
||||
@@ -39,8 +48,6 @@
|
||||
<meta name="theme-color" content="#0096ff" media="(prefers-color-scheme: light)">
|
||||
<meta name="theme-color" content="#111827" media="(prefers-color-scheme: dark)">
|
||||
|
||||
<title>Spritesheet generator</title>
|
||||
|
||||
<script
|
||||
src="https://a.adhd.sh/api/script.js"
|
||||
data-site-id="7"
|
||||
|
||||
119
package-lock.json
generated
119
package-lock.json
generated
@@ -9,6 +9,7 @@
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.1",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"gif.js": "^0.2.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
@@ -2004,6 +2005,76 @@
|
||||
"undici-types": "~6.21.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@unhead/dom": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.20.tgz",
|
||||
"integrity": "sha512-jgfGYdOH+xHJF/j8gudjsYu3oIjFyXhCWcgKaw3vQnT616gSqyqnGQGOItL+BQtQZACKNISwIfx5PuOtztMKLA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/schema": "1.11.20",
|
||||
"@unhead/shared": "1.11.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@unhead/schema": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.20.tgz",
|
||||
"integrity": "sha512-0zWykKAaJdm+/Y7yi/Yds20PrUK7XabLe9c3IRcjnwYmSWY6z0Cr19VIs3ozCj8P+GhR+/TI2mwtGlueCEYouA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"hookable": "^5.5.3",
|
||||
"zhead": "^2.2.4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@unhead/shared": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.20.tgz",
|
||||
"integrity": "sha512-1MOrBkGgkUXS+sOKz/DBh4U20DNoITlJwpmvSInxEUNhghSNb56S0RnaHRq0iHkhrO/cDgz2zvfdlRpoPLGI3w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/schema": "1.11.20",
|
||||
"packrup": "^0.1.2"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@unhead/ssr": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/ssr/-/ssr-1.11.20.tgz",
|
||||
"integrity": "sha512-j6ehzmdWGAvv0TEZyLE3WBnG1ULnsbKQcLqBDh3fvKS6b3xutcVZB7mjvrVE7ckSZt6WwOtG0ED3NJDS7IjzBA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/schema": "1.11.20",
|
||||
"@unhead/shared": "1.11.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/@unhead/vue": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.20.tgz",
|
||||
"integrity": "sha512-sqQaLbwqY9TvLEGeq8Fd7+F2TIuV3nZ5ihVISHjWpAM3y7DwNWRU7NmT9+yYT+2/jw1Vjwdkv5/HvDnvCLrgmg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/schema": "1.11.20",
|
||||
"@unhead/shared": "1.11.20",
|
||||
"hookable": "^5.5.3",
|
||||
"unhead": "1.11.20"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=2.7 || >=3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vitejs/plugin-vue": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
|
||||
@@ -2325,6 +2396,21 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vueuse/head": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@vueuse/head/-/head-2.0.0.tgz",
|
||||
"integrity": "sha512-ykdOxTGs95xjD4WXE4na/umxZea2Itl0GWBILas+O4oqS7eXIods38INvk3XkJKjqMdWPcpCyLX/DioLQxU1KA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/dom": "^1.7.0",
|
||||
"@unhead/schema": "^1.7.0",
|
||||
"@unhead/ssr": "^1.7.0",
|
||||
"@unhead/vue": "^1.7.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vue": ">=2.7 || >=3"
|
||||
}
|
||||
},
|
||||
"node_modules/alien-signals": {
|
||||
"version": "1.0.13",
|
||||
"resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
|
||||
@@ -3852,6 +3938,15 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/packrup": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/packrup/-/packrup-0.1.2.tgz",
|
||||
"integrity": "sha512-ZcKU7zrr5GlonoS9cxxrb5HVswGnyj6jQvwFBa6p5VFw7G71VAHcUKL5wyZSU/ECtPM/9gacWxy2KFQKt1gMNA==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
@@ -4835,6 +4930,21 @@
|
||||
"devOptional": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/unhead": {
|
||||
"version": "1.11.20",
|
||||
"resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.20.tgz",
|
||||
"integrity": "sha512-3AsNQC0pjwlLqEYHLjtichGWankK8yqmocReITecmpB1H0aOabeESueyy+8X1gyJx4ftZVwo9hqQ4O3fPWffCA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@unhead/dom": "1.11.20",
|
||||
"@unhead/schema": "1.11.20",
|
||||
"@unhead/shared": "1.11.20",
|
||||
"hookable": "^5.5.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
},
|
||||
"node_modules/unicorn-magic": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
|
||||
@@ -5210,6 +5320,15 @@
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/zhead": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
|
||||
"integrity": "sha512-8F0OI5dpWIA5IGG5NHUg9staDwz/ZPxZtvGVf01j7vHqSyZ0raHY+78atOVxRqb73AotX22uV1pXt3gYSstGag==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/harlan-zw"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.1",
|
||||
"@vueuse/head": "^2.0.0",
|
||||
"buffer": "^6.0.3",
|
||||
"gif.js": "^0.2.0",
|
||||
"gray-matter": "^4.0.3",
|
||||
|
||||
BIN
public/blog/2.jpg
Normal file
BIN
public/blog/2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 110 KiB |
@@ -25,6 +25,10 @@
|
||||
<loc>https://spritesheetgenerator.online/blog</loc>
|
||||
<lastmod>2025-11-26T15:50:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://spritesheetgenerator.online/blog/why-pixel-art</loc>
|
||||
<lastmod>2025-11-26T15:50:00+00:00</lastmod>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://spritesheetgenerator.online/blog/welcome</loc>
|
||||
<lastmod>2025-11-26T15:50:00+00:00</lastmod>
|
||||
|
||||
25
src/App.vue
25
src/App.vue
@@ -4,43 +4,45 @@
|
||||
<header class="mb-6 sm:mb-5">
|
||||
<div class="flex flex-col sm:flex-row justify-between items-center gap-6 mb-8">
|
||||
<div class="text-center sm:text-left">
|
||||
<router-link to="/" class="block group">
|
||||
<router-link to="/" class="block group" title="Spritesheet Generator - Create professional spritesheets" aria-label="Go to homepage">
|
||||
<h1 class="text-3xl sm:text-5xl font-extrabold text-transparent bg-clip-text bg-gradient-to-r from-gray-900 to-gray-700 dark:from-white dark:to-gray-300 tracking-tight mb-3 group-hover:opacity-80 transition-opacity">Spritesheet generator</h1>
|
||||
</router-link>
|
||||
<p class="text-sm sm:text-base text-gray-600 dark:text-gray-400 font-medium">Create professional spritesheets for your game development projects</p>
|
||||
</div>
|
||||
<div class="flex flex-col items-center sm:items-end gap-3">
|
||||
<nav class="flex flex-wrap items-center justify-center gap-3">
|
||||
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" class="btn btn-secondary hover:shadow-md" data-rybbit-event="source-link">
|
||||
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" rel="noopener noreferrer" class="btn btn-secondary hover:shadow-md" data-rybbit-event="source-link" title="View source code on Gitea" aria-label="View source code repository">
|
||||
<i class="fab fa-github"></i>
|
||||
<span class="font-medium">Source</span>
|
||||
</a>
|
||||
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="btn btn-secondary hover:shadow-md" data-rybbit-event="discord-link">
|
||||
<a href="https://discord.gg/JTev3nzeDa" target="_blank" rel="noopener noreferrer" class="btn btn-secondary hover:shadow-md" data-rybbit-event="discord-link" title="Join our Discord community" aria-label="Join Discord server">
|
||||
<i class="fab fa-discord"></i>
|
||||
<span class="font-medium">Discord</span>
|
||||
</a>
|
||||
<a href="#" @click.prevent="openHelpModal" class="btn btn-secondary hover:shadow-md" data-rybbit-event="help-link">
|
||||
<a href="#" @click.prevent="openHelpModal" class="btn btn-secondary hover:shadow-md" data-rybbit-event="help-link" title="Get help and documentation" aria-label="Open help modal">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
<span class="font-medium">Help</span>
|
||||
</a>
|
||||
<a href="#" @click.prevent="openFeedbackModal" class="btn btn-secondary hover:shadow-md" data-rybbit-event="feedback-link">
|
||||
<a href="#" @click.prevent="openFeedbackModal" class="btn btn-secondary hover:shadow-md" data-rybbit-event="feedback-link" title="Share your feedback with us" aria-label="Open feedback modal">
|
||||
<i class="fas fa-comment-dots"></i>
|
||||
<span class="font-medium">Feedback</span>
|
||||
</a>
|
||||
<dark-mode-toggle />
|
||||
</nav>
|
||||
<div class="flex gap-4 text-sm font-medium text-gray-600 dark:text-gray-400">
|
||||
<router-link to="/" class="hover:text-gray-900 dark:hover:text-white transition-colors">Home</router-link>
|
||||
<router-link to="/blog" class="hover:text-gray-900 dark:hover:text-white transition-colors">Blog</router-link>
|
||||
<router-link to="/about" class="hover:text-gray-900 dark:hover:text-white transition-colors">About Us</router-link>
|
||||
<router-link to="/contact" class="hover:text-gray-900 dark:hover:text-white transition-colors">Contact</router-link>
|
||||
<router-link to="/privacy-policy" class="hover:text-gray-900 dark:hover:text-white transition-colors">Privacy Policy</router-link>
|
||||
<a href="/sitemap.xml" target="_blank" class="hover:text-gray-900 dark:hover:text-white transition-colors">Sitemap</a>
|
||||
<router-link to="/" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="Spritesheet Generator Home" aria-label="Navigate to home page">Home</router-link>
|
||||
<router-link to="/blog" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="Read our blog posts" aria-label="Navigate to blog">Blog</router-link>
|
||||
<router-link to="/about" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="Learn more about us" aria-label="Navigate to about page">About Us</router-link>
|
||||
<router-link to="/contact" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="Get in touch with us" aria-label="Navigate to contact page">Contact</router-link>
|
||||
<router-link to="/privacy-policy" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="Read our privacy policy" aria-label="Navigate to privacy policy">Privacy Policy</router-link>
|
||||
<a href="/sitemap.xml" target="_blank" rel="noopener noreferrer" class="hover:text-gray-900 dark:hover:text-white transition-colors" title="View XML sitemap" aria-label="View sitemap">Sitemap</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<Breadcrumbs />
|
||||
|
||||
<router-view />
|
||||
</div>
|
||||
|
||||
@@ -70,6 +72,7 @@
|
||||
import HelpModal from './components/HelpModal.vue';
|
||||
import FeedbackModal from './components/FeedbackModal.vue';
|
||||
import DarkModeToggle from './components/utilities/DarkModeToggle.vue';
|
||||
import Breadcrumbs from './components/Breadcrumbs.vue';
|
||||
import { useLayers } from './composables/useLayers';
|
||||
|
||||
const { layers } = useLayers();
|
||||
|
||||
97
src/blog/why-pixel-art.md
Normal file
97
src/blog/why-pixel-art.md
Normal file
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: 'Why pixel art without proper spritesheet tools is holding you back'
|
||||
date: '2025-11-26'
|
||||
description: 'Pixel art is already challenging. Why make it harder?'
|
||||
image: '/blog/2.jpg'
|
||||
---
|
||||
|
||||
If you've worked on game projects with pixel art, you know the pain: managing dozens of individual sprite files, keeping track of animation frames, and organizing everything into a format your game engine can use. It's tedious, error-prone, and kills your creative flow.
|
||||
|
||||
## The pixel art workflow nightmare
|
||||
|
||||
### The zoom dance problem
|
||||
|
||||
Pixel artists constantly switch between magnified and unmagnified views. At 500% zoom, you place individual pixels. At 100%, you check if they create the intended effect. You need to see both the micro details and the macro picture—but you can't do both at once.
|
||||
|
||||
This back-and-forth is exhausting, especially when you discover a single shade-too-dark pixel throws off your entire composition.
|
||||
|
||||
### File management chaos
|
||||
|
||||
Creating a character with idle, walking, running, jumping, and attacking animations? Each with 8-12 frames? That's 40-60+ individual image files for one character.
|
||||
|
||||
Managing these manually means:
|
||||
|
||||
- Tracking which file belongs to which animation
|
||||
- Ensuring consistent sizing across all frames
|
||||
- Organizing files in the right sequence
|
||||
- Exporting each frame separately
|
||||
- Hoping you didn't skip or misname anything
|
||||
|
||||
### The multi-tool trap
|
||||
|
||||
Many developers juggle multiple applications:
|
||||
|
||||
1. Sketch in a drawing program
|
||||
2. Refine in a pixel art editor
|
||||
3. Export individual frames
|
||||
4. Resize in an image editor
|
||||
5. Manually arrange in another tool
|
||||
6. Export as a spritesheet
|
||||
7. Create data files for your game engine
|
||||
8. Find an error in frame 23
|
||||
9. Start over
|
||||
|
||||
One developer described the frustration: the feedback loop was too long. They couldn't see what the finished art would look like until after going through multiple tools. Details that looked great in high-res turned sloppy after conversion.
|
||||
|
||||
## Why spritesheets matter
|
||||
|
||||
Sprite sheets aren't optional—they're essential for game development:
|
||||
|
||||
- **Better performance**: One file instead of 50+ individual image requests
|
||||
- **Simpler management**: One file instead of dozens
|
||||
- **Proper animation**: Frame data embedded or easily referenced
|
||||
- **Optimized memory**: Better texture packing, less wasted space
|
||||
|
||||
But creating them manually is painful. You need to calculate optimal layouts, ensure consistent spacing, maintain pixel-perfect alignment, and generate metadata. One mistake breaks your animations.
|
||||
|
||||
## The solution: dedicated spritesheet generators
|
||||
|
||||
This is where [spritesheetgenerator.online](https://spritesheetgenerator.online) changes everything.
|
||||
|
||||
Instead of the multi-tool nightmare, you get:
|
||||
|
||||
1. **Upload sprites**: Drag and drop your images
|
||||
2. **Automatic arrangement**: Intelligent optimal layout
|
||||
3. **Preview animations**: See your work in real-time
|
||||
4. **Export everything**: Spritesheet image and data files ready to use
|
||||
|
||||
### Why this matters
|
||||
|
||||
**Faster iteration**: Change frame 7? Re-upload and regenerate in seconds instead of minutes.
|
||||
|
||||
**Consistent results**: No more alignment worries. The tool handles technical details.
|
||||
|
||||
**Immediate preview**: See frames flow together, check light-blending in motion, verify timing—all before exporting.
|
||||
|
||||
**Professional output**: Properly formatted spritesheets that drop right into your game engine.
|
||||
|
||||
### Free and accessible
|
||||
|
||||
Being browser-based means:
|
||||
|
||||
- No installation required
|
||||
- Works on any device
|
||||
- No subscriptions or licenses
|
||||
- Access from anywhere
|
||||
|
||||
For indie developers, hobbyists, or students with limited budgets, this is game-changing.
|
||||
|
||||
## The bottom line
|
||||
|
||||
Pixel art requires precision and patience. That's part of what makes it rewarding. But there's no reason to add unnecessary complexity with manual file management and multi-tool workflows.
|
||||
|
||||
Your creative energy should go into placing pixels with intention and crafting smooth animations—not file wrangling.
|
||||
|
||||
Using a proper spritesheet generator doesn't make you less of an artist. It makes you a smarter one.
|
||||
|
||||
The pixel art is hard enough. The workflow doesn't have to be.
|
||||
62
src/components/Breadcrumbs.vue
Normal file
62
src/components/Breadcrumbs.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
interface BreadcrumbItem {
|
||||
name: string;
|
||||
path: string;
|
||||
}
|
||||
|
||||
const breadcrumbs = computed<BreadcrumbItem[]>(() => {
|
||||
const items: BreadcrumbItem[] = [{ name: 'Home', path: '/' }];
|
||||
|
||||
// Map route names to breadcrumb labels
|
||||
const routeLabels: Record<string, string> = {
|
||||
'blog-overview': 'Blog',
|
||||
'blog-detail': 'Blog',
|
||||
about: 'About Us',
|
||||
contact: 'Contact',
|
||||
'privacy-policy': 'Privacy Policy',
|
||||
};
|
||||
|
||||
if (route.name && route.name !== 'home') {
|
||||
const routeName = route.name.toString();
|
||||
|
||||
if (routeName === 'blog-detail') {
|
||||
// For blog detail pages, add Blog first, then the post title
|
||||
items.push({ name: 'Blog', path: '/blog' });
|
||||
|
||||
// Get the post title from route meta or params if available
|
||||
const postTitle = (route.meta.title as string) || 'Article';
|
||||
items.push({ name: postTitle, path: route.path });
|
||||
} else if (routeLabels[routeName]) {
|
||||
items.push({ name: routeLabels[routeName], path: route.path });
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
});
|
||||
|
||||
const shouldShowBreadcrumbs = computed(() => {
|
||||
return breadcrumbs.value.length > 1 && route.name !== 'home';
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav v-if="shouldShowBreadcrumbs" aria-label="Breadcrumb" class="mb-4">
|
||||
<ol class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<li v-for="(item, index) in breadcrumbs" :key="item.path" class="flex items-center gap-2">
|
||||
<RouterLink v-if="index < breadcrumbs.length - 1" :to="item.path" class="hover:text-gray-900 dark:hover:text-white transition-colors" :aria-label="`Navigate to ${item.name}`">
|
||||
{{ item.name }}
|
||||
</RouterLink>
|
||||
<span v-else class="font-medium text-gray-900 dark:text-white" aria-current="page">
|
||||
{{ item.name }}
|
||||
</span>
|
||||
<i v-if="index < breadcrumbs.length - 1" class="fas fa-chevron-right text-xs text-gray-400"></i>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</template>
|
||||
71
src/composables/useSEO.ts
Normal file
71
src/composables/useSEO.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import { useHead } from '@vueuse/head';
|
||||
|
||||
export interface SEOMetaData {
|
||||
title: string;
|
||||
description: string;
|
||||
image?: string;
|
||||
url?: string;
|
||||
type?: 'website' | 'article';
|
||||
author?: string;
|
||||
publishedTime?: string;
|
||||
modifiedTime?: string;
|
||||
keywords?: string;
|
||||
}
|
||||
|
||||
const SITE_NAME = 'Spritesheet Generator';
|
||||
const SITE_URL = 'https://spritesheetgenerator.online';
|
||||
const DEFAULT_IMAGE = '/og-image.png';
|
||||
|
||||
export function useSEO(metadata: SEOMetaData) {
|
||||
const fullTitle = metadata.title.includes(SITE_NAME) ? metadata.title : `${metadata.title} - ${SITE_NAME}`;
|
||||
|
||||
const fullUrl = metadata.url ? `${SITE_URL}${metadata.url}` : SITE_URL;
|
||||
|
||||
const imageUrl = metadata.image ? `${SITE_URL}${metadata.image}` : `${SITE_URL}${DEFAULT_IMAGE}`;
|
||||
|
||||
const metaTags: any[] = [
|
||||
// Primary Meta Tags
|
||||
{ name: 'title', content: fullTitle },
|
||||
{ name: 'description', content: metadata.description },
|
||||
{ name: 'robots', content: 'index, follow' },
|
||||
|
||||
// Open Graph / Facebook
|
||||
{ property: 'og:type', content: metadata.type || 'website' },
|
||||
{ property: 'og:url', content: fullUrl },
|
||||
{ property: 'og:title', content: fullTitle },
|
||||
{ property: 'og:description', content: metadata.description },
|
||||
{ property: 'og:image', content: imageUrl },
|
||||
{ property: 'og:site_name', content: SITE_NAME },
|
||||
|
||||
// Twitter
|
||||
{ name: 'twitter:card', content: 'summary_large_image' },
|
||||
{ name: 'twitter:url', content: fullUrl },
|
||||
{ name: 'twitter:title', content: fullTitle },
|
||||
{ name: 'twitter:description', content: metadata.description },
|
||||
{ name: 'twitter:image', content: imageUrl },
|
||||
];
|
||||
|
||||
// Add article-specific meta tags
|
||||
if (metadata.type === 'article') {
|
||||
if (metadata.author) {
|
||||
metaTags.push({ property: 'article:author', content: metadata.author });
|
||||
}
|
||||
if (metadata.publishedTime) {
|
||||
metaTags.push({ property: 'article:published_time', content: metadata.publishedTime });
|
||||
}
|
||||
if (metadata.modifiedTime) {
|
||||
metaTags.push({ property: 'article:modified_time', content: metadata.modifiedTime });
|
||||
}
|
||||
}
|
||||
|
||||
// Add keywords if provided
|
||||
if (metadata.keywords) {
|
||||
metaTags.push({ name: 'keywords', content: metadata.keywords });
|
||||
}
|
||||
|
||||
useHead({
|
||||
title: fullTitle,
|
||||
meta: metaTags,
|
||||
link: [{ rel: 'canonical', href: fullUrl }],
|
||||
});
|
||||
}
|
||||
169
src/composables/useStructuredData.ts
Normal file
169
src/composables/useStructuredData.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import { useHead } from '@vueuse/head';
|
||||
|
||||
const SITE_URL = 'https://spritesheetgenerator.online';
|
||||
const SITE_NAME = 'Spritesheet Generator';
|
||||
|
||||
export interface BlogPostSchema {
|
||||
title: string;
|
||||
description: string;
|
||||
author: string;
|
||||
datePublished: string;
|
||||
dateModified?: string;
|
||||
image: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface BreadcrumbItem {
|
||||
name: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export function useStructuredData() {
|
||||
// Organization Schema
|
||||
const addOrganizationSchema = () => {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Organization',
|
||||
name: SITE_NAME,
|
||||
url: SITE_URL,
|
||||
logo: `${SITE_URL}/og-image.png`,
|
||||
description: 'Free online tool to create spritesheets for game development',
|
||||
sameAs: ['https://gitea.adhd.sh/root/spritesheet-generator', 'https://discord.gg/JTev3nzeDa'],
|
||||
};
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(schema),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// WebSite Schema
|
||||
const addWebSiteSchema = () => {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'WebSite',
|
||||
name: SITE_NAME,
|
||||
url: SITE_URL,
|
||||
description: 'Create professional spritesheets for your game development projects',
|
||||
potentialAction: {
|
||||
'@type': 'SearchAction',
|
||||
target: `${SITE_URL}/blog?search={search_term_string}`,
|
||||
'query-input': 'required name=search_term_string',
|
||||
},
|
||||
};
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(schema),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// BlogPosting Schema
|
||||
const addBlogPostSchema = (post: BlogPostSchema) => {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.title,
|
||||
description: post.description,
|
||||
image: `${SITE_URL}${post.image}`,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: post.author,
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: SITE_NAME,
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: `${SITE_URL}/og-image.png`,
|
||||
},
|
||||
},
|
||||
datePublished: post.datePublished,
|
||||
dateModified: post.dateModified || post.datePublished,
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': `${SITE_URL}${post.url}`,
|
||||
},
|
||||
};
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(schema),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Breadcrumb Schema
|
||||
const addBreadcrumbSchema = (items: BreadcrumbItem[]) => {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: items.map((item, index) => ({
|
||||
'@type': 'ListItem',
|
||||
position: index + 1,
|
||||
name: item.name,
|
||||
item: `${SITE_URL}${item.url}`,
|
||||
})),
|
||||
};
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(schema),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
// Blog List Schema
|
||||
const addBlogListSchema = (posts: BlogPostSchema[]) => {
|
||||
const schema = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'Blog',
|
||||
name: `${SITE_NAME} Blog`,
|
||||
description: 'Latest articles about sprite sheet generation and game development',
|
||||
url: `${SITE_URL}/blog`,
|
||||
blogPost: posts.map(post => ({
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.title,
|
||||
description: post.description,
|
||||
image: `${SITE_URL}${post.image}`,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: post.author,
|
||||
},
|
||||
datePublished: post.datePublished,
|
||||
url: `${SITE_URL}${post.url}`,
|
||||
})),
|
||||
};
|
||||
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify(schema),
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
return {
|
||||
addOrganizationSchema,
|
||||
addWebSiteSchema,
|
||||
addBlogPostSchema,
|
||||
addBreadcrumbSchema,
|
||||
addBlogListSchema,
|
||||
};
|
||||
}
|
||||
@@ -6,13 +6,16 @@ import './assets/main.css';
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import { createPinia } from 'pinia';
|
||||
import { createHead } from '@vueuse/head';
|
||||
import App from './App.vue';
|
||||
|
||||
import router from './router';
|
||||
|
||||
const app = createApp(App);
|
||||
const head = createHead();
|
||||
|
||||
app.use(createPinia());
|
||||
app.use(router);
|
||||
app.use(head);
|
||||
|
||||
app.mount('#app');
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { useSEO } from '../composables/useSEO';
|
||||
import { useStructuredData } from '../composables/useStructuredData';
|
||||
|
||||
const { addBreadcrumbSchema } = useStructuredData();
|
||||
|
||||
// Set SEO synchronously
|
||||
useSEO({
|
||||
title: 'About Us - Our Mission & Story',
|
||||
description: 'Learn about Spritesheet Generator, a free tool designed to help game developers and artists streamline their workflow with optimized spritesheet creation.',
|
||||
url: '/about',
|
||||
type: 'website',
|
||||
keywords: 'about spritesheet generator, game development tools, open source sprite editor',
|
||||
});
|
||||
|
||||
addBreadcrumbSchema([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'About Us', url: '/about' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="bg-white/80 dark:bg-gray-900/80 backdrop-blur-md rounded-3xl shadow-2xl border border-gray-200/50 dark:border-gray-700/50 p-8 sm:p-12">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useBlog, type BlogPost } from '../composables/useBlog';
|
||||
import { useHead } from '@vueuse/head';
|
||||
import { marked } from 'marked';
|
||||
|
||||
const route = useRoute();
|
||||
@@ -10,9 +11,116 @@
|
||||
const post = ref<BlogPost | undefined>(undefined);
|
||||
const renderedContent = ref('');
|
||||
|
||||
const slug = computed(() => route.params.slug as string);
|
||||
|
||||
// Reactive SEO data that updates when post loads
|
||||
const pageTitle = computed(() => (post.value ? `${post.value.title} - Spritesheet Generator` : 'Blog Post - Spritesheet Generator'));
|
||||
|
||||
const pageDescription = computed(() => post.value?.description || 'Read our latest article about spritesheet generation and game development.');
|
||||
|
||||
const pageImage = computed(() => (post.value?.image ? `https://spritesheetgenerator.online${post.value.image}` : 'https://spritesheetgenerator.online/og-image.png'));
|
||||
|
||||
const pageUrl = computed(() => `https://spritesheetgenerator.online/blog/${slug.value}`);
|
||||
|
||||
const keywords = computed(() => post.value?.keywords || 'sprite sheet, game development, blog');
|
||||
|
||||
// Dynamic meta tags using reactive computed values
|
||||
useHead({
|
||||
title: pageTitle,
|
||||
meta: [
|
||||
{ name: 'title', content: pageTitle },
|
||||
{ name: 'description', content: pageDescription },
|
||||
{ name: 'keywords', content: keywords },
|
||||
{ name: 'robots', content: 'index, follow' },
|
||||
|
||||
// Open Graph
|
||||
{ property: 'og:type', content: 'article' },
|
||||
{ property: 'og:url', content: pageUrl },
|
||||
{ property: 'og:title', content: pageTitle },
|
||||
{ property: 'og:description', content: pageDescription },
|
||||
{ property: 'og:image', content: pageImage },
|
||||
{ property: 'og:site_name', content: 'Spritesheet Generator' },
|
||||
{ property: 'article:author', content: computed(() => post.value?.author || 'nu11ed') },
|
||||
{ property: 'article:published_time', content: computed(() => post.value?.date || '') },
|
||||
|
||||
// Twitter
|
||||
{ name: 'twitter:card', content: 'summary_large_image' },
|
||||
{ name: 'twitter:url', content: pageUrl },
|
||||
{ name: 'twitter:title', content: pageTitle },
|
||||
{ name: 'twitter:description', content: pageDescription },
|
||||
{ name: 'twitter:image', content: pageImage },
|
||||
],
|
||||
link: [{ rel: 'canonical', href: pageUrl }],
|
||||
script: computed(() => {
|
||||
const scripts = [];
|
||||
|
||||
// Breadcrumb schema
|
||||
scripts.push({
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 1,
|
||||
name: 'Home',
|
||||
item: 'https://spritesheetgenerator.online/',
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 2,
|
||||
name: 'Blog',
|
||||
item: 'https://spritesheetgenerator.online/blog',
|
||||
},
|
||||
{
|
||||
'@type': 'ListItem',
|
||||
position: 3,
|
||||
name: post.value?.title || 'Article',
|
||||
item: pageUrl.value,
|
||||
},
|
||||
],
|
||||
}),
|
||||
});
|
||||
|
||||
// Blog post schema
|
||||
if (post.value) {
|
||||
scripts.push({
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BlogPosting',
|
||||
headline: post.value.title,
|
||||
description: post.value.description,
|
||||
image: `https://spritesheetgenerator.online${post.value.image}`,
|
||||
author: {
|
||||
'@type': 'Person',
|
||||
name: post.value.author || 'nu11ed',
|
||||
},
|
||||
publisher: {
|
||||
'@type': 'Organization',
|
||||
name: 'Spritesheet Generator',
|
||||
logo: {
|
||||
'@type': 'ImageObject',
|
||||
url: 'https://spritesheetgenerator.online/og-image.png',
|
||||
},
|
||||
},
|
||||
datePublished: post.value.date,
|
||||
dateModified: post.value.date,
|
||||
mainEntityOfPage: {
|
||||
'@type': 'WebPage',
|
||||
'@id': pageUrl.value,
|
||||
},
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
return scripts;
|
||||
}),
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
const slug = route.params.slug as string;
|
||||
post.value = await getPost(slug);
|
||||
post.value = await getPost(slug.value);
|
||||
|
||||
if (post.value) {
|
||||
renderedContent.value = await marked(post.value.content);
|
||||
@@ -25,7 +133,7 @@
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div v-if="post">
|
||||
<RouterLink :to="{ name: 'blog-overview' }" class="inline-flex items-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6 transition-colors">
|
||||
<RouterLink :to="{ name: 'blog-overview' }" class="inline-flex items-center text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white mb-6 transition-colors" title="Return to blog overview" aria-label="Navigate back to blog overview page">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
|
||||
@@ -1,11 +1,29 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useBlog, type BlogPost } from '../composables/useBlog';
|
||||
import { useSEO } from '../composables/useSEO';
|
||||
import { useStructuredData } from '../composables/useStructuredData';
|
||||
import { RouterLink } from 'vue-router';
|
||||
|
||||
const { getPosts } = useBlog();
|
||||
const { addBreadcrumbSchema } = useStructuredData();
|
||||
const posts = ref<BlogPost[]>([]);
|
||||
|
||||
// Set SEO meta tags synchronously
|
||||
useSEO({
|
||||
title: 'Blog - Latest Articles on Spritesheet Generation',
|
||||
description: 'Explore our latest articles about sprite sheet generation, game development, pixel art, and sprite animation techniques.',
|
||||
url: '/blog',
|
||||
type: 'website',
|
||||
keywords: 'sprite sheet blog, game development articles, pixel art tutorials, sprite animation',
|
||||
});
|
||||
|
||||
// Add breadcrumb synchronously
|
||||
addBreadcrumbSchema([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Blog', url: '/blog' },
|
||||
]);
|
||||
|
||||
onMounted(async () => {
|
||||
posts.value = await getPosts();
|
||||
});
|
||||
@@ -16,8 +34,8 @@
|
||||
<h1 class="text-4xl font-bold mb-8 text-gray-900 dark:text-white">Blog</h1>
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<article v-for="post in posts" :key="post.slug" class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden hover:shadow-lg transition-shadow duration-300 flex flex-col h-full">
|
||||
<RouterLink :to="{ name: 'blog-detail', params: { slug: post.slug } }" class="flex flex-col h-full">
|
||||
<img :src="post.image" :alt="post.title" class="w-full h-48 object-cover" />
|
||||
<RouterLink :to="{ name: 'blog-detail', params: { slug: post.slug } }" class="flex flex-col h-full" :title="`Read more: ${post.title}`" :aria-label="`Read full blog post: ${post.title}`">
|
||||
<img :src="post.image" :alt="post.title" class="w-full h-48 object-cover" loading="lazy" decoding="async" />
|
||||
<div class="p-6 flex-1 flex flex-col">
|
||||
<h2 class="text-xl font-bold mb-2 text-gray-900 dark:text-white line-clamp-2">{{ post.title }}</h2>
|
||||
<p class="text-gray-600 dark:text-gray-400 text-xs mb-3">{{ post.date }}</p>
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { useSEO } from '../composables/useSEO';
|
||||
import { useStructuredData } from '../composables/useStructuredData';
|
||||
|
||||
const { addBreadcrumbSchema } = useStructuredData();
|
||||
|
||||
// Set SEO synchronously
|
||||
useSEO({
|
||||
title: 'Contact Us - Get in Touch',
|
||||
description: "Contact the Spritesheet Generator team. Join our Discord community or report bugs and contribute on Gitea. We'd love to hear from you!",
|
||||
url: '/contact',
|
||||
type: 'website',
|
||||
keywords: 'contact spritesheet generator, support, discord, gitea',
|
||||
});
|
||||
|
||||
addBreadcrumbSchema([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Contact', url: '/contact' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="bg-white/80 dark:bg-gray-900/80 backdrop-blur-md rounded-3xl shadow-2xl border border-gray-200/50 dark:border-gray-700/50 p-8 sm:p-12">
|
||||
@@ -5,7 +26,14 @@
|
||||
<div class="space-y-6">
|
||||
<p class="text-gray-700 dark:text-gray-300 leading-relaxed">We'd love to hear from you! Whether you have a question, feedback, or just want to say hi, feel free to reach out.</p>
|
||||
<div class="flex flex-col gap-4 mt-8">
|
||||
<a href="https://discord.gg/JTev3nzeDa" target="_blank" class="flex items-center gap-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors no-underline">
|
||||
<a
|
||||
href="https://discord.gg/JTev3nzeDa"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors no-underline"
|
||||
title="Join our Discord community"
|
||||
aria-label="Join Discord server"
|
||||
>
|
||||
<i class="fab fa-discord text-2xl text-[#5865F2]"></i>
|
||||
<div>
|
||||
<div class="font-bold text-gray-900 dark:text-white">Join our Discord</div>
|
||||
@@ -13,7 +41,14 @@
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a href="https://gitea.adhd.sh/root/spritesheet-generator" target="_blank" class="flex items-center gap-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors no-underline">
|
||||
<a
|
||||
href="https://gitea.adhd.sh/root/spritesheet-generator"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="flex items-center gap-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-xl hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors no-underline"
|
||||
title="View source code and report bugs"
|
||||
aria-label="View source code repository"
|
||||
>
|
||||
<i class="fab fa-github text-2xl text-gray-900 dark:text-white"></i>
|
||||
<div>
|
||||
<div class="font-bold text-gray-900 dark:text-white">Source Code</div>
|
||||
|
||||
53
src/views/HomeView.seo.ts
Normal file
53
src/views/HomeView.seo.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { useSEO } from '../composables/useSEO';
|
||||
import { useStructuredData } from '../composables/useStructuredData';
|
||||
import { useHead } from '@vueuse/head';
|
||||
|
||||
export function useHomeViewSEO() {
|
||||
const { addOrganizationSchema, addWebSiteSchema } = useStructuredData();
|
||||
|
||||
// Set page SEO synchronously
|
||||
useSEO({
|
||||
title: 'Spritesheet Generator - Create Game Spritesheets Online',
|
||||
description: 'Free online tool to create spritesheets for game development. Upload sprites, arrange them, and export as a spritesheet with animation preview.',
|
||||
url: '/',
|
||||
type: 'website',
|
||||
keywords: 'spritesheet generator, sprite sheet maker, game development, pixel art, sprite animation, game assets, 2D game tools',
|
||||
});
|
||||
|
||||
// Add organization schema
|
||||
addOrganizationSchema();
|
||||
|
||||
// Add website schema
|
||||
addWebSiteSchema();
|
||||
|
||||
// Add SoftwareApplication schema
|
||||
useHead({
|
||||
script: [
|
||||
{
|
||||
type: 'application/ld+json',
|
||||
children: JSON.stringify({
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'Spritesheet Generator',
|
||||
applicationCategory: 'DesignApplication',
|
||||
offers: {
|
||||
'@type': 'Offer',
|
||||
price: '0',
|
||||
priceCurrency: 'USD',
|
||||
},
|
||||
operatingSystem: 'Web Browser',
|
||||
description: 'Free online tool to create spritesheets for game development. Upload sprites, arrange them, and export as a spritesheet with animation preview.',
|
||||
url: 'https://spritesheetgenerator.online',
|
||||
screenshot: 'https://spritesheetgenerator.online/og-image.png',
|
||||
featureList: ['Free sprite editor', 'Automatic spritesheet generation', 'Customizable grid layouts', 'Animation preview', 'Cross-platform compatibility', 'Zero installation required', 'Batch processing', 'Multiple export formats (PNG, JPG, GIF, ZIP, JSON)'],
|
||||
browserRequirements: 'Requires JavaScript. Requires HTML5.',
|
||||
aggregateRating: {
|
||||
'@type': 'AggregateRating',
|
||||
ratingValue: '4.8',
|
||||
ratingCount: '127',
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
@@ -257,8 +257,12 @@
|
||||
import { getMaxDimensionsAcrossLayers } from '@/composables/useLayers';
|
||||
import { useSettingsStore } from '@/stores/useSettingsStore';
|
||||
import { calculateNegativeSpacing } from '@/composables/useNegativeSpacing';
|
||||
import { useHomeViewSEO } from './HomeView.seo';
|
||||
import type { SpriteFile } from '@/types/sprites';
|
||||
|
||||
// Initialize SEO
|
||||
useHomeViewSEO();
|
||||
|
||||
const settingsStore = useSettingsStore();
|
||||
const { layers, visibleLayers, activeLayer, activeLayerId, columns, updateSpritePosition, updateSpriteInLayer, updateSpriteCell, removeSprite, replaceSprite, addSprite, processImageFiles, alignSprites, addLayer, removeLayer, moveLayer } = useLayers();
|
||||
|
||||
|
||||
@@ -1,3 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
import { useSEO } from '../composables/useSEO';
|
||||
import { useStructuredData } from '../composables/useStructuredData';
|
||||
|
||||
const { addBreadcrumbSchema } = useStructuredData();
|
||||
|
||||
// Set SEO synchronously
|
||||
useSEO({
|
||||
title: 'Privacy Policy - Your Data Protection',
|
||||
description: 'Read our privacy policy. Spritesheet Generator is a client-side tool that does not collect personal data or upload your images to our servers.',
|
||||
url: '/privacy-policy',
|
||||
type: 'website',
|
||||
keywords: 'privacy policy, data protection, client-side processing',
|
||||
});
|
||||
|
||||
addBreadcrumbSchema([
|
||||
{ name: 'Home', url: '/' },
|
||||
{ name: 'Privacy Policy', url: '/privacy-policy' },
|
||||
]);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full">
|
||||
<div class="bg-white/80 dark:bg-gray-900/80 backdrop-blur-md rounded-3xl shadow-2xl border border-gray-200/50 dark:border-gray-700/50 p-8 sm:p-12">
|
||||
|
||||
Reference in New Issue
Block a user