create-velocity-astro 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,58 @@
1
+ ---
2
+ import PageLayout from '@/layouts/PageLayout.astro';
3
+ import { getCollection } from 'astro:content';
4
+
5
+ const posts = await getCollection('blog', ({ data }) => !data.draft);
6
+ const sortedPosts = posts.sort(
7
+ (a, b) => new Date(b.data.publishDate).getTime() - new Date(a.data.publishDate).getTime()
8
+ );
9
+ ---
10
+
11
+ <PageLayout
12
+ title="Blog"
13
+ description="Read our latest articles and updates."
14
+ >
15
+ <section class="py-16 md:py-24">
16
+ <div class="container mx-auto px-4">
17
+ <h1 class="text-3xl font-bold tracking-tight sm:text-4xl mb-8">Blog</h1>
18
+
19
+ {sortedPosts.length === 0 ? (
20
+ <div class="text-center py-12">
21
+ <p class="text-muted-foreground">No posts yet. Create your first post in</p>
22
+ <code class="bg-muted px-2 py-1 rounded text-sm mt-2 inline-block">
23
+ src/content/blog/
24
+ </code>
25
+ </div>
26
+ ) : (
27
+ <div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
28
+ {sortedPosts.map((post) => (
29
+ <article class="group">
30
+ <a href={`/blog/${post.id}`} class="block">
31
+ {post.data.image && (
32
+ <img
33
+ src={post.data.image}
34
+ alt={post.data.title}
35
+ class="aspect-video w-full rounded-lg object-cover mb-4"
36
+ />
37
+ )}
38
+ <h2 class="text-xl font-semibold group-hover:text-primary transition-colors">
39
+ {post.data.title}
40
+ </h2>
41
+ <p class="mt-2 text-muted-foreground line-clamp-2">
42
+ {post.data.description}
43
+ </p>
44
+ <time class="mt-2 text-sm text-muted-foreground block">
45
+ {new Date(post.data.publishDate).toLocaleDateString('en-US', {
46
+ year: 'numeric',
47
+ month: 'long',
48
+ day: 'numeric',
49
+ })}
50
+ </time>
51
+ </a>
52
+ </article>
53
+ ))}
54
+ </div>
55
+ )}
56
+ </div>
57
+ </section>
58
+ </PageLayout>
@@ -0,0 +1,36 @@
1
+ ---
2
+ import PageLayout from '@/layouts/PageLayout.astro';
3
+ ---
4
+
5
+ <PageLayout
6
+ title="Welcome"
7
+ description="Welcome to your new Velocity site. Start building something amazing."
8
+ >
9
+ <section class="py-24 md:py-32">
10
+ <div class="container mx-auto px-4 text-center">
11
+ <h1 class="text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl">
12
+ Welcome to <span class="text-primary">Velocity</span>
13
+ </h1>
14
+ <p class="mt-6 text-lg text-muted-foreground max-w-2xl mx-auto">
15
+ Your new Astro site is ready. Edit this page at
16
+ <code class="bg-muted px-2 py-1 rounded text-sm">src/pages/index.astro</code>
17
+ </p>
18
+ <div class="mt-10 flex flex-col sm:flex-row gap-4 justify-center">
19
+ <a
20
+ href="/blog"
21
+ class="inline-flex items-center justify-center rounded-md bg-primary px-6 py-3 text-sm font-medium text-primary-foreground shadow transition-colors hover:bg-primary/90"
22
+ >
23
+ View Blog
24
+ </a>
25
+ <a
26
+ href="https://docs.astro.build"
27
+ target="_blank"
28
+ rel="noopener noreferrer"
29
+ class="inline-flex items-center justify-center rounded-md border border-input bg-background px-6 py-3 text-sm font-medium shadow-sm transition-colors hover:bg-accent hover:text-accent-foreground"
30
+ >
31
+ Astro Docs
32
+ </a>
33
+ </div>
34
+ </div>
35
+ </section>
36
+ </PageLayout>
@@ -0,0 +1,73 @@
1
+ ---
2
+ import BlogCard from './BlogCard.astro';
3
+ import { getCollection } from 'astro:content';
4
+ import { defaultLocale, type Locale } from '@/i18n/config';
5
+ import { useTranslations } from '@/i18n/index';
6
+
7
+ interface Props {
8
+ currentSlug: string;
9
+ tags: string[];
10
+ locale?: Locale;
11
+ maxPosts?: number;
12
+ }
13
+
14
+ const { currentSlug, tags, locale = defaultLocale, maxPosts = 3 } = Astro.props;
15
+ const t = useTranslations(locale);
16
+
17
+ // Get all published posts in the same locale
18
+ const allPosts = await getCollection('blog', ({ data }) => {
19
+ return data.locale === locale && (import.meta.env.PROD ? data.draft !== true : true);
20
+ });
21
+
22
+ // Filter to find posts with matching tags
23
+ const relatedPosts = allPosts
24
+ .filter((post) => {
25
+ // Exclude current post
26
+ if (post.id === currentSlug || post.id.endsWith(`/${currentSlug}`)) return false;
27
+
28
+ // Must have at least one matching tag
29
+ if (!tags.length) return false;
30
+ return post.data.tags.some((tag) => tags.includes(tag));
31
+ })
32
+ // Sort by number of matching tags, then by date
33
+ .sort((a, b) => {
34
+ const aMatches = a.data.tags.filter((tag) => tags.includes(tag)).length;
35
+ const bMatches = b.data.tags.filter((tag) => tags.includes(tag)).length;
36
+
37
+ if (bMatches !== aMatches) return bMatches - aMatches;
38
+ return b.data.publishedAt.valueOf() - a.data.publishedAt.valueOf();
39
+ })
40
+ .slice(0, maxPosts);
41
+
42
+ // Generate URLs for each post (locale-aware)
43
+ const getPostUrl = (postId: string) => {
44
+ const slug = postId.replace(`${locale}/`, '');
45
+ // Default locale doesn't have prefix
46
+ if (locale === defaultLocale) {
47
+ return `/blog/${slug}`;
48
+ }
49
+ return `/${locale}/blog/${slug}`;
50
+ };
51
+ ---
52
+
53
+ {relatedPosts.length > 0 && (
54
+ <section class="border-t border-border pt-12 mt-12">
55
+ <h2 class="font-display text-2xl font-bold text-foreground mb-8">
56
+ {t('blog.relatedPosts')}
57
+ </h2>
58
+
59
+ <div class="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
60
+ {relatedPosts.map((post) => (
61
+ <BlogCard
62
+ title={post.data.title}
63
+ description={post.data.description}
64
+ href={getPostUrl(post.id)}
65
+ publishedAt={post.data.publishedAt}
66
+ tags={post.data.tags}
67
+ author={post.data.author}
68
+ image={post.data.image}
69
+ />
70
+ ))}
71
+ </div>
72
+ </section>
73
+ )}
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: "Welcome to Velocity"
3
+ description: "Get started with Velocity, the modern Astro boilerplate with everything you need to build fast, beautiful websites."
4
+ publishedAt: 2026-01-28
5
+ author: "Southwell Media"
6
+ tags: ["astro", "tutorial", "getting-started"]
7
+ featured: true
8
+ locale: en
9
+ ---
10
+
11
+ # Welcome to Velocity
12
+
13
+ Velocity is a production-ready starter template built on **Astro 6** and **Tailwind CSS v4**. It's designed to help you build fast, beautiful, and accessible websites with minimal setup time.
14
+
15
+ ## Features
16
+
17
+ - 🚀 **Astro 6** - The latest version with all stable features
18
+ - 🎨 **Tailwind CSS v4** - CSS-first design token system
19
+ - 🌍 **Internationalization** - Built-in i18n with 3 languages
20
+ - 🔍 **SEO Optimized** - Complete meta tags, OG images, and structured data
21
+ - 🌙 **Dark Mode** - Automatic theme detection and toggle
22
+ - ♿ **Accessible** - WCAG 2.2 AA compliant components
23
+
24
+ ## Getting Started
25
+
26
+ 1. Clone the repository
27
+ 2. Install dependencies with `pnpm install`
28
+ 3. Start the dev server with `pnpm dev`
29
+
30
+ That's it! You're ready to start building.
31
+
32
+ ## Customization
33
+
34
+ Velocity is designed to be easily customizable. Start by editing:
35
+
36
+ - `src/config/site.config.ts` - Site metadata
37
+ - `src/styles/tokens/colors.css` - Brand colors
38
+ - `src/i18n/translations/` - Translations
39
+
40
+ Happy building! 🎉
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: "Bienvenido a Velocity"
3
+ description: "Comienza con Velocity, el boilerplate moderno de Astro con todo lo que necesitas para construir sitios web rápidos y hermosos."
4
+ publishedAt: 2026-01-28
5
+ author: "Southwell Media"
6
+ tags: ["astro", "tutorial", "inicio"]
7
+ featured: true
8
+ locale: es
9
+ ---
10
+
11
+ # Bienvenido a Velocity
12
+
13
+ Velocity es una plantilla lista para producción construida con **Astro 6** y **Tailwind CSS v4**. Está diseñada para ayudarte a construir sitios web rápidos, hermosos y accesibles con un tiempo de configuración mínimo.
14
+
15
+ ## Características
16
+
17
+ - 🚀 **Astro 6** - La última versión con todas las características estables
18
+ - 🎨 **Tailwind CSS v4** - Sistema de tokens de diseño CSS-first
19
+ - 🌍 **Internacionalización** - i18n integrado con 3 idiomas
20
+ - 🔍 **Optimizado para SEO** - Meta tags completos, imágenes OG y datos estructurados
21
+ - 🌙 **Modo Oscuro** - Detección automática de tema y toggle
22
+ - ♿ **Accesible** - Componentes conformes a WCAG 2.2 AA
23
+
24
+ ## Comenzando
25
+
26
+ 1. Clona el repositorio
27
+ 2. Instala las dependencias con `pnpm install`
28
+ 3. Inicia el servidor de desarrollo con `pnpm dev`
29
+
30
+ ¡Eso es todo! Estás listo para comenzar a construir.
31
+
32
+ ## Personalización
33
+
34
+ Velocity está diseñado para ser fácilmente personalizable. Comienza editando:
35
+
36
+ - `src/config/site.config.ts` - Metadatos del sitio
37
+ - `src/styles/tokens/colors.css` - Colores de marca
38
+ - `src/i18n/translations/` - Traducciones
39
+
40
+ ¡Feliz construcción! 🎉
@@ -0,0 +1,40 @@
1
+ ---
2
+ title: "Bienvenue sur Velocity"
3
+ description: "Commencez avec Velocity, le boilerplate Astro moderne avec tout ce dont vous avez besoin pour créer des sites web rapides et magnifiques."
4
+ publishedAt: 2026-01-28
5
+ author: "Southwell Media"
6
+ tags: ["astro", "tutoriel", "démarrage"]
7
+ featured: true
8
+ locale: fr
9
+ ---
10
+
11
+ # Bienvenue sur Velocity
12
+
13
+ Velocity est un template prêt pour la production construit avec **Astro 6** et **Tailwind CSS v4**. Il est conçu pour vous aider à créer des sites web rapides, beaux et accessibles avec un temps de configuration minimal.
14
+
15
+ ## Fonctionnalités
16
+
17
+ - 🚀 **Astro 6** - La dernière version avec toutes les fonctionnalités stables
18
+ - 🎨 **Tailwind CSS v4** - Système de tokens de design CSS-first
19
+ - 🌍 **Internationalisation** - i18n intégré avec 3 langues
20
+ - 🔍 **Optimisé pour le SEO** - Meta tags complets, images OG et données structurées
21
+ - 🌙 **Mode Sombre** - Détection automatique du thème et basculement
22
+ - ♿ **Accessible** - Composants conformes à WCAG 2.2 AA
23
+
24
+ ## Premiers pas
25
+
26
+ 1. Clonez le dépôt
27
+ 2. Installez les dépendances avec `pnpm install`
28
+ 3. Démarrez le serveur de développement avec `pnpm dev`
29
+
30
+ C'est tout ! Vous êtes prêt à commencer à construire.
31
+
32
+ ## Personnalisation
33
+
34
+ Velocity est conçu pour être facilement personnalisable. Commencez par éditer :
35
+
36
+ - `src/config/site.config.ts` - Métadonnées du site
37
+ - `src/styles/tokens/colors.css` - Couleurs de marque
38
+ - `src/i18n/translations/` - Traductions
39
+
40
+ Bonne construction ! 🎉
@@ -48,6 +48,21 @@ export const en = {
48
48
  success: 'Message sent successfully!',
49
49
  error: 'Failed to send message. Please try again.',
50
50
  },
