diff --git a/sites/plus.skeleton.dev/scripts/screenshots.js b/sites/plus.skeleton.dev/scripts/screenshots.js index beadb9abd7..cf5137ebe2 100644 --- a/sites/plus.skeleton.dev/scripts/screenshots.js +++ b/sites/plus.skeleton.dev/scripts/screenshots.js @@ -1,6 +1,17 @@ /** - * Captures and optimizes screenshots for community project pages. - * Run with: pnpm screenshots + * Captures and optimizes imagery for the plus.skeleton.dev showcases. + * + * Usage: + * pnpm screenshots capture both showcases (community + templates) + * pnpm screenshots --community capture community projects only + * pnpm screenshots --template capture templates only + * + * Per entry the script writes: + * - card.webp used by the listing/index page (768x432) + * - hero.webp used by the detail page banner (1920x1080) + * - screenshot-NNN.webp one per screenshot URL (zero-padded, 3 digits) + * + * Screenshot dimensions are per-showcase (community: 960x540, templates: 426x240). * * Page selection guidelines: * - Prefer visually rich pages: tools, editors, galleries, dashboards @@ -8,7 +19,7 @@ * - Skip legal, privacy, and terms pages * - Avoid pages that are mostly text with no notable design * - Use scrollY to capture sections below the fold on single-page sites - * - heroCard and heroDetail should target the homepage hero section + * - homepageUrl should target the project/template homepage hero */ import { chromium } from 'playwright'; @@ -18,32 +29,71 @@ import path from 'path'; import { fileURLToPath } from 'url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); -const OUT = path.join(__dirname, '../src/lib/assets/images/community'); - -// Config --- -// Add a new entry here for each community project. The slug must match the -// project's directory name under src/lib/assets/images/community/. - -const projects = [ - { - slug: 'etesie', - heroUrl: 'https://etesie.dev', - screenshots: [ - { url: 'https://etesie.dev/themes/browse' }, - { url: 'https://etesie.dev/docs/auth/overview/introduction' }, - { url: 'https://etesie.dev/figma' }, +const ASSETS = path.join(__dirname, '../src/lib/assets/images'); + +// Capture viewport (matches the largest output — hero — so we never upscale). +const VIEWPORT = { width: 1920, height: 1080 }; + +// Shared output sizes. +const CARD = { width: 768, height: 432, quality: 88 }; +const HERO = { width: 1920, height: 1080, quality: 92 }; + +// Showcases --- + +const showcases = { + community: { + label: 'community', + outDir: path.join(ASSETS, 'community'), + screenshotSize: { width: 960, height: 540, quality: 85 }, + entries: [ + { + slug: 'etesie', + homepageUrl: 'https://etesie.dev', + screenshots: [ + { url: 'https://etesie.dev/themes/browse' }, + { url: 'https://etesie.dev/docs/auth/overview/introduction' }, + { url: 'https://etesie.dev/figma' }, + ], + }, + { + slug: 'typogram', + homepageUrl: 'https://typogram.co', + screenshots: [ + { url: 'https://typogram.co/studio/landing.html' }, + { url: 'https://typogram.co/brand-guidelines.html?brandName=Radiant%20Waves%20Reiki' }, + { url: 'https://typogram.co/font-discovery/' }, + ], + }, ], }, - { - slug: 'typogram', - heroUrl: 'https://typogram.co', - screenshots: [ - { url: 'https://typogram.co/studio/landing.html' }, - { url: 'https://typogram.co/brand-guidelines.html?brandName=Radiant%20Waves%20Reiki' }, - { url: 'https://typogram.co/font-discovery/' }, + template: { + label: 'templates', + outDir: path.join(ASSETS, 'templates'), + screenshotSize: { width: 426, height: 240, quality: 85 }, + entries: [ + { + slug: 'persona', + homepageUrl: 'https://skeleton-template-portfolio.vercel.app/', + screenshots: [ + { url: 'https://skeleton-template-portfolio.vercel.app/about' }, + { url: 'https://skeleton-template-portfolio.vercel.app/projects' }, + { url: 'https://skeleton-template-portfolio.vercel.app/projects/development/project-1' }, + { url: 'https://skeleton-template-portfolio.vercel.app/projects/design/project-6' }, + ], + }, + { + slug: 'stardust', + homepageUrl: 'https://skeleton-template-blog.vercel.app/', + screenshots: [ + { url: 'https://skeleton-template-blog.vercel.app/about' }, + { url: 'https://skeleton-template-blog.vercel.app/blog' }, + { url: 'https://skeleton-template-blog.vercel.app/blog/second-post/' }, + { url: 'https://skeleton-template-blog.vercel.app/blog/third-post/' }, + ], + }, ], }, -]; +}; // Helpers --- @@ -67,11 +117,11 @@ async function hideScrollbars(page) { }); } -// Opens a page, waits for it to settle, then returns a raw screenshot buffer. -async function capturePage(browser, { url, width, height, scrollY = 0 }) { +// Opens a page, waits for it to settle, then returns a raw screenshot buffer at VIEWPORT. +async function capturePage(browser, { url, scrollY = 0 }) { const ctx = await browser.newContext({ colorScheme: 'dark', - viewport: { width, height }, + viewport: VIEWPORT, deviceScaleFactor: 1, }); const page = await ctx.newPage(); @@ -86,38 +136,56 @@ async function capturePage(browser, { url, width, height, scrollY = 0 }) { await page.waitForTimeout(500); } - const buffer = await page.screenshot({ clip: { x: 0, y: 0, width, height } }); + const buffer = await page.screenshot({ clip: { x: 0, y: 0, ...VIEWPORT } }); await page.close(); + await ctx.close(); return buffer; } -// Capture functions --- - -async function captureHero(browser, url, dest) { - console.log(` hero -> ${url}`); - const buffer = await capturePage(browser, { url, width: 1280, height: 720 }); - - await sharp(buffer).resize(1280, 720, { fit: 'cover', position: 'top' }).webp({ quality: 92 }).toFile(path.join(dest, 'hero.webp')); - console.log(' saved hero.webp'); +// Writes a buffer to disk resized to the given output spec. +async function writeImage(buffer, { width, height, quality }, file) { + await sharp(buffer).resize(width, height, { fit: 'cover', position: 'top' }).webp({ quality }).toFile(file); } -async function captureScreenshot(browser, { url, scrollY }, index, dest) { - const filename = `screenshot-${String(index).padStart(3, '0')}.webp`; - console.log(` ${filename} -> ${url}`); +// Pipeline --- +async function captureEntry(browser, entry, showcase) { + console.log(`\n=== ${showcase.label}/${entry.slug} ===`); + const dest = path.join(showcase.outDir, entry.slug); + fs.mkdirSync(dest, { recursive: true }); + + // Homepage → card + hero (one capture, two outputs). + console.log(` homepage -> ${entry.homepageUrl}`); try { - const buffer = await capturePage(browser, { url, width: 1280, height: 720, scrollY }); - await sharp(buffer).resize(960, 540, { fit: 'cover', position: 'top' }).webp({ quality: 85 }).toFile(path.join(dest, filename)); - console.log(` saved ${filename}`); + const buffer = await capturePage(browser, { url: entry.homepageUrl }); + await writeImage(buffer, HERO, path.join(dest, 'hero.webp')); + console.log(' saved hero.webp'); + await writeImage(buffer, CARD, path.join(dest, 'card.webp')); + console.log(' saved card.webp'); } catch (e) { - console.error(` FAILED: ${e.message}`); + console.error(` FAILED homepage: ${e.message}`); + } + + // Screenshots. + for (let i = 0; i < entry.screenshots.length; i++) { + const { url, scrollY } = entry.screenshots[i]; + const file = `screenshot-${String(i + 1).padStart(3, '0')}.webp`; + console.log(` ${file} -> ${url}`); + try { + const buffer = await capturePage(browser, { url, scrollY }); + await writeImage(buffer, showcase.screenshotSize, path.join(dest, file)); + console.log(` saved ${file}`); + } catch (e) { + console.error(` FAILED: ${e.message}`); + } } } -function printSummary(projects) { - console.log('\n=== Done ==='); - for (const { slug } of projects) { - const dir = path.join(OUT, slug); +function printSummary(showcase) { + console.log(`\n=== Summary: ${showcase.label} ===`); + for (const { slug } of showcase.entries) { + const dir = path.join(showcase.outDir, slug); + if (!fs.existsSync(dir)) continue; const files = fs.readdirSync(dir).sort(); console.log(`\n${slug}:`); for (const file of files) { @@ -129,21 +197,27 @@ function printSummary(projects) { // Main --- +function selectShowcases(argv) { + const flags = argv.slice(2); + if (flags.length === 0) return [showcases.community, showcases.template]; + if (flags.length === 1 && flags[0] === '--community') return [showcases.community]; + if (flags.length === 1 && flags[0] === '--template') return [showcases.template]; + + console.error('Usage: pnpm screenshots [--community | --template]'); + process.exit(1); +} + +const selected = selectShowcases(process.argv); + // --hide-scrollbars suppresses scrollbars at the browser level; hideScrollbars() // also injects CSS as a fallback for sites that render their own scrollbar styles. const browser = await chromium.launch({ args: ['--hide-scrollbars'] }); -for (const project of projects) { - console.log(`\n=== ${project.slug} ===`); - const dest = path.join(OUT, project.slug); - fs.mkdirSync(dest, { recursive: true }); - - await captureHero(browser, project.heroUrl, dest); - - for (let i = 0; i < project.screenshots.length; i++) { - await captureScreenshot(browser, project.screenshots[i], i + 1, dest); +for (const showcase of selected) { + for (const entry of showcase.entries) { + await captureEntry(browser, entry, showcase); } } await browser.close(); -printSummary(projects); +for (const showcase of selected) printSummary(showcase); diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/card.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/card.webp new file mode 100644 index 0000000000..ade76a10fc Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/card.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/hero.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/hero.webp index dc5eaed4f6..a0ec84cc54 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/hero.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/hero.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-001.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-001.webp index f8172e15f6..578be3a363 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-001.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-001.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-002.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-002.webp index baccb6a766..727a7d980f 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-002.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-002.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-003.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-003.webp index 015174b6be..0dad1ddf97 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-003.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/etesie/screenshot-003.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/card.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/card.webp new file mode 100644 index 0000000000..944a016354 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/card.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/hero.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/hero.webp index fa8cc31ff9..d8ace3a74b 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/hero.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/hero.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-001.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-001.webp index fb9cef4828..99875c5d90 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-001.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-001.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-002.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-002.webp index e99d5239a8..1690a84503 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-002.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-002.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-003.webp b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-003.webp index 743d8c7f67..c7bffff627 100644 Binary files a/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-003.webp and b/sites/plus.skeleton.dev/src/lib/assets/images/community/typogram/screenshot-003.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/card.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/card.webp new file mode 100644 index 0000000000..083dcd2a44 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/card.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/hero.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/hero.webp new file mode 100644 index 0000000000..5edb8cf814 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/hero.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-001.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-001.webp new file mode 100644 index 0000000000..741b5b4cbf Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-001.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-002.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-002.webp new file mode 100644 index 0000000000..7ab31e6581 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-002.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-003.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-003.webp new file mode 100644 index 0000000000..cc5add2be1 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-003.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-004.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-004.webp new file mode 100644 index 0000000000..ff91b112da Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/persona/screenshot-004.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/card.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/card.webp new file mode 100644 index 0000000000..ddfc4e519c Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/card.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/hero.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/hero.webp new file mode 100644 index 0000000000..3159d039fa Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/hero.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-001.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-001.webp new file mode 100644 index 0000000000..6b16ea2dca Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-001.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-002.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-002.webp new file mode 100644 index 0000000000..26eabbb650 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-002.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-003.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-003.webp new file mode 100644 index 0000000000..3e08a1ae9a Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-003.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-004.webp b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-004.webp new file mode 100644 index 0000000000..2a56543531 Binary files /dev/null and b/sites/plus.skeleton.dev/src/lib/assets/images/templates/stardust/screenshot-004.webp differ diff --git a/sites/plus.skeleton.dev/src/lib/components/layout/footer.svelte b/sites/plus.skeleton.dev/src/lib/components/layout/footer.svelte index e274734a56..bef115bab1 100644 --- a/sites/plus.skeleton.dev/src/lib/components/layout/footer.svelte +++ b/sites/plus.skeleton.dev/src/lib/components/layout/footer.svelte @@ -47,7 +47,7 @@ {/each} - + diff --git a/sites/plus.skeleton.dev/src/lib/components/layout/page-header.svelte b/sites/plus.skeleton.dev/src/lib/components/layout/page-header.svelte index c7af297bc6..ba2d5204b4 100644 --- a/sites/plus.skeleton.dev/src/lib/components/layout/page-header.svelte +++ b/sites/plus.skeleton.dev/src/lib/components/layout/page-header.svelte @@ -78,5 +78,5 @@ - + diff --git a/sites/plus.skeleton.dev/src/lib/remote/community/get-projects.remote.ts b/sites/plus.skeleton.dev/src/lib/remote/community/get-projects.remote.ts index 67ffad8351..cfb02111fe 100644 --- a/sites/plus.skeleton.dev/src/lib/remote/community/get-projects.remote.ts +++ b/sites/plus.skeleton.dev/src/lib/remote/community/get-projects.remote.ts @@ -7,11 +7,13 @@ import { getRequestEvent, query } from '$app/server'; +import etesieCard from '$lib/assets/images/community/etesie/card.webp'; import etesieHero from '$lib/assets/images/community/etesie/hero.webp'; import etesieScreenshot001 from '$lib/assets/images/community/etesie/screenshot-001.webp'; import etesieScreenshot002 from '$lib/assets/images/community/etesie/screenshot-002.webp'; import etesieScreenshot003 from '$lib/assets/images/community/etesie/screenshot-003.webp'; +import typogramCard from '$lib/assets/images/community/typogram/card.webp'; import typogramHero from '$lib/assets/images/community/typogram/hero.webp'; import typogramScreenshot001 from '$lib/assets/images/community/typogram/screenshot-001.webp'; import typogramScreenshot002 from '$lib/assets/images/community/typogram/screenshot-002.webp'; @@ -51,6 +53,7 @@ export interface CommunityProject { avatar: string; }; images: { + card: CommunityImage; hero: CommunityImage; screenshots: CommunityImage[]; }; @@ -76,6 +79,7 @@ const projects: CommunityProject[] = [ avatar: 'https://www.google.com/s2/favicons?domain=etesie.dev&sz=64', }, images: { + card: { src: etesieCard, alt: 'Etesie homepage — frontend foundations for product teams' }, hero: { src: etesieHero, alt: 'Etesie homepage — frontend foundations for product teams' }, screenshots: [ { src: etesieScreenshot001, alt: 'Etesie theme browser' }, @@ -103,6 +107,7 @@ const projects: CommunityProject[] = [ avatar: 'https://www.google.com/s2/favicons?domain=typogram.co&sz=64', }, images: { + card: { src: typogramCard, alt: 'Typogram homepage — logo and brand design tool' }, hero: { src: typogramHero, alt: 'Typogram homepage — logo and brand design tool' }, screenshots: [ { src: typogramScreenshot001, alt: 'Typogram studio interface' }, diff --git a/sites/plus.skeleton.dev/src/lib/remote/templates/get-templates.remote.ts b/sites/plus.skeleton.dev/src/lib/remote/templates/get-templates.remote.ts new file mode 100644 index 0000000000..d6722ea68b --- /dev/null +++ b/sites/plus.skeleton.dev/src/lib/remote/templates/get-templates.remote.ts @@ -0,0 +1,138 @@ +/** + * Template data for the Plus site's templates showcase. + * Each entry includes metadata, screenshots, and per-framework download links + * for the listing and detail pages. + */ + +import { getRequestEvent, query } from '$app/server'; + +import personaCard from '$lib/assets/images/templates/persona/card.webp'; +import personaHero from '$lib/assets/images/templates/persona/hero.webp'; +import personaScreenshot001 from '$lib/assets/images/templates/persona/screenshot-001.webp'; +import personaScreenshot002 from '$lib/assets/images/templates/persona/screenshot-002.webp'; +import personaScreenshot003 from '$lib/assets/images/templates/persona/screenshot-003.webp'; +import personaScreenshot004 from '$lib/assets/images/templates/persona/screenshot-004.webp'; + +import stardustCard from '$lib/assets/images/templates/stardust/card.webp'; +import stardustHero from '$lib/assets/images/templates/stardust/hero.webp'; +import stardustScreenshot001 from '$lib/assets/images/templates/stardust/screenshot-001.webp'; +import stardustScreenshot002 from '$lib/assets/images/templates/stardust/screenshot-002.webp'; +import stardustScreenshot003 from '$lib/assets/images/templates/stardust/screenshot-003.webp'; +import stardustScreenshot004 from '$lib/assets/images/templates/stardust/screenshot-004.webp'; + +export interface TemplateImage { + src: string; + alt: string; +} + +export interface TemplateDownload { + key: string; + label: string; + href: string; +} + +export interface Template { + slug: string; + name: string; + category: string; + tagline: string; + description: string; + isPremium: boolean; + previewUrl: string; + images: { + card: TemplateImage; + hero: TemplateImage; + screenshots: TemplateImage[]; + }; + downloads: TemplateDownload[]; +} + +// const placeholder = (size: string) => `https://placehold.co/${size}/1f1f1f/666666/webp?font=raleway`; + +const placeholderDescription = + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + +const templates: Template[] = [ + // Free: Portfolio Site + { + slug: 'persona', + name: 'Persona', + category: 'Portfolio', + tagline: 'A personal portfolio website for promoting yourself.', + description: placeholderDescription, + isPremium: false, + previewUrl: 'https://skeleton-template-portfolio.vercel.app/', + images: { + card: { src: personaCard, alt: 'Persona template screenshot' }, + hero: { src: personaHero, alt: 'Persona homepage hero' }, + screenshots: [ + { src: personaScreenshot001, alt: 'Persona about page' }, + { src: personaScreenshot002, alt: 'Persona projects index' }, + { src: personaScreenshot003, alt: 'Persona project detail page' }, + { src: personaScreenshot004, alt: 'Persona design project page' }, + ], + }, + downloads: [ + { key: 'svelte', label: 'SvelteKit', href: 'https://github.com/skeletonlabs/skeleton-templates/tree/main/templates/portfolio' }, + ], + }, + // Free: Blog Site + { + slug: 'stardust', + name: 'Stardust', + category: 'Blog', + tagline: 'An personal blog template powered by markdown.', + description: placeholderDescription, + isPremium: false, + previewUrl: 'https://skeleton-template-blog.vercel.app/', + images: { + card: { src: stardustCard, alt: 'Stardust template screenshot' }, + hero: { src: stardustHero, alt: 'Stardust homepage hero' }, + screenshots: [ + { src: stardustScreenshot001, alt: 'Stardust about page' }, + { src: stardustScreenshot002, alt: 'Stardust blog index' }, + { src: stardustScreenshot003, alt: 'Stardust blog post' }, + { src: stardustScreenshot004, alt: 'Stardust blog post' }, + ], + }, + downloads: [{ key: 'astro', label: 'Astro', href: 'https://github.com/skeletonlabs/skeleton-templates/tree/main/templates/blog' }], + }, + // Premium: Placeholder + // { + // slug: 'placeholder', + // name: 'Placeholder', + // category: 'Admin', + // tagline: 'An admin dashboard template with analytics, billing, and settings.', + // description: placeholderDescription, + // isPremium: true, + // previewUrl: 'https://example.skeleton.dev', + // images: { + // card: { src: placeholder('768x432'), alt: 'Placeholder template screenshot' }, + // hero: { src: placeholder('1920x960'), alt: 'Placeholder dashboard hero' }, + // screenshots: [ + // { src: placeholder('426x240'), alt: 'Placeholder dashboard' }, + // { src: placeholder('426x240'), alt: 'Placeholder analytics' }, + // { src: placeholder('426x240'), alt: 'Placeholder billing' }, + // { src: placeholder('426x240'), alt: 'Placeholder team management' }, + // ], + // }, + // downloads: [ + // { key: 'svelte', label: 'Svelte', href: '#' }, + // { key: 'react', label: 'React', href: '#' }, + // { key: 'vue', label: 'Vue', href: '#' }, + // { key: 'solid', label: 'Solid.js', href: '#' }, + // { key: 'astro', label: 'Astro', href: '#' }, + // ], + // }, +]; + +/** Get all templates */ +export const getTemplates = query(async (): Promise => { + return templates; +}); + +/** Get a single template by slug (matches route param `slug`) */ +export const getTemplate = query(async (): Promise