From 09c77f54149b1791bc17f6b10eca106b7c1fbe30 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 26 Nov 2025 17:13:42 +0100 Subject: [PATCH] BReadcrumbs --- index.html | 15 ++- package-lock.json | 119 ++++++++++++++++++ package.json | 1 + src/App.vue | 3 + src/components/Breadcrumbs.vue | 69 +++++++++++ src/composables/useSEO.ts | 79 ++++++++++++ src/composables/useStructuredData.ts | 172 +++++++++++++++++++++++++++ src/main.ts | 3 + src/views/AboutUs.vue | 23 ++++ src/views/BlogDetail.vue | 34 +++++- src/views/BlogOverview.vue | 34 +++++- src/views/Contact.vue | 27 ++++- src/views/HomeView.seo.ts | 65 ++++++++++ src/views/HomeView.vue | 4 + src/views/PrivacyPolicy.vue | 23 ++++ 15 files changed, 663 insertions(+), 8 deletions(-) create mode 100644 src/components/Breadcrumbs.vue create mode 100644 src/composables/useSEO.ts create mode 100644 src/composables/useStructuredData.ts create mode 100644 src/views/HomeView.seo.ts diff --git a/index.html b/index.html index f091d86..da22870 100644 --- a/index.html +++ b/index.html @@ -2,16 +2,24 @@ + + + + + + + + + - + - Spritesheet generator - Create Game Spritesheets Online @@ -20,6 +28,7 @@ + @@ -39,8 +48,6 @@ - Spritesheet generator - + + diff --git a/src/composables/useSEO.ts b/src/composables/useSEO.ts new file mode 100644 index 0000000..005c05e --- /dev/null +++ b/src/composables/useSEO.ts @@ -0,0 +1,79 @@ +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 } + ] + }); +} diff --git a/src/composables/useStructuredData.ts b/src/composables/useStructuredData.ts new file mode 100644 index 0000000..d768bba --- /dev/null +++ b/src/composables/useStructuredData.ts @@ -0,0 +1,172 @@ +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 + }; +} diff --git a/src/main.ts b/src/main.ts index 6d7fed9..f09902b 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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'); diff --git a/src/views/AboutUs.vue b/src/views/AboutUs.vue index bd7d5dd..0950440 100644 --- a/src/views/AboutUs.vue +++ b/src/views/AboutUs.vue @@ -1,3 +1,26 @@ + +