51
+
52
+ // Blog
53
+ blog: {
54
+ title: 'Blog',
55
+ description: 'Latest articles and updates',
56
+ allPosts: 'All posts',
57
+ featured: 'Featured',
58
+ noPosts: 'No posts found',
59
+ relatedPosts: 'Related Posts',
60
+ backToBlog: 'Back to Blog',
61
+ subscribe: 'Subscribe',
62
+ subscribeDescription: 'Get the latest articles and updates delivered to your inbox.',
63
+ emailPlaceholder: 'Enter your email',
64
+ subscribeButton: 'Subscribe',
65
+ },
51
66
  } as const;
52
67
 
53
68
  export type TranslationKeys = typeof en;
@@ -50,4 +50,19 @@ export const es: TranslationKeys = {
50
50
  success: '¡Mensaje enviado con éxito!',
51
51
  error: 'Error al enviar el mensaje. Por favor, inténtalo de nuevo.',
52
52
  },
53
+
54
+ // Blog
55
+ blog: {
56
+ title: 'Blog',
57
+ description: 'Últimos artículos y actualizaciones',
58
+ allPosts: 'Todos los artículos',
59
+ featured: 'Destacados',
60
+ noPosts: 'No se encontraron artículos',
61
+ relatedPosts: 'Artículos relacionados',
62
+ backToBlog: 'Volver al Blog',
63
+ subscribe: 'Suscríbete',
64
+ subscribeDescription: 'Recibe los últimos artículos y actualizaciones en tu correo.',
65
+ emailPlaceholder: 'Introduce tu correo',
66
+ subscribeButton: 'Suscribirse',
67
+ },
53
68
  } as const;
@@ -50,4 +50,19 @@ export const fr: TranslationKeys = {
50
50
  success: 'Message envoyé avec succès !',
51
51
  error: "Échec de l'envoi du message. Veuillez réessayer.",
52
52
  },
53
+
54
+ // Blog
55
+ blog: {
56
+ title: 'Blog',
57
+ description: 'Derniers articles et mises à jour',
58
+ allPosts: 'Tous les articles',
59
+ featured: 'À la une',
60
+ noPosts: 'Aucun article trouvé',
61
+ relatedPosts: 'Articles connexes',
62
+ backToBlog: 'Retour au Blog',
63
+ subscribe: "S'abonner",
64
+ subscribeDescription: 'Recevez les derniers articles et mises à jour dans votre boîte mail.',
65
+ emailPlaceholder: 'Entrez votre e-mail',
66
+ subscribeButton: "S'abonner",
67
+ },
53
68
  } as const;
@@ -0,0 +1,133 @@
1
+ ---
2
+ import BaseLayout from './BaseLayout.astro';
3
+ import Navbar from '@/components/landing/Navbar.astro';
4
+ import LandingFooter from '@/components/landing/LandingFooter.astro';
5
+ import Breadcrumbs from '@/components/seo/Breadcrumbs.astro';
6
+ import ArticleHero from '@/components/blog/ArticleHero.astro';
7
+ import ShareButtons from '@/components/blog/ShareButtons.astro';
8
+ import RelatedPosts from '@/components/blog/RelatedPosts.astro';
9
+ import { defaultLocale, type Locale } from '@/i18n/config';
10
+ import { useTranslations } from '@/i18n/index';
11
+
12
+ interface Props {
13
+ title: string;
14
+ description: string;
15
+ publishedAt: Date;
16
+ updatedAt?: Date;
17
+ author?: string;
18
+ image?: string;
19
+ imageAlt?: string;
20
+ tags?: string[];
21
+ slug?: string;
22
+ locale?: Locale;
23
+ }
24
+
25
+ const {
26
+ title,
27
+ description,
28
+ publishedAt,
29
+ updatedAt,
30
+ author = 'Team',
31
+ image,
32
+ imageAlt,
33
+ tags = [],
34
+ slug = '',
35
+ locale = defaultLocale,
36
+ } = Astro.props;
37
+
38
+ const t = useTranslations(locale);
39
+ const ogImage = image || `/og${Astro.url.pathname}.png`;
40
+
41
+ // Build breadcrumbs with locale-aware URLs
42
+ const blogUrl = locale === defaultLocale ? '/blog' : `/${locale}/blog`;
43
+ const homeUrl = locale === defaultLocale ? '/' : `/${locale}`;
44
+
45
+ const breadcrumbs = [
46
+ { label: t('nav.home'), href: homeUrl },
47
+ { label: t('nav.blog'), href: blogUrl },
48
+ { label: title },
49
+ ];
50
+
51
+ // Get full URL for sharing
52
+ const fullUrl = new URL(Astro.url.pathname, Astro.site).toString();
53
+ ---
54
+
55
+ <BaseLayout
56
+ title={title}
57
+ description={description}
58
+ image={ogImage}
59
+ imageAlt={imageAlt || title}
60
+ lang={locale}
61
+ article={{
62
+ publishedTime: publishedAt,
63
+ modifiedTime: updatedAt,
64
+ authors: [author],
65
+ tags,
66
+ }}
67
+ >
68
+ <Navbar slot="header" />
69
+
70
+ <!-- Article Hero -->
71
+ <ArticleHero
72
+ title={title}
73
+ description={description}
74
+ publishedAt={publishedAt}
75
+ updatedAt={updatedAt}
76
+ author={author}
77
+ tags={tags}
78
+ image={image}
79
+ imageAlt={imageAlt}
80
+ />
81
+
82
+ <article class={`py-12 ${image ? 'pt-24' : ''}`}>
83
+ <div class="mx-auto max-w-4xl px-6">
84
+ <!-- Breadcrumbs -->
85
+ <Breadcrumbs items={breadcrumbs} class="mb-8" />
86
+
87
+ <!-- Article Content -->
88
+ <div class="prose prose-lg max-w-none prose-headings:font-display prose-headings:font-bold prose-a:text-brand-600 hover:prose-a:text-brand-500 prose-code:text-brand-600 prose-code:bg-brand-50 prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-pre:bg-background-secondary prose-pre:border prose-pre:border-border dark:prose-invert dark:prose-a:text-brand-400 dark:hover:prose-a:text-brand-300 dark:prose-code:text-brand-400 dark:prose-code:bg-brand-900/30">
89
+ <slot />
90
+ </div>
91
+
92
+ <!-- Article Footer -->
93
+ <footer class="mt-12 pt-8 border-t border-border">
94
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-6">
95
+ <!-- Back link -->
96
+ <a
97
+ href={blogUrl}
98
+ class="inline-flex items-center gap-2 text-sm font-medium text-brand-600 hover:text-brand-500 dark:text-brand-400 dark:hover:text-brand-300 transition-colors"
99
+ >
100
+ <svg
101
+ class="h-4 w-4"
102
+ xmlns="http://www.w3.org/2000/svg"
103
+ fill="none"
104
+ viewBox="0 0 24 24"
105
+ stroke="currentColor"
106
+ stroke-width="2"
107
+ >
108
+ <path stroke-linecap="round" stroke-linejoin="round" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
109
+ </svg>
110
+ {t('blog.backToBlog')}
111
+ </a>
112
+
113
+ <!-- Share buttons -->
114
+ <ShareButtons title={title} url={fullUrl} />
115
+ </div>
116
+ </footer>
117
+ </div>
118
+ </article>
119
+
120
+ <!-- Related Posts -->
121
+ {tags.length > 0 && (
122
+ <div class="mx-auto max-w-6xl px-6 pb-16">
123
+ <RelatedPosts
124
+ currentSlug={slug}
125
+ tags={tags}
126
+ locale={locale}
127
+ maxPosts={3}
128
+ />
129
+ </div>
130
+ )}
131
+
132
+ <LandingFooter slot="footer" />
133
+ </BaseLayout>
@@ -0,0 +1,55 @@
1
+ ---
2
+ import BlogLayout from '@/layouts/BlogLayout.astro';
3
+ import { getCollection, render } from 'astro:content';
4
+ import { locales, defaultLocale, isValidLocale, type Locale } from '@/i18n/config';
5
+
6
+ export async function getStaticPaths() {
7
+ const paths: { params: { lang: string; slug: string }; props: { post: any } }[] = [];
8
+
9
+ // Get all posts for non-default locales
10
+ for (const locale of locales) {
11
+ if (locale === defaultLocale) continue; // Skip default locale (handled by /blog/[...slug].astro)
12
+
13
+ const posts = await getCollection('blog', ({ data }) => {
14
+ return data.locale === locale && (import.meta.env.PROD ? data.draft !== true : true);
15
+ });
16
+
17
+ for (const post of posts) {
18
+ paths.push({
19
+ params: {
20
+ lang: locale,
21
+ slug: post.id.replace(`${locale}/`, ''),
22
+ },
23
+ props: { post },
24
+ });
25
+ }
26
+ }
27
+
28
+ return paths;
29
+ }
30
+
31
+ const { lang } = Astro.params;
32
+ const { post } = Astro.props;
33
+
34
+ if (!lang || !isValidLocale(lang)) {
35
+ return Astro.redirect('/blog');
36
+ }
37
+
38
+ const locale = lang as Locale;
39
+ const { Content } = await render(post);
40
+ ---
41
+
42
+ <BlogLayout
43
+ title={post.data.title}
44
+ description={post.data.description}
45
+ publishedAt={post.data.publishedAt}
46
+ updatedAt={post.data.updatedAt}
47
+ author={post.data.author}
48
+ image={post.data.image}
49
+ imageAlt={post.data.imageAlt}
50
+ tags={post.data.tags}
51
+ slug={post.id}
52
+ locale={locale}
53
+ >
54
+ <Content />
55
+ </BlogLayout>