Don't use tabs in editor

This commit is contained in:
prototypa
2023-01-09 21:44:06 -05:00
parent 58484dfca8
commit f641767078
56 changed files with 2217 additions and 2203 deletions

View File

@ -4,13 +4,5 @@
"singleQuote": true, "singleQuote": true,
"tabWidth": 2, "tabWidth": 2,
"trailingComma": "es5", "trailingComma": "es5",
"useTabs": true, "useTabs": false
"overrides": [
{
"files": [".*", "*.json", "*.md", "*.toml", "*.yml"],
"options": {
"useTabs": false
}
}
]
} }

View File

@ -157,7 +157,8 @@ const CONFIG = {
language: 'en', // Default language language: 'en', // Default language
textDirection: 'ltr', // Default html text direction textDirection: 'ltr', // Default html text direction
dateFormatter: new Intl.DateTimeFormat('en', { // Date format dateFormatter: new Intl.DateTimeFormat('en', {
// Date format
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',

View File

@ -16,58 +16,58 @@ import { SITE } from './src/config.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url)); const __dirname = path.dirname(fileURLToPath(import.meta.url));
const whenExternalScripts = (items = []) => const whenExternalScripts = (items = []) =>
SITE.googleAnalyticsId ? (Array.isArray(items) ? items.map((item) => item()) : [items()]) : []; SITE.googleAnalyticsId ? (Array.isArray(items) ? items.map((item) => item()) : [items()]) : [];
export default defineConfig({ export default defineConfig({
site: SITE.origin, site: SITE.origin,
base: SITE.basePathname, base: SITE.basePathname,
trailingSlash: SITE.trailingSlash ? 'always' : 'never', trailingSlash: SITE.trailingSlash ? 'always' : 'never',
output: 'static', output: 'static',
integrations: [ integrations: [
tailwind({ tailwind({
config: { config: {
applyBaseStyles: false, applyBaseStyles: false,
}, },
}), }),
sitemap(), sitemap(),
image({ image({
serviceEntryPoint: '@astrojs/image/sharp', serviceEntryPoint: '@astrojs/image/sharp',
}), }),
mdx(), mdx(),
...whenExternalScripts(() => ...whenExternalScripts(() =>
partytown({ partytown({
config: { forward: ['dataLayer.push'] }, config: { forward: ['dataLayer.push'] },
}) })
), ),
compress({ compress({
css: true, css: true,
html: true, html: true,
img: false, img: false,
js: true, js: true,
svg: false, svg: false,
logger: 1, logger: 1,
}), }),
], ],
markdown: { markdown: {
remarkPlugins: [remarkReadingTime], remarkPlugins: [remarkReadingTime],
extendDefaultPlugins: true, extendDefaultPlugins: true,
}, },
vite: { vite: {
resolve: { resolve: {
alias: { alias: {
'~': path.resolve(__dirname, './src'), '~': path.resolve(__dirname, './src'),
}, },
}, },
}, },
experimental: { experimental: {
contentCollections: true, contentCollections: true,
}, },
}); });

View File

@ -3,42 +3,42 @@
@tailwind utilities; @tailwind utilities;
@layer components { @layer components {
.btn { .btn {
@apply inline-flex items-center justify-center rounded-md shadow-md border-gray-400 border bg-transparent font-medium text-center text-base text-gray-700 leading-snug transition py-3.5 px-6 md:px-7 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800; @apply inline-flex items-center justify-center rounded-md shadow-md border-gray-400 border bg-transparent font-medium text-center text-base text-gray-700 leading-snug transition py-3.5 px-6 md:px-7 ease-in duration-200 focus:ring-blue-500 focus:ring-offset-blue-200 focus:ring-2 focus:ring-offset-2 hover:bg-gray-100 hover:border-gray-600 dark:text-slate-300 dark:border-slate-500 dark:hover:bg-slate-800 dark:hover:border-slate-800;
} }
.btn-ghost { .btn-ghost {
@apply border-none shadow-none text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white; @apply border-none shadow-none text-gray-600 hover:text-gray-900 dark:text-gray-400 dark:hover:text-white;
} }
.btn-primary { .btn-primary {
@apply font-semibold bg-primary-800 text-white border-primary-800 hover:bg-primary-900 hover:border-primary-900 hover:text-white dark:text-white dark:bg-primary-700 dark:border-primary-700 dark:hover:border-primary-900 dark:hover:bg-primary-900; @apply font-semibold bg-primary-800 text-white border-primary-800 hover:bg-primary-900 hover:border-primary-900 hover:text-white dark:text-white dark:bg-primary-700 dark:border-primary-700 dark:hover:border-primary-900 dark:hover:bg-primary-900;
} }
} }
#header.scroll { #header.scroll {
@apply shadow-md md:shadow-lg bg-white md:bg-white/90 md:backdrop-blur-sm dark:bg-slate-900 dark:md:bg-slate-900/90; @apply shadow-md md:shadow-lg bg-white md:bg-white/90 md:backdrop-blur-sm dark:bg-slate-900 dark:md:bg-slate-900/90;
} }
.dropdown:hover .dropdown-menu { .dropdown:hover .dropdown-menu {
display: block; display: block;
} }
[astro-icon].icon-light > * { [astro-icon].icon-light > * {
stroke-width: 1.2; stroke-width: 1.2;
} }
[astro-icon].icon-bold > * { [astro-icon].icon-bold > * {
stroke-width: 2.4; stroke-width: 2.4;
} }
[data-aw-toggle-menu] path { [data-aw-toggle-menu] path {
@apply transition; @apply transition;
} }
[data-aw-toggle-menu].expanded g > path:first-child { [data-aw-toggle-menu].expanded g > path:first-child {
@apply -rotate-45 translate-y-[15px] translate-x-[-3px]; @apply -rotate-45 translate-y-[15px] translate-x-[-3px];
} }
[data-aw-toggle-menu].expanded g > path:last-child { [data-aw-toggle-menu].expanded g > path:last-child {
@apply rotate-45 translate-y-[-8px] translate-x-[14px]; @apply rotate-45 translate-y-[-8px] translate-x-[14px];
} }

View File

@ -3,12 +3,12 @@ import Item from '~/components/blog/GridItem.astro';
import type { Post } from '~/types'; import type { Post } from '~/types';
export interface Props { export interface Props {
posts: Array<Post>; posts: Array<Post>;
} }
const { posts } = Astro.props; const { posts } = Astro.props;
--- ---
<div class="grid gap-6 row-gap-5 md:grid-cols-2 lg:grid-cols-4 -mb-6"> <div class="grid gap-6 row-gap-5 md:grid-cols-2 lg:grid-cols-4 -mb-6">
{posts.map((post) => <Item post={post} />)} {posts.map((post) => <Item post={post} />)}
</div> </div>

View File

@ -8,7 +8,7 @@ import { findImage } from '~/utils/images';
import { getPermalink } from '~/utils/permalinks'; import { getPermalink } from '~/utils/permalinks';
export interface Props { export interface Props {
post: Post; post: Post;
} }
const { post } = Astro.props; const { post } = Astro.props;
@ -16,33 +16,33 @@ const image = await findImage(post.image);
--- ---
<article class="mb-6 transition"> <article class="mb-6 transition">
<div class="relative h-0 pb-[56.25%] lg:h-64 overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg mb-6"> <div class="relative h-0 pb-[56.25%] lg:h-64 overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg mb-6">
{ {
image && ( image && (
<Picture <Picture
src={image} src={image}
class="object-cover w-full lg:h-64 rounded shadow-lg bg-gray-400 dark:bg-slate-700" class="object-cover w-full lg:h-64 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]} widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px" sizes="(max-width: 900px) 400px, 900px"
alt={post.title} alt={post.title}
aspectRatio="16:9" aspectRatio="16:9"
/> />
) )
} }
</div> </div>
<h3 class="mb-2 text-xl font-bold leading-tight sm:text-2xl font-heading"> <h3 class="mb-2 text-xl font-bold leading-tight sm:text-2xl font-heading">
{ {
BLOG?.post?.disabled ? ( BLOG?.post?.disabled ? (
post.title post.title
) : ( ) : (
<a <a
href={getPermalink(post.slug, 'post')} href={getPermalink(post.slug, 'post')}
class="hover:text-primary-800 dark:hover:text-primary-700 transition ease-in duration-200" class="hover:text-primary-800 dark:hover:text-primary-700 transition ease-in duration-200"
> >
{post.title} {post.title}
</a> </a>
) )
} }
</h3> </h3>
<p class="text-gray-500 dark:text-slate-400 text-lg">{post.excerpt || post.description}</p> <p class="text-gray-500 dark:text-slate-400 text-lg">{post.excerpt || post.description}</p>
</article> </article>

View File

@ -3,8 +3,12 @@ const { title = await Astro.slots.render('default'), subtitle = await Astro.slot
--- ---
<header class="mb-8 md:mb-16 text-center max-w-3xl mx-auto"> <header class="mb-8 md:mb-16 text-center max-w-3xl mx-auto">
<h1 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading"> <h1 class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading">
{title} {title}
</h1> </h1>
{subtitle && <div class="mt-2 md:mt-3 mx-auto text-xl text-gray-500 dark:text-slate-400 font-medium" set:html={subtitle} />} {
subtitle && (
<div class="mt-2 md:mt-3 mx-auto text-xl text-gray-500 dark:text-slate-400 font-medium" set:html={subtitle} />
)
}
</header> </header>

View File

@ -5,49 +5,49 @@ import { getBlogPermalink } from '~/utils/permalinks';
import { findPostsByIds } from '~/utils/blog'; import { findPostsByIds } from '~/utils/blog';
export interface Props { export interface Props {
title?: string; title?: string;
allPostsText?: string; allPostsText?: string;
allPostsLink?: string | URL; allPostsLink?: string | URL;
information?: string; information?: string;
postIds: string[]; postIds: string[];
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
allPostsText = 'View all posts', allPostsText = 'View all posts',
allPostsLink = getBlogPermalink(), allPostsLink = getBlogPermalink(),
information = await Astro.slots.render('information'), information = await Astro.slots.render('information'),
postIds = [], postIds = [],
} = Astro.props; } = Astro.props;
const posts = await findPostsByIds(postIds); const posts = await findPostsByIds(postIds);
--- ---
<section class="px-4 py-16 mx-auto max-w-6xl lg:py-20"> <section class="px-4 py-16 mx-auto max-w-6xl lg:py-20">
<div class="flex flex-col lg:justify-between lg:flex-row mb-8"> <div class="flex flex-col lg:justify-between lg:flex-row mb-8">
<div class="md:max-w-sm"> <div class="md:max-w-sm">
{ {
title && ( title && (
<h2 <h2
class="text-3xl font-bold tracking-tight sm:text-4xl sm:leading-none group font-heading mb-2" class="text-3xl font-bold tracking-tight sm:text-4xl sm:leading-none group font-heading mb-2"
set:html={title} set:html={title}
/> />
) )
} }
{ {
allPostsText && allPostsLink && ( allPostsText && allPostsLink && (
<a <a
class="text-gray-500 dark:text-slate-400 hover:text-primary-800 transition ease-in duration-200 block mb-6 md:mb-0" class="text-gray-500 dark:text-slate-400 hover:text-primary-800 transition ease-in duration-200 block mb-6 md:mb-0"
href={allPostsLink} href={allPostsLink}
> >
{allPostsText} » {allPostsText} »
</a> </a>
) )
} }
</div> </div>
{information && <p class="text-gray-700 dark:text-slate-400 lg:text-sm lg:max-w-md" set:html={information} />} {information && <p class="text-gray-700 dark:text-slate-400 lg:text-sm lg:max-w-md" set:html={information} />}
</div> </div>
<Grid posts={posts} /> <Grid posts={posts} />
</section> </section>

View File

@ -9,22 +9,22 @@ const posts = await findLatestPosts({ count });
--- ---
<section class="px-4 py-16 mx-auto max-w-6xl lg:py-20"> <section class="px-4 py-16 mx-auto max-w-6xl lg:py-20">
<div class="flex flex-col mb-6 lg:justify-between lg:flex-row md:mb-8"> <div class="flex flex-col mb-6 lg:justify-between lg:flex-row md:mb-8">
<h2 class="max-w-lg mb-2 text-3xl font-bold tracking-tight sm:text-4xl sm:leading-none lg:mb-5 group font-heading"> <h2 class="max-w-lg mb-2 text-3xl font-bold tracking-tight sm:text-4xl sm:leading-none lg:mb-5 group font-heading">
<span class="inline-block mb-1 sm:mb-4" <span class="inline-block mb-1 sm:mb-4"
>Latest articles<br class="hidden md:block" /> in our <a >Latest articles<br class="hidden md:block" /> in our <a
class="hover:text-primary-800 underline underline-offset-4 decoration-1 decoration-dotted transition ease-in duration-200" class="hover:text-primary-800 underline underline-offset-4 decoration-1 decoration-dotted transition ease-in duration-200"
href={getBlogPermalink()}>Blog</a href={getBlogPermalink()}>Blog</a
> >
</span> </span>
</h2> </h2>
<p class="text-gray-700 dark:text-slate-400 lg:text-sm lg:max-w-md"> <p class="text-gray-700 dark:text-slate-400 lg:text-sm lg:max-w-md">
The blog will be used to display AstroWind documentation. Each new article will be an important step that you will The blog will be used to display AstroWind documentation. Each new article will be an important step that you will
need to know to be an expert in creating a website using Astro + Tailwind CSS The blog does not exist yet, but need to know to be an expert in creating a website using Astro + Tailwind CSS The blog does not exist yet, but
very soon. Astro is a very interesting technology. Thanks. very soon. Astro is a very interesting technology. Thanks.
</p> </p>
</div> </div>
<Grid posts={posts} /> <Grid posts={posts} />
</section> </section>

View File

@ -3,18 +3,18 @@ import Item from '~/components/blog/ListItem.astro';
import type { Post } from '~/types'; import type { Post } from '~/types';
export interface Props { export interface Props {
posts: Array<Post>; posts: Array<Post>;
} }
const { posts } = Astro.props; const { posts } = Astro.props;
--- ---
<ul> <ul>
{ {
posts.map((post) => ( posts.map((post) => (
<li class="mb-12 md:mb-20"> <li class="mb-12 md:mb-20">
<Item post={post} /> <Item post={post} />
</li> </li>
)) ))
} }
</ul> </ul>

View File

@ -11,7 +11,7 @@ import { findImage } from '~/utils/images';
import { getFormattedDate } from '~/utils/utils'; import { getFormattedDate } from '~/utils/utils';
export interface Props { export interface Props {
post: Post; post: Post;
} }
const { post } = Astro.props; const { post } = Astro.props;
@ -21,53 +21,49 @@ const link = !BLOG?.post?.disabled ? getPermalink(post.slug, 'post') : '';
--- ---
<article class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 ${image ? 'md:grid-cols-2' : ''}`}> <article class={`max-w-md mx-auto md:max-w-none grid gap-6 md:gap-8 ${image ? 'md:grid-cols-2' : ''}`}>
{ {
image && ( image && (
<a class="relative block group" href={link ?? 'javascript:void(0)'}> <a class="relative block group" href={link ?? 'javascript:void(0)'}>
<div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg"> <div class="relative h-0 pb-[56.25%] md:pb-[75%] md:h-72 lg:pb-[56.25%] overflow-hidden bg-gray-400 dark:bg-slate-700 rounded shadow-lg">
{image && ( {image && (
<Picture <Picture
src={image} src={image}
class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700" class="absolute inset-0 object-cover w-full h-full mb-6 rounded shadow-lg bg-gray-400 dark:bg-slate-700"
widths={[400, 900]} widths={[400, 900]}
sizes="(max-width: 900px) 400px, 900px" sizes="(max-width: 900px) 400px, 900px"
alt={post.title} alt={post.title}
aspectRatio="16:9" aspectRatio="16:9"
/> />
)} )}
</div> </div>
</a> </a>
) )
} }
<div class="mt-2"> <div class="mt-2">
<header> <header>
<div class="mb-1"> <div class="mb-1">
<span class="text-sm"> <span class="text-sm">
<Icon name="tabler:clock" class="w-3.5 h-3.5 inline-block -mt-0.5 text-gray-500 dark:text-gray-400" /> <Icon name="tabler:clock" class="w-3.5 h-3.5 inline-block -mt-0.5 text-gray-500 dark:text-gray-400" />
<time datetime={String(post.publishDate)}>{getFormattedDate(post.publishDate)}</time> ~ <time datetime={String(post.publishDate)}>{getFormattedDate(post.publishDate)}</time> ~
{Math.ceil(post.readingTime)} min read {Math.ceil(post.readingTime)} min read
</span> </span>
</div> </div>
<h2 class="text-xl sm:text-2xl font-bold leading-tight mb-2 font-heading text-gray-700 dark:text-slate-300"> <h2 class="text-xl sm:text-2xl font-bold leading-tight mb-2 font-heading text-gray-700 dark:text-slate-300">
{ {
link ? ( link ? (
<a <a class="hover:text-primary-800 dark:hover:text-primary-700 transition ease-in duration-200" href={link}>
class="hover:text-primary-800 dark:hover:text-primary-700 transition ease-in duration-200" {post.title}
href={link} </a>
> ) : (
{post.title} post.title
</a> )
) : ( }
post.title </h2>
) </header>
}
</h2>
</header>
{post.excerpt && <p class="flex-grow text-gray-500 dark:text-slate-400 text-lg">{post.excerpt}</p>} {post.excerpt && <p class="flex-grow text-gray-500 dark:text-slate-400 text-lg">{post.excerpt}</p>}
<footer class="mt-5"> <footer class="mt-5">
<PostTags tags={post.tags} /> <PostTags tags={post.tags} />
</footer>
</footer> </div>
</div>
</article> </article>

View File

@ -10,70 +10,72 @@ import { getFormattedDate } from '~/utils/utils';
import type { Post } from '~/types'; import type { Post } from '~/types';
export interface Props { export interface Props {
post: Post; post: Post;
url: string | URL; url: string | URL;
} }
const { post, url } = Astro.props; const { post, url } = Astro.props;
--- ---
<section class="py-8 sm:py-16 lg:py-20 mx-auto"> <section class="py-8 sm:py-16 lg:py-20 mx-auto">
<article> <article>
<header class={post.image ? '' : ''}> <header class={post.image ? '' : ''}>
<div class="flex justify-between flex-col sm:flex-row max-w-3xl mx-auto mt-0 mb-2 px-4 sm:px-6 sm:items-center"> <div class="flex justify-between flex-col sm:flex-row max-w-3xl mx-auto mt-0 mb-2 px-4 sm:px-6 sm:items-center">
<p> <p>
<Icon name="tabler:clock" class="w-4 h-4 inline-block -mt-1 text-gray-500 dark:text-gray-400" /> <Icon name="tabler:clock" class="w-4 h-4 inline-block -mt-1 text-gray-500 dark:text-gray-400" />
<time datetime={String(post.publishDate)}>{getFormattedDate(post.publishDate)}</time> ~ { <time datetime={String(post.publishDate)}>{getFormattedDate(post.publishDate)}</time> ~ {
Math.ceil(post.readingTime) Math.ceil(post.readingTime)
} min read } min read
</p> </p>
</div> </div>
<h1 <h1
class="px-4 sm:px-6 max-w-3xl mx-auto text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading" class="px-4 sm:px-6 max-w-3xl mx-auto text-4xl md:text-5xl font-bold leading-tighter tracking-tighter font-heading"
> >
{post.title} {post.title}
</h1> </h1>
<p class="max-w-3xl mx-auto mt-4 mb-8 px-4 sm:px-6 text-xl md:text-2xl text-gray-500 dark:text-slate-400 font-medium text-justify"> <p
{post.excerpt} class="max-w-3xl mx-auto mt-4 mb-8 px-4 sm:px-6 text-xl md:text-2xl text-gray-500 dark:text-slate-400 font-medium text-justify"
</p> >
{post.excerpt}
{ </p>
post.image ? (
<Picture {
src={post.image} post.image ? (
class="max-w-full lg:max-w-6xl mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700" <Picture
widths={[400, 900]} src={post.image}
sizes="(max-width: 900px) 400px, 900px" class="max-w-full lg:max-w-6xl mx-auto mb-6 sm:rounded-md bg-gray-400 dark:bg-slate-700"
alt={post.description || ''} widths={[400, 900]}
loading="eager" sizes="(max-width: 900px) 400px, 900px"
aspectRatio={16 / 9} alt={post.description || ''}
width={900} loading="eager"
height={506} aspectRatio={16 / 9}
/> width={900}
) : ( height={506}
<div class="max-w-3xl mx-auto px-4 sm:px-6"> />
<div class="border-t dark:border-slate-700" /> ) : (
</div> <div class="max-w-3xl mx-auto px-4 sm:px-6">
) <div class="border-t dark:border-slate-700" />
} </div>
</header> )
<div }
class="mx-auto px-6 sm:px-6 max-w-3xl prose prose-lg lg:prose-xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary-800 dark:prose-a:text-primary-400 prose-img:rounded-md prose-img:shadow-lg mt-8" </header>
> <div
{ class="mx-auto px-6 sm:px-6 max-w-3xl prose prose-lg lg:prose-xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary-800 dark:prose-a:text-primary-400 prose-img:rounded-md prose-img:shadow-lg mt-8"
post.Content ? ( >
<> {
{/* @ts-ignore */} post.Content ? (
<post.Content /> <>
</> {/* @ts-ignore */}
) : ( <post.Content />
<Fragment set:html={post.content} /> </>
) ) : (
} <Fragment set:html={post.content} />
</div> )
<div class="mx-auto px-6 sm:px-6 max-w-3xl mt-8 flex justify-between flex-col sm:flex-row"> }
<PostTags tags={post.tags} class="mr-5" /> </div>
<SocialShare url={url} text={post.title} class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600" /> <div class="mx-auto px-6 sm:px-6 max-w-3xl mt-8 flex justify-between flex-col sm:flex-row">
</div> <PostTags tags={post.tags} class="mr-5" />
</article> <SocialShare url={url} text={post.title} class="mt-5 sm:mt-1 align-middle text-gray-500 dark:text-slate-600" />
</div>
</article>
</section> </section>

View File

@ -4,7 +4,7 @@ import { getBlogPermalink } from '~/utils/permalinks';
--- ---
<div class="mx-auto px-6 sm:px-6 max-w-3xl pt-8 md:pt-4 pb-12 md:pb-20"> <div class="mx-auto px-6 sm:px-6 max-w-3xl pt-8 md:pt-4 pb-12 md:pb-20">
<a class="btn btn-ghost px-3 md:px-3" href={getBlogPermalink()} <a class="btn btn-ghost px-3 md:px-3" href={getBlogPermalink()}
><Icon name="tabler:chevron-left" class="w-5 h-5 mr-1 -ml-1.5" /> Back to Blog</a ><Icon name="tabler:chevron-left" class="w-5 h-5 mr-1 -ml-1.5" /> Back to Blog</a
> >
</div> </div>

View File

@ -3,117 +3,117 @@ import { SITE } from '~/config.mjs';
--- ---
<script is:inline define:vars={{ defaultTheme: SITE.defaultTheme }}> <script is:inline define:vars={{ defaultTheme: SITE.defaultTheme }}>
function applyTheme(theme) { function applyTheme(theme) {
if (theme === 'dark') { if (theme === 'dark') {
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
} else { } else {
document.documentElement.classList.remove('dark'); document.documentElement.classList.remove('dark');
} }
} }
if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) { if ((defaultTheme && defaultTheme.endsWith(':only')) || (!localStorage.theme && defaultTheme !== 'system')) {
applyTheme(defaultTheme.replace(':only', '')); applyTheme(defaultTheme.replace(':only', ''));
} else if ( } else if (
localStorage.theme === 'dark' || localStorage.theme === 'dark' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches) (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)
) { ) {
applyTheme('dark'); applyTheme('dark');
} else { } else {
applyTheme('light'); applyTheme('light');
} }
function attachEvent(selector, event, fn) { function attachEvent(selector, event, fn) {
const matches = typeof selector === 'string' ? document.querySelectorAll(selector) : selector; const matches = typeof selector === 'string' ? document.querySelectorAll(selector) : selector;
if (matches && matches.length) { if (matches && matches.length) {
matches.forEach((elem) => { matches.forEach((elem) => {
elem.addEventListener(event, (e) => fn(e, elem), false); elem.addEventListener(event, (e) => fn(e, elem), false);
}); });
} }
} }
window.onload = function () { window.onload = function () {
let lastKnownScrollPosition = window.scrollY; let lastKnownScrollPosition = window.scrollY;
let ticking = true; let ticking = true;
attachEvent('[data-aw-toggle-menu]', 'click', function (_, elem) { attachEvent('[data-aw-toggle-menu]', 'click', function (_, elem) {
elem.classList.toggle('expanded'); elem.classList.toggle('expanded');
document.body.classList.toggle('overflow-hidden'); document.body.classList.toggle('overflow-hidden');
document.getElementById('header')?.classList.toggle('h-screen'); document.getElementById('header')?.classList.toggle('h-screen');
document.querySelector('#header nav')?.classList.toggle('hidden'); document.querySelector('#header nav')?.classList.toggle('hidden');
}); });
attachEvent('[data-aw-toggle-color-scheme]', 'click', function () { attachEvent('[data-aw-toggle-color-scheme]', 'click', function () {
if (defaultTheme.endsWith(':only')) { if (defaultTheme.endsWith(':only')) {
return; return;
} }
document.documentElement.classList.toggle('dark'); document.documentElement.classList.toggle('dark');
localStorage.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light'; localStorage.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
}); });
attachEvent('[data-aw-social-share]', 'click', function (_, elem) { attachEvent('[data-aw-social-share]', 'click', function (_, elem) {
const network = elem.getAttribute('data-aw-social-share'); const network = elem.getAttribute('data-aw-social-share');
const url = encodeURIComponent(elem.getAttribute('data-aw-url')); const url = encodeURIComponent(elem.getAttribute('data-aw-url'));
const text = encodeURIComponent(elem.getAttribute('data-aw-text')); const text = encodeURIComponent(elem.getAttribute('data-aw-text'));
let href; let href;
switch (network) { switch (network) {
case 'facebook': case 'facebook':
href = `https://www.facebook.com/sharer.php?u=${url}`; href = `https://www.facebook.com/sharer.php?u=${url}`;
break; break;
case 'twitter': case 'twitter':
href = `https://twitter.com/intent/tweet?url=${url}&text=${text}`; href = `https://twitter.com/intent/tweet?url=${url}&text=${text}`;
break; break;
case 'linkedin': case 'linkedin':
href = `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${text}`; href = `https://www.linkedin.com/shareArticle?mini=true&url=${url}&title=${text}`;
break; break;
case 'whatsapp': case 'whatsapp':
href = `https://wa.me/?text=${text}%20${url}`; href = `https://wa.me/?text=${text}%20${url}`;
break; break;
case 'mail': case 'mail':
href = `mailto:?subject=%22${text}%22&body=${text}%20${url}`; href = `mailto:?subject=%22${text}%22&body=${text}%20${url}`;
break; break;
default: default:
return; return;
} }
const newlink = document.createElement('a'); const newlink = document.createElement('a');
newlink.target = '_blank'; newlink.target = '_blank';
newlink.href = href; newlink.href = href;
newlink.click(); newlink.click();
}); });
function appyHeaderStylesOnScroll() { function appyHeaderStylesOnScroll() {
const header = document.getElementById('header'); const header = document.getElementById('header');
if (lastKnownScrollPosition > 60 && !header.classList.contains('scroll')) { if (lastKnownScrollPosition > 60 && !header.classList.contains('scroll')) {
document.getElementById('header').classList.add('scroll'); document.getElementById('header').classList.add('scroll');
} else if (lastKnownScrollPosition <= 60 && header.classList.contains('scroll')) { } else if (lastKnownScrollPosition <= 60 && header.classList.contains('scroll')) {
document.getElementById('header').classList.remove('scroll'); document.getElementById('header').classList.remove('scroll');
} }
ticking = false; ticking = false;
} }
appyHeaderStylesOnScroll(); appyHeaderStylesOnScroll();
attachEvent([document], 'scroll', function () { attachEvent([document], 'scroll', function () {
lastKnownScrollPosition = window.scrollY; lastKnownScrollPosition = window.scrollY;
if (!ticking) { if (!ticking) {
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
appyHeaderStylesOnScroll(); appyHeaderStylesOnScroll();
}); });
ticking = true; ticking = true;
} }
}); });
}; };
window.onpageshow = function () { window.onpageshow = function () {
document.documentElement.classList.add('motion-safe:scroll-smooth'); document.documentElement.classList.add('motion-safe:scroll-smooth');
const elem = document.querySelector('[data-aw-toggle-menu]'); const elem = document.querySelector('[data-aw-toggle-menu]');
if (elem) { if (elem) {
elem.classList.remove('expanded'); elem.classList.remove('expanded');
} }
document.body.classList.remove('overflow-hidden'); document.body.classList.remove('overflow-hidden');
document.getElementById('header')?.classList.remove('h-screen'); document.getElementById('header')?.classList.remove('h-screen');
document.querySelector('#header nav')?.classList.add('hidden'); document.querySelector('#header nav')?.classList.add('hidden');
}; };
</script> </script>

View File

@ -2,5 +2,5 @@
--- ---
<span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white"> <span class="self-center ml-2 text-2xl md:text-xl font-bold text-gray-900 whitespace-nowrap dark:text-white">
🚀 AstroWind</span 🚀 AstroWind</span
> >

View File

@ -12,74 +12,74 @@ import Fonts from '~/components/common/Fonts.astro';
import SplitbeeAnalytics from './SplitbeeAnalytics.astro'; import SplitbeeAnalytics from './SplitbeeAnalytics.astro';
export interface Props extends MetaSEO { export interface Props extends MetaSEO {
dontUseTitleTemplate?: boolean; dontUseTitleTemplate?: boolean;
} }
const defaultImage = SITE.defaultImage const defaultImage = SITE.defaultImage
? ( ? (
await getImage({ await getImage({
src: SITE.defaultImage, src: SITE.defaultImage,
alt: 'Default image', alt: 'Default image',
width: 1200, width: 1200,
height: 628, height: 628,
}) })
).src ).src
: ''; : '';
const { const {
title = SITE.name, title = SITE.name,
description = '', description = '',
image: _image = defaultImage, image: _image = defaultImage,
canonical = getCanonical(String(Astro.url.pathname)), canonical = getCanonical(String(Astro.url.pathname)),
noindex = false, noindex = false,
nofollow = false, nofollow = false,
ogTitle = title, ogTitle = title,
ogType = 'website', ogType = 'website',
dontUseTitleTemplate = false, dontUseTitleTemplate = false,
} = Astro.props; } = Astro.props;
const image = const image =
typeof _image === 'string' typeof _image === 'string'
? new URL(_image, Astro.site) ? new URL(_image, Astro.site)
: _image && typeof _image['src'] !== 'undefined' : _image && typeof _image['src'] !== 'undefined'
? // @ts-ignore ? // @ts-ignore
new URL(getRelativeUrlByFilePath(_image.src), Astro.site) new URL(getRelativeUrlByFilePath(_image.src), Astro.site)
: null; : null;
--- ---
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<AstroSeo <AstroSeo
title={title} title={title}
titleTemplate={dontUseTitleTemplate ? '%s' : `%s — ${SITE.name}`} titleTemplate={dontUseTitleTemplate ? '%s' : `%s — ${SITE.name}`}
description={description} description={description}
canonical={String(canonical)} canonical={String(canonical)}
noindex={noindex} noindex={noindex}
nofollow={nofollow} nofollow={nofollow}
openGraph={{ openGraph={{
url: String(canonical), url: String(canonical),
title: ogTitle, title: ogTitle,
description: description, description: description,
type: ogType, type: ogType,
images: image images: image
? [ ? [
{ {
url: image.toString(), url: image.toString(),
alt: ogTitle, alt: ogTitle,
}, },
] ]
: undefined, : undefined,
// site_name: 'SiteName', // site_name: 'SiteName',
}} }}
twitter={{ twitter={{
// handle: '@handle', // handle: '@handle',
// site: '@site', // site: '@site',
cardType: image ? 'summary_large_image' : undefined, cardType: image ? 'summary_large_image' : undefined,
}} }}
/> />
<Fonts /> <Fonts />

View File

@ -3,32 +3,32 @@ import { Icon } from 'astro-icon';
import { getPermalink } from '~/utils/permalinks'; import { getPermalink } from '~/utils/permalinks';
export interface Props { export interface Props {
prevUrl: string; prevUrl: string;
nextUrl: string; nextUrl: string;
prevText?: string; prevText?: string;
nextText?: string; nextText?: string;
} }
const { prevUrl, nextUrl, prevText = 'Newer posts', nextText = 'Older posts' } = Astro.props; const { prevUrl, nextUrl, prevText = 'Newer posts', nextText = 'Older posts' } = Astro.props;
--- ---
{ {
(prevUrl || nextUrl) && ( (prevUrl || nextUrl) && (
<div class="container flex"> <div class="container flex">
<div class="flex flex-row mx-auto container justify-between"> <div class="flex flex-row mx-auto container justify-between">
<a href={getPermalink(prevUrl)} class={`btn btn-ghost px-3 mr-2 ${!prevUrl ? 'invisible' : ''}`}> <a href={getPermalink(prevUrl)} class={`btn btn-ghost px-3 mr-2 ${!prevUrl ? 'invisible' : ''}`}>
<div class="flex flex-row align-middle"> <div class="flex flex-row align-middle">
<Icon name="tabler:chevron-left" class="w-6 h-6" /> <Icon name="tabler:chevron-left" class="w-6 h-6" />
<p class="ml-2">{prevText}</p> <p class="ml-2">{prevText}</p>
</div> </div>
</a> </a>
<a href={getPermalink(nextUrl)} class={`btn btn-ghost px-3 ${!nextUrl ? 'invisible' : ''}`}> <a href={getPermalink(nextUrl)} class={`btn btn-ghost px-3 ${!nextUrl ? 'invisible' : ''}`}>
<div class="flex flex-row align-middle"> <div class="flex flex-row align-middle">
<span class="mr-2">{nextText}</span> <span class="mr-2">{nextText}</span>
<Icon name="tabler:chevron-right" class="w-6 h-6" /> <Icon name="tabler:chevron-right" class="w-6 h-6" />
</div> </div>
</a> </a>
</div> </div>
</div> </div>
) )
} }

View File

@ -2,29 +2,29 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
export interface Props { export interface Props {
text: string; text: string;
url: string | URL; url: string | URL;
class?: string; class?: string;
} }
const { text, url, class: className = 'inline-block' } = Astro.props; const { text, url, class: className = 'inline-block' } = Astro.props;
--- ---
<div class={className}> <div class={className}>
<span class="align-super font-bold dark:text-slate-400">Share:</span> <span class="align-super font-bold dark:text-slate-400">Share:</span>
<button class="ml-2" title="Twitter Share" data-aw-social-share="twitter" data-aw-url={url} data-aw-text={text} <button class="ml-2" title="Twitter Share" data-aw-social-share="twitter" data-aw-url={url} data-aw-text={text}
><Icon name="logos:twitter" class="w-6 h-6" /> ><Icon name="logos:twitter" class="w-6 h-6" />
</button> </button>
<button class="ml-2" title="Facebook Share" data-aw-social-share="facebook" data-aw-url={url} <button class="ml-2" title="Facebook Share" data-aw-social-share="facebook" data-aw-url={url}
><Icon name="logos:facebook" class="w-6 h-6" /> ><Icon name="logos:facebook" class="w-6 h-6" />
</button> </button>
<button class="ml-2" title="Linkedin Share" data-aw-social-share="linkedin" data-aw-url={url} data-aw-text={text} <button class="ml-2" title="Linkedin Share" data-aw-social-share="linkedin" data-aw-url={url} data-aw-text={text}
><Icon name="logos:linkedin-icon" class="w-6 h-6" /> ><Icon name="logos:linkedin-icon" class="w-6 h-6" />
</button> </button>
<button class="ml-2" title="Whatsapp Share" data-aw-social-share="whatsapp" data-aw-url={url} data-aw-text={text} <button class="ml-2" title="Whatsapp Share" data-aw-social-share="whatsapp" data-aw-url={url} data-aw-text={text}
><Icon name="logos:whatsapp" class="w-6 h-6" /> ><Icon name="logos:whatsapp" class="w-6 h-6" />
</button> </button>
<button class="ml-2" title="Email Share" data-aw-social-share="mail" data-aw-url={url} data-aw-text={text} <button class="ml-2" title="Email Share" data-aw-social-share="mail" data-aw-url={url} data-aw-text={text}
><Icon name="tabler:mail" class="w-6 h-6" /> ><Icon name="tabler:mail" class="w-6 h-6" />
</button> </button>
</div> </div>

View File

@ -5,21 +5,30 @@ import { BLOG } from '~/config.mjs';
import type { Post } from '~/types'; import type { Post } from '~/types';
export interface Props { export interface Props {
tags: Post['tags']; tags: Post['tags'];
class?: string; class?: string;
} }
const { tags, class: className = 'text-sm' } = Astro.props; const { tags, class: className = 'text-sm' } = Astro.props;
--- ---
{ {
tags && Array.isArray(tags) && ( tags && Array.isArray(tags) && (
<ul class={className}> <ul class={className}>
{tags.map((tag) => ( {tags.map((tag) => (
<li class="bg-gray-100 dark:bg-slate-700 inline-block mr-2 mb-2 py-0.5 px-2 lowercase font-medium"> <li class="bg-gray-100 dark:bg-slate-700 inline-block mr-2 mb-2 py-0.5 px-2 lowercase font-medium">
{BLOG?.tag?.disabled ? tag : <a href={getPermalink(tag, 'tag')} class="text-gray-600 dark:text-slate-300 hover:text-primary-800 dark:hover:text-gray-200">{tag}</a>} {BLOG?.tag?.disabled ? (
</li> tag
))} ) : (
</ul> <a
) href={getPermalink(tag, 'tag')}
class="text-gray-600 dark:text-slate-300 hover:text-primary-800 dark:hover:text-gray-200"
>
{tag}
</a>
)}
</li>
))}
</ul>
)
} }

View File

@ -2,21 +2,21 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
export interface Props { export interface Props {
label?: string; label?: string;
class?: string; class?: string;
iconClass?: string; iconClass?: string;
iconName?: string; iconName?: string;
} }
const { const {
label = 'Toggle Menu', label = 'Toggle Menu',
class: class:
className = 'ml-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center transition', className = 'ml-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center transition',
iconClass = 'w-6 h-6', iconClass = 'w-6 h-6',
iconName = 'tabler:menu', iconName = 'tabler:menu',
} = Astro.props; } = Astro.props;
--- ---
<button type="button" class={className} aria-label={label} data-aw-toggle-menu> <button type="button" class={className} aria-label={label} data-aw-toggle-menu>
<Icon name={iconName} class={iconClass} optimize={false} /> <Icon name={iconName} class={iconClass} optimize={false} />
</button> </button>

View File

@ -2,21 +2,21 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
export interface Props { export interface Props {
label?: string; label?: string;
class?: string; class?: string;
iconClass?: string; iconClass?: string;
iconName?: string; iconName?: string;
} }
const { const {
label = 'Toggle between Dark and Light mode', label = 'Toggle between Dark and Light mode',
class: class:
className = 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center', className = 'text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center',
iconClass = 'w-6 h-6', iconClass = 'w-6 h-6',
iconName = 'tabler:sun', iconName = 'tabler:sun',
} = Astro.props; } = Astro.props;
--- ---
<button type="button" class={className} aria-label={label} data-aw-toggle-color-scheme> <button type="button" class={className} aria-label={label} data-aw-toggle-color-scheme>
<Icon name={iconName} class={iconClass} /> <Icon name={iconName} class={iconClass} />
</button> </button>

View File

@ -3,26 +3,26 @@ import { getPermalink } from '~/utils/permalinks';
--- ---
<div <div
class="hidden md:block bg-primary-900 dark:bg-slate-800 dark:border-slate-800 dark:text-slate-400 border-b border-primary-900 text-sm px-3 py-2 text-gray-200 overflow-hidden whitespace-nowrap text-ellipsis" class="hidden md:block bg-primary-900 dark:bg-slate-800 dark:border-slate-800 dark:text-slate-400 border-b border-primary-900 text-sm px-3 py-2 text-gray-200 overflow-hidden whitespace-nowrap text-ellipsis"
> >
<span class="text-xs py-0.5 px-1 bg-primary-800 dark:bg-slate-700 dark:text-slate-300 font-semibold">NEW</span> <span class="text-xs py-0.5 px-1 bg-primary-800 dark:bg-slate-700 dark:text-slate-300 font-semibold">NEW</span>
<a <a
href={getPermalink('useful-resources-to-create-websites', 'post')} href={getPermalink('useful-resources-to-create-websites', 'post')}
class="hover:underline text-gray-200 dark:text-slate-400" class="hover:underline text-gray-200 dark:text-slate-400"
>Useful tools and resources to create a professional website »</a >Useful tools and resources to create a professional website »</a
> >
<a <a
target="_blank" target="_blank"
rel="noopener" rel="noopener"
class="float-right" class="float-right"
title="If you like AstroWind, give us a star." title="If you like AstroWind, give us a star."
href="https://github.com/onwidget/astrowind" href="https://github.com/onwidget/astrowind"
> >
<img <img
src="https://img.shields.io/github/stars/onwidget/astrowind.svg?style=social&label=Stars&maxAge=86400" src="https://img.shields.io/github/stars/onwidget/astrowind.svg?style=social&label=Stars&maxAge=86400"
alt="Follow @onWidget" alt="Follow @onWidget"
width="84" width="84"
height="20" height="20"
/> />
</a> </a>
</div> </div>

View File

@ -2,54 +2,56 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
interface CallToAction { interface CallToAction {
text: string; text: string;
href: string; href: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
description?: string; description?: string;
callToAction?: string | CallToAction; callToAction?: string | CallToAction;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
callToAction = await Astro.slots.render('callToAction'), callToAction = await Astro.slots.render('callToAction'),
} = Astro.props; } = Astro.props;
--- ---
<section class="relative"> <section class="relative">
<div class="max-w-6xl mx-auto px-4 sm:px-6"> <div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="py-12 md:py-20"> <div class="py-12 md:py-20">
<div class="max-w-3xl mx-auto text-center p-6 rounded-md shadow-xl dark:shadow-none dark:border dark:border-slate-600"> <div
{ class="max-w-3xl mx-auto text-center p-6 rounded-md shadow-xl dark:shadow-none dark:border dark:border-slate-600"
title && ( >
<h2 {
class="text-4xl md:text-4xl font-bold leading-tighter tracking-tighter mb-4 font-heading" title && (
set:html={title} <h2
/> class="text-4xl md:text-4xl font-bold leading-tighter tracking-tighter mb-4 font-heading"
) set:html={title}
} />
{subtitle && <p class="text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />} )
{ }
typeof callToAction === 'string' ? ( {subtitle && <p class="text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />}
<Fragment set:html={callToAction} /> {
) : ( typeof callToAction === 'string' ? (
callToAction && <Fragment set:html={callToAction} />
callToAction.text && ) : (
callToAction.href && ( callToAction &&
<div class="mt-6 max-w-xs mx-auto"> callToAction.text &&
<a class="btn btn-primary w-full sm:w-auto" href={callToAction.href} target="_blank" rel="noopener"> callToAction.href && (
{callToAction.icon && <Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />} <div class="mt-6 max-w-xs mx-auto">
{callToAction.text} <a class="btn btn-primary w-full sm:w-auto" href={callToAction.href} target="_blank" rel="noopener">
</a> {callToAction.icon && <Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />}
</div> {callToAction.text}
) </a>
) </div>
} )
</div> )
</div> }
</div> </div>
</div>
</div>
</section> </section>

View File

@ -1,107 +1,107 @@
--- ---
import Icon from "astro-icon" import Icon from 'astro-icon';
import { Picture } from '@astrojs/image/components'; import { Picture } from '@astrojs/image/components';
interface Item { interface Item {
title: string; title: string;
description?: string; description?: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
content?: string; content?: string;
items?: Array<Item>; items?: Array<Item>;
image?: string | any; // TODO: find HTMLElementProps image?: string | any; // TODO: find HTMLElementProps
isReversed?: boolean; isReversed?: boolean;
isAfterContent?: boolean; isAfterContent?: boolean;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
content = await Astro.slots.render('content'), content = await Astro.slots.render('content'),
items = [], items = [],
image = await Astro.slots.render('image'), image = await Astro.slots.render('image'),
isReversed = false, isReversed = false,
isAfterContent = false, isAfterContent = false,
} = Astro.props; } = Astro.props;
--- ---
<section class={`bg-primary-50 dark:bg-slate-800 py-16 md:py-20 ${isAfterContent ? 'pt-0 md:pt-0' : ''}`}> <section class={`bg-primary-50 dark:bg-slate-800 py-16 md:py-20 ${isAfterContent ? 'pt-0 md:pt-0' : ''}`}>
<div class="max-w-xl sm:mx-auto lg:max-w-2xl"> <div class="max-w-xl sm:mx-auto lg:max-w-2xl">
{ {
(title || subtitle || highlight) && ( (title || subtitle || highlight) && (
<div class="mb-10 md:mx-auto text-center md:mb-12 max-w-3xl"> <div class="mb-10 md:mx-auto text-center md:mb-12 max-w-3xl">
{highlight && ( {highlight && (
<p <p
class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase" class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"
set:html={highlight} set:html={highlight}
/> />
)} )}
{title && ( {title && (
<h2 <h2
class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading" class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"
set:html={title} set:html={title}
/> />
)} )}
{subtitle && ( {subtitle && (
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} /> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />
)} )}
</div> </div>
) )
} }
</div> </div>
<div class="mx-auto max-w-6xl p-4 md:px-8"> <div class="mx-auto max-w-6xl p-4 md:px-8">
<div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}> <div class={`md:flex ${isReversed ? 'md:flex-row-reverse' : ''} md:gap-16`}>
<div class="md:basis-1/2 self-center"> <div class="md:basis-1/2 self-center">
{content && <div class="mb-12 text-lg text-gray-600 dark:text-slate-400" set:html={content} />} {content && <div class="mb-12 text-lg text-gray-600 dark:text-slate-400" set:html={content} />}
{ {
items && ( items && (
<div class="space-y-8"> <div class="space-y-8">
{items.map(({ title: title2, description, icon }) => ( {items.map(({ title: title2, description, icon }) => (
<div class="flex"> <div class="flex">
<div class="flex-shrink-0"> <div class="flex-shrink-0">
<div class="flex h-7 w-7 items-center justify-center rounded-full bg-blue-800 text-gray-50"> <div class="flex h-7 w-7 items-center justify-center rounded-full bg-blue-800 text-gray-50">
<Icon name={icon? icon : "tabler:check"} class="w-5 h-5" /> <Icon name={icon ? icon : 'tabler:check'} class="w-5 h-5" />
</div> </div>
</div> </div>
<div class="ml-4"> <div class="ml-4">
{title2 && <h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white">{title2}</h3>} {title2 && <h3 class="text-lg font-medium leading-6 text-gray-900 dark:text-white">{title2}</h3>}
{description && <p class="mt-2 text-gray-600 dark:text-slate-400" set:html={description} />} {description && <p class="mt-2 text-gray-600 dark:text-slate-400" set:html={description} />}
</div> </div>
</div> </div>
))} ))}
</div> </div>
) )
} }
</div> </div>
<div aria-hidden="true" class="mt-10 md:mt-0 md:basis-1/2"> <div aria-hidden="true" class="mt-10 md:mt-0 md:basis-1/2">
{ {
image && ( image && (
<div class="relative m-auto max-w-4xl"> <div class="relative m-auto max-w-4xl">
{typeof image === 'string' ? ( {typeof image === 'string' ? (
<Fragment set:html={image} /> <Fragment set:html={image} />
) : ( ) : (
<Picture <Picture
class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg" class="mx-auto w-full rounded-lg bg-gray-500 shadow-lg"
width={500} width={500}
height={500} height={500}
widths={[400, 768]} widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px" sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="500:500" aspectRatio="500:500"
{...image} {...image}
/> />
)} )}
</div> </div>
) )
} }
</div> </div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -2,65 +2,65 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
interface Item { interface Item {
question: string; question: string;
answer: string; answer: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
items: Array<Array<Item>>; items: Array<Array<Item>>;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
items = [], items = [],
} = Astro.props; } = Astro.props;
--- ---
<div class="px-4 py-16 mx-auto max-w-6xl lg:py-20"> <div class="px-4 py-16 mx-auto max-w-6xl lg:py-20">
<div class="max-w-xl sm:mx-auto lg:max-w-2xl"> <div class="max-w-xl sm:mx-auto lg:max-w-2xl">
{ {
(title || subtitle || highlight) && ( (title || subtitle || highlight) && (
<div class="max-w-xl mb-10 md:mx-auto md:text-center lg:max-w-2xl md:mb-12"> <div class="max-w-xl mb-10 md:mx-auto md:text-center lg:max-w-2xl md:mb-12">
{highlight && ( {highlight && (
<p <p
class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase" class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"
set:html={highlight} set:html={highlight}
/> />
)} )}
{title && ( {title && (
<h2 <h2
class="max-w-lg mb-4 text-3xl font-bold leading-none md:tracking-tight sm:text-4xl md:mx-auto font-heading" class="max-w-lg mb-4 text-3xl font-bold leading-none md:tracking-tight sm:text-4xl md:mx-auto font-heading"
set:html={title} set:html={title}
/> />
)} )}
{subtitle && <p class="max-w-3xl mx-auto text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />} {subtitle && <p class="max-w-3xl mx-auto text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />}
</div> </div>
) )
} }
</div> </div>
<div class="max-w-screen-xl sm:mx-auto"> <div class="max-w-screen-xl sm:mx-auto">
<div class="grid grid-cols-1 gap-x-8 gap-y-8 lg:gap-x-16 md:grid-cols-2"> <div class="grid grid-cols-1 gap-x-8 gap-y-8 lg:gap-x-16 md:grid-cols-2">
{ {
items && items &&
items.map((subitems) => ( items.map((subitems) => (
<div class="space-y-8"> <div class="space-y-8">
{subitems.map(({ question, answer }) => ( {subitems.map(({ question, answer }) => (
<div> <div>
<h3 class="mb-4 text-xl font-bold"> <h3 class="mb-4 text-xl font-bold">
<Icon name="tabler:arrow-down-right" class="w-7 h-7 text-primary-800 inline-block" /> <Icon name="tabler:arrow-down-right" class="w-7 h-7 text-primary-800 inline-block" />
{question} {question}
</h3> </h3>
{answer && <div class="text-gray-700 dark:text-gray-400" set:html={answer} />} {answer && <div class="text-gray-700 dark:text-gray-400" set:html={answer} />}
</div> </div>
))} ))}
</div> </div>
)) ))
} }
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,70 +2,70 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
interface Item { interface Item {
title: string; title: string;
description: string; description: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
items: Array<Array<Item>>; items: Array<Array<Item>>;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
items = [], items = [],
} = Astro.props; } = Astro.props;
--- ---
<section class="scroll-mt-16" id="features"> <section class="scroll-mt-16" id="features">
<div class="px-4 py-16 mx-auto max-w-6xl lg:px-8 lg:py-20"> <div class="px-4 py-16 mx-auto max-w-6xl lg:px-8 lg:py-20">
{ {
(title || subtitle || highlight) && ( (title || subtitle || highlight) && (
<div class="mb-10 md:mx-auto text-center md:mb-12 max-w-3xl"> <div class="mb-10 md:mx-auto text-center md:mb-12 max-w-3xl">
{highlight && ( {highlight && (
<p <p
class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase" class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"
set:html={highlight} set:html={highlight}
/> />
)} )}
{title && ( {title && (
<h2 <h2
class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading" class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"
set:html={title} set:html={title}
/> />
)} )}
{subtitle && ( {subtitle && (
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} /> <p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />
)} )}
</div> </div>
) )
} }
<div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0"> <div class="grid mx-auto space-y-6 md:grid-cols-2 md:space-y-0">
{ {
items.map((subitems) => ( items.map((subitems) => (
<div class="space-y-8 sm:px-8"> <div class="space-y-8 sm:px-8">
{subitems.map(({ title, description, icon }) => ( {subitems.map(({ title, description, icon }) => (
<div class="flex flex-row max-w-md"> <div class="flex flex-row max-w-md">
<div class="mb-4 mr-4"> <div class="mb-4 mr-4">
<div class="flex items-center justify-center w-12 h-12 rounded-full bg-primary-800 dark:bg-primary-700"> <div class="flex items-center justify-center w-12 h-12 rounded-full bg-primary-800 dark:bg-primary-700">
{icon && <Icon name={icon} class="w-6 h-6 text-white icon-light" />} {icon && <Icon name={icon} class="w-6 h-6 text-white icon-light" />}
</div> </div>
</div> </div>
<div> <div>
<h3 class="mb-3 text-xl font-bold">{title}</h3> <h3 class="mb-3 text-xl font-bold">{title}</h3>
<p class="text-gray-600 dark:text-slate-400" set:html={description} /> <p class="text-gray-600 dark:text-slate-400" set:html={description} />
</div> </div>
</div> </div>
))} ))}
</div> </div>
)) ))
} }
</div> </div>
</div> </div>
</section> </section>

View File

@ -2,65 +2,68 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
interface Item { interface Item {
title?: string; title?: string;
description?: string; description?: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
items: Array<Item>; items: Array<Item>;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
items = [], items = [],
} = Astro.props; } = Astro.props;
--- ---
<section class="relative"> <section class="relative">
<div class="absolute inset-0 bg-primary-50 dark:bg-slate-800 pointer-events-none mb-32" aria-hidden="true"></div> <div class="absolute inset-0 bg-primary-50 dark:bg-slate-800 pointer-events-none mb-32" aria-hidden="true"></div>
<div class="relative max-w-6xl mx-auto px-4 sm:px-6 -mb-12"> <div class="relative max-w-6xl mx-auto px-4 sm:px-6 -mb-12">
<div class="py-4 pt-8 sm:py-6 lg:py-8 lg:pt-12"> <div class="py-4 pt-8 sm:py-6 lg:py-8 lg:pt-12">
{ {
(title || subtitle || highlight) && ( (title || subtitle || highlight) && (
<div class="mb-8 md:mx-auto text-center max-w-3xl"> <div class="mb-8 md:mx-auto text-center max-w-3xl">
{highlight && ( {highlight && (
<p <p
class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase" class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"
set:html={highlight} set:html={highlight}
/> />
)} )}
{title && ( {title && (
<h2 <h2
class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading" class="text-4xl md:text-5xl font-bold leading-tighter tracking-tighter mb-4 font-heading"
set:html={title} set:html={title}
/> />
)} )}
{subtitle && ( {subtitle && (
<p class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} /> <p
)} class="max-w-3xl mx-auto sm:text-center text-xl text-gray-600 dark:text-slate-400"
</div> set:html={subtitle}
) />
} )}
<div class={`grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 my-12 dark:text-white items-stretch`}> </div>
{ )
items.map(({ title, description, icon }) => ( }
<div class="relative flex flex-col p-6 bg-white dark:bg-slate-900 rounded shadow-lg hover:shadow-md transition border border-transparent dark:border-slate-800"> <div class={`grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-3 my-12 dark:text-white items-stretch`}>
<div class="flex items-center"> {
<Icon name={icon} class="w-10 h-10" /> items.map(({ title, description, icon }) => (
<div class="ml-4 text-xl font-bold">{title}</div> <div class="relative flex flex-col p-6 bg-white dark:bg-slate-900 rounded shadow-lg hover:shadow-md transition border border-transparent dark:border-slate-800">
</div> <div class="flex items-center">
{description && <p class="text-gray-500 dark:text-gray-400 text-md mt-4" set:html={description} />} <Icon name={icon} class="w-10 h-10" />
</div> <div class="ml-4 text-xl font-bold">{title}</div>
)) </div>
} {description && <p class="text-gray-500 dark:text-gray-400 text-md mt-4" set:html={description} />}
</div> </div>
</div> ))
</div> }
</div>
</div>
</div>
</section> </section>

View File

@ -3,126 +3,126 @@ import { Icon } from 'astro-icon';
import { getHomePermalink, getPermalink } from '~/utils/permalinks'; import { getHomePermalink, getPermalink } from '~/utils/permalinks';
const links = [ const links = [
{ {
title: 'Product', title: 'Product',
items: [ items: [
{ title: 'Features', href: '#' }, { title: 'Features', href: '#' },
{ title: 'Security', href: '#' }, { title: 'Security', href: '#' },
{ title: 'Team', href: '#' }, { title: 'Team', href: '#' },
{ title: 'Enterprise', href: '#' }, { title: 'Enterprise', href: '#' },
{ title: 'Customer stories', href: '#' }, { title: 'Customer stories', href: '#' },
{ title: 'Pricing', href: '#' }, { title: 'Pricing', href: '#' },
{ title: 'Resources', href: '#' }, { title: 'Resources', href: '#' },
], ],
}, },
{ {
title: 'Platform', title: 'Platform',
items: [ items: [
{ title: 'Developer API', href: '#' }, { title: 'Developer API', href: '#' },
{ title: 'Partners', href: '#' }, { title: 'Partners', href: '#' },
{ title: 'Atom', href: '#' }, { title: 'Atom', href: '#' },
{ title: 'Electron', href: '#' }, { title: 'Electron', href: '#' },
{ title: 'AstroWind Desktop', href: '#' }, { title: 'AstroWind Desktop', href: '#' },
], ],
}, },
{ {
title: 'Support', title: 'Support',
items: [ items: [
{ title: 'Docs', href: '#' }, { title: 'Docs', href: '#' },
{ title: 'Community Forum', href: '#' }, { title: 'Community Forum', href: '#' },
{ title: 'Professional Services', href: '#' }, { title: 'Professional Services', href: '#' },
{ title: 'Skills', href: '#' }, { title: 'Skills', href: '#' },
{ title: 'Status', href: '#' }, { title: 'Status', href: '#' },
], ],
}, },
{ {
title: 'Company', title: 'Company',
items: [ items: [
{ title: 'About', href: '#' }, { title: 'About', href: '#' },
{ title: 'Blog', href: '#' }, { title: 'Blog', href: '#' },
{ title: 'Careers', href: '#' }, { title: 'Careers', href: '#' },
{ title: 'Press', href: '#' }, { title: 'Press', href: '#' },
{ title: 'Inclusion', href: '#' }, { title: 'Inclusion', href: '#' },
{ title: 'Social Impact', href: '#' }, { title: 'Social Impact', href: '#' },
{ title: 'Shop', href: '#' }, { title: 'Shop', href: '#' },
], ],
}, },
]; ];
const social = [ const social = [
{ label: 'Twitter', icon: 'tabler:brand-twitter', href: '#' }, { label: 'Twitter', icon: 'tabler:brand-twitter', href: '#' },
{ label: 'Instagram', icon: 'tabler:brand-instagram', href: '#' }, { label: 'Instagram', icon: 'tabler:brand-instagram', href: '#' },
{ label: 'Facebook', icon: 'tabler:brand-facebook', href: '#' }, { label: 'Facebook', icon: 'tabler:brand-facebook', href: '#' },
{ label: 'RSS', icon: 'tabler:rss', href: getPermalink('/rss.xml') }, { label: 'RSS', icon: 'tabler:rss', href: getPermalink('/rss.xml') },
{ label: 'Github', icon: 'tabler:brand-github', href: 'https://github.com/onwidget/astrowind' }, { label: 'Github', icon: 'tabler:brand-github', href: 'https://github.com/onwidget/astrowind' },
]; ];
--- ---
<footer class="border-t border-gray-200 dark:border-slate-800"> <footer class="border-t border-gray-200 dark:border-slate-800">
<div class="max-w-6xl mx-auto px-4 sm:px-6"> <div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="grid grid-cols-12 gap-4 gap-y-8 sm:gap-8 py-8 md:py-12"> <div class="grid grid-cols-12 gap-4 gap-y-8 sm:gap-8 py-8 md:py-12">
<div class="col-span-12 lg:col-span-4"> <div class="col-span-12 lg:col-span-4">
<div class="mb-2"> <div class="mb-2">
<a class="inline-block font-bold text-xl" href={getHomePermalink()}>AstroWind</a> <a class="inline-block font-bold text-xl" href={getHomePermalink()}>AstroWind</a>
</div> </div>
<div class="text-sm text-gray-600"> <div class="text-sm text-gray-600">
<a <a
class="text-gray-600 hover:text-gray-700 dark:text-gray-400 hover:underline transition duration-150 ease-in-out" class="text-gray-600 hover:text-gray-700 dark:text-gray-400 hover:underline transition duration-150 ease-in-out"
href={getPermalink('/terms')}>Terms</a href={getPermalink('/terms')}>Terms</a
> · > ·
<a <a
class="text-gray-600 hover:text-gray-700 dark:text-gray-400 hover:underline transition duration-150 ease-in-out" class="text-gray-600 hover:text-gray-700 dark:text-gray-400 hover:underline transition duration-150 ease-in-out"
href={getPermalink('/privacy')}>Privacy Policy</a href={getPermalink('/privacy')}>Privacy Policy</a
> >
</div> </div>
</div> </div>
{ {
links.map(({ title, items }) => ( links.map(({ title, items }) => (
<div class="col-span-6 md:col-span-3 lg:col-span-2"> <div class="col-span-6 md:col-span-3 lg:col-span-2">
<div class="text-gray-800 dark:text-gray-300 font-medium mb-2">{title}</div> <div class="text-gray-800 dark:text-gray-300 font-medium mb-2">{title}</div>
{items && Array.isArray(items) && items.length > 0 && ( {items && Array.isArray(items) && items.length > 0 && (
<ul class="text-sm"> <ul class="text-sm">
{items.map(({ title, href }) => ( {items.map(({ title, href }) => (
<li class="mb-2"> <li class="mb-2">
<a <a
class="text-gray-600 hover:text-gray-700 hover:underline dark:text-gray-400 transition duration-150 ease-in-out" class="text-gray-600 hover:text-gray-700 hover:underline dark:text-gray-400 transition duration-150 ease-in-out"
href={href} href={href}
> >
{title} {title}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
)} )}
</div> </div>
)) ))
} }
</div> </div>
<div class="md:flex md:items-center md:justify-between py-6 md:py-8"> <div class="md:flex md:items-center md:justify-between py-6 md:py-8">
<ul class="flex mb-4 md:order-1 -ml-2 md:ml-4 md:mb-0"> <ul class="flex mb-4 md:order-1 -ml-2 md:ml-4 md:mb-0">
{ {
social.map(({ label, href, icon }) => ( social.map(({ label, href, icon }) => (
<li> <li>
<a <a
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center" class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center"
aria-label={label} aria-label={label}
href={href} href={href}
> >
<Icon name={icon} class="w-5 h-5" /> <Icon name={icon} class="w-5 h-5" />
</a> </a>
</li> </li>
)) ))
} }
</ul> </ul>
<div class="text-sm text-gray-700 mr-4 dark:text-slate-400"> <div class="text-sm text-gray-700 mr-4 dark:text-slate-400">
<span <span
class="w-5 h-5 md:w-6 md:h-6 md:-mt-0.5 bg-cover mr-1.5 float-left rounded-sm bg-[url(https://onwidget.com/favicon/favicon-32x32.png)]" class="w-5 h-5 md:w-6 md:h-6 md:-mt-0.5 bg-cover mr-1.5 float-left rounded-sm bg-[url(https://onwidget.com/favicon/favicon-32x32.png)]"
> >
</span> </span>
Made by <a class="text-blue-600 hover:underline dark:text-gray-200" href="https://onwidget.com/"> onWidget</a> · Made by <a class="text-blue-600 hover:underline dark:text-gray-200" href="https://onwidget.com/"> onWidget</a> ·
All rights reserved. All rights reserved.
</div> </div>
</div> </div>
</div> </div>
</footer> </footer>

View File

@ -7,117 +7,117 @@ import ToggleMenu from '~/components/common/ToggleMenu.astro';
import { getHomePermalink, getBlogPermalink, getPermalink } from '~/utils/permalinks'; import { getHomePermalink, getBlogPermalink, getPermalink } from '~/utils/permalinks';
const links = [ const links = [
{ {
text: 'Pages', text: 'Pages',
links: [ links: [
{ {
text: 'Features', text: 'Features',
href: "#", href: '#',
}, },
{ {
text: 'Pricing', text: 'Pricing',
href: "#", href: '#',
}, },
{ {
text: 'About us', text: 'About us',
href: "#", href: '#',
}, },
{ {
text: 'Contact', text: 'Contact',
href: "#", href: '#',
}, },
{ {
text: 'Terms', text: 'Terms',
href: getPermalink('/terms'), href: getPermalink('/terms'),
}, },
{ {
text: 'Privacy policy', text: 'Privacy policy',
href: getPermalink('/privacy'), href: getPermalink('/privacy'),
}, },
], ],
}, },
{ {
text: 'Resources', text: 'Resources',
href: getPermalink('useful-resources-to-create-websites', 'post'), href: getPermalink('useful-resources-to-create-websites', 'post'),
}, },
{ {
text: 'Blog', text: 'Blog',
href: getBlogPermalink(), href: getBlogPermalink(),
}, },
]; ];
--- ---
<header class="sticky top-0 z-40 flex-none mx-auto w-full transition-all ease-in duration-100" id="header"> <header class="sticky top-0 z-40 flex-none mx-auto w-full transition-all ease-in duration-100" id="header">
<div class="py-3 px-3 md:py-3.5 md:px-4 mx-auto w-full md:flex md:justify-between max-w-6xl"> <div class="py-3 px-3 md:py-3.5 md:px-4 mx-auto w-full md:flex md:justify-between max-w-6xl">
<div class="flex justify-between"> <div class="flex justify-between">
<a class="flex items-center" href={getHomePermalink()}> <a class="flex items-center" href={getHomePermalink()}>
<Logo /> <Logo />
</a> </a>
<div class="flex items-center md:hidden"> <div class="flex items-center md:hidden">
<ToggleTheme /> <ToggleTheme />
<ToggleMenu /> <ToggleMenu />
</div> </div>
</div> </div>
<nav <nav
class="items-center w-full md:w-auto hidden md:flex text-gray-600 dark:text-slate-200 h-[calc(100vh-72px)] md:h-auto overflow-y-auto md:overflow-visible" class="items-center w-full md:w-auto hidden md:flex text-gray-600 dark:text-slate-200 h-[calc(100vh-72px)] md:h-auto overflow-y-auto md:overflow-visible"
aria-label="Main navigation" aria-label="Main navigation"
> >
<ul class="flex flex-col pt-8 md:pt-0 md:flex-row md:self-center w-full md:w-auto text-xl md:text-base"> <ul class="flex flex-col pt-8 md:pt-0 md:flex-row md:self-center w-full md:w-auto text-xl md:text-base">
{ {
links.map(({ text, href, links }) => ( links.map(({ text, href, links }) => (
<li class={links?.length ? 'dropdown' : ''}> <li class={links?.length ? 'dropdown' : ''}>
{links?.length ? ( {links?.length ? (
<> <>
<button class="font-medium hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out"> <button class="font-medium hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out">
{text} <Icon name="tabler:chevron-down" class="w-3.5 h-3.5 ml-0.5 hidden md:inline" /> {text} <Icon name="tabler:chevron-down" class="w-3.5 h-3.5 ml-0.5 hidden md:inline" />
</button> </button>
<ul class="dropdown-menu rounded md:absolute pl-4 md:pl-0 md:hidden font-medium md:bg-white md:min-w-[200px] dark:md:bg-slate-800 drop-shadow-xl"> <ul class="dropdown-menu rounded md:absolute pl-4 md:pl-0 md:hidden font-medium md:bg-white md:min-w-[200px] dark:md:bg-slate-800 drop-shadow-xl">
{links.map(({ text: text2, href: href2 }) => ( {links.map(({ text: text2, href: href2 }) => (
<li> <li>
<a <a
class="first:rounded-t last:rounded-b md:hover:bg-gray-100 dark:hover:bg-gray-700 py-2 px-5 block whitespace-no-wrap" class="first:rounded-t last:rounded-b md:hover:bg-gray-100 dark:hover:bg-gray-700 py-2 px-5 block whitespace-no-wrap"
href={href2} href={href2}
> >
{text2} {text2}
</a> </a>
</li> </li>
))} ))}
</ul> </ul>
</> </>
) : ( ) : (
<a <a
class="font-medium hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out" class="font-medium hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out"
href={href} href={href}
> >
{text} {text}
</a> </a>
)} )}
</li> </li>
)) ))
} }
<li class="md:hidden"> <li class="md:hidden">
<a <a
class="font-bold hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out" class="font-bold hover:text-gray-900 dark:hover:text-white px-4 py-3 flex items-center transition duration-150 ease-in-out"
href="https://github.com/onwidget/astrowind">Github</a href="https://github.com/onwidget/astrowind">Github</a
> >
</li> </li>
</ul> </ul>
<div class="md:self-center flex items-center md:mb-0 ml-4"> <div class="md:self-center flex items-center md:mb-0 ml-4">
<div class="hidden items-center md:flex"> <div class="hidden items-center md:flex">
<ToggleTheme iconClass="w-5 h-5" /> <ToggleTheme iconClass="w-5 h-5" />
<a <a
class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center" class="text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5 inline-flex items-center"
aria-label="RSS Feed" aria-label="RSS Feed"
href={getPermalink('/rss.xml')} href={getPermalink('/rss.xml')}
> >
<Icon name="tabler:rss" class="w-5 h-5" /> <Icon name="tabler:rss" class="w-5 h-5" />
</a> </a>
<a <a
class="btn w-full ml-3 py-2.5 px-5 font-semibold text-gray-600 shadow-none text-sm" class="btn w-full ml-3 py-2.5 px-5 font-semibold text-gray-600 shadow-none text-sm"
href="https://github.com/onwidget/astrowind">Download</a href="https://github.com/onwidget/astrowind">Download</a
> >
</div> </div>
</div> </div>
</nav> </nav>
</div> </div>
</header> </header>

View File

@ -3,96 +3,104 @@ import { Icon } from 'astro-icon';
import { Picture } from '@astrojs/image/components'; import { Picture } from '@astrojs/image/components';
interface CallToAction { interface CallToAction {
text: string; text: string;
href: string; href: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
callToAction?: string | CallToAction; callToAction?: string | CallToAction;
callToAction2?: string | CallToAction; callToAction2?: string | CallToAction;
image?: string | any; // TODO: find HTMLElementProps image?: string | any; // TODO: find HTMLElementProps
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
callToAction = await Astro.slots.render('callToAction'), callToAction = await Astro.slots.render('callToAction'),
callToAction2 = await Astro.slots.render('callToAction2'), callToAction2 = await Astro.slots.render('callToAction2'),
image = await Astro.slots.render('image'), image = await Astro.slots.render('image'),
} = Astro.props; } = Astro.props;
--- ---
<section> <section>
<div class="max-w-6xl mx-auto px-4 sm:px-6"> <div class="max-w-6xl mx-auto px-4 sm:px-6">
<div class="py-12 md:py-20"> <div class="py-12 md:py-20">
<div class="text-center pb-10 md:pb-16 max-w-5xl mx-auto"> <div class="text-center pb-10 md:pb-16 max-w-5xl mx-auto">
{ {
title && ( title && (
<h1 <h1
class="text-5xl md:text-[3.50rem] font-bold leading-tighter tracking-tighter mb-4 font-heading" class="text-5xl md:text-[3.50rem] font-bold leading-tighter tracking-tighter mb-4 font-heading"
set:html={title} set:html={title}
/> />
) )
} }
<div class="max-w-3xl mx-auto"> <div class="max-w-3xl mx-auto">
{subtitle && <p class="text-xl font-medium text-gray-500 mb-8 dark:text-slate-400" set:html={subtitle} />} {subtitle && <p class="text-xl font-medium text-gray-500 mb-8 dark:text-slate-400" set:html={subtitle} />}
<div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4"> <div class="max-w-xs sm:max-w-md m-auto flex flex-nowrap flex-col sm:flex-row sm:justify-center gap-4">
{ {
callToAction && ( callToAction && (
<div class="flex w-full sm:w-auto"> <div class="flex w-full sm:w-auto">
{typeof callToAction === 'string' ? ( {typeof callToAction === 'string' ? (
<Fragment set:html={callToAction} /> <Fragment set:html={callToAction} />
) : ( ) : (
<a class="btn btn-primary sm:mb-0 w-full" href={callToAction?.href} target="_blank" rel="noopener"> <a class="btn btn-primary sm:mb-0 w-full" href={callToAction?.href} target="_blank" rel="noopener">
{callToAction?.icon && <><Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />{' '}</>} {callToAction?.icon && (
{callToAction?.text} <>
</a> <Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />{' '}
)} </>
</div> )}
) {callToAction?.text}
} </a>
{ )}
callToAction2 && ( </div>
<div class="flex w-full sm:w-auto"> )
{typeof callToAction2 === 'string' ? ( }
<Fragment set:html={callToAction2} /> {
) : ( callToAction2 && (
<a class="btn w-full" href={callToAction2?.href}> <div class="flex w-full sm:w-auto">
{callToAction2?.icon && <><Icon name={callToAction2.icon} class="w-5 h-5 mr-1 -ml-1.5" />{' '}</>} {typeof callToAction2 === 'string' ? (
{callToAction2.text} <Fragment set:html={callToAction2} />
</a> ) : (
)} <a class="btn w-full" href={callToAction2?.href}>
</div> {callToAction2?.icon && (
) <>
} <Icon name={callToAction2.icon} class="w-5 h-5 mr-1 -ml-1.5" />{' '}
</div> </>
</div> )}
</div> {callToAction2.text}
<div> </a>
{ )}
image && ( </div>
<div class="relative m-auto max-w-4xl"> )
{typeof image === 'string' ? ( }
<Fragment set:html={image} /> </div>
) : ( </div>
<Picture </div>
class="mx-auto rounded-md w-full" <div>
widths={[400, 768, 1480]} {
sizes="(max-width: 767px) 400px, (max-width: 1479px) 768px, 1480px" image && (
aspectRatio={1480 / 833} <div class="relative m-auto max-w-4xl">
loading="eager" {typeof image === 'string' ? (
width={1480} <Fragment set:html={image} />
height={833} ) : (
{...image} <Picture
/> class="mx-auto rounded-md w-full"
)} widths={[400, 768, 1480]}
</div> sizes="(max-width: 767px) 400px, (max-width: 1479px) 768px, 1480px"
) aspectRatio={1480 / 833}
} loading="eager"
</div> width={1480}
</div> height={833}
</div> {...image}
/>
)}
</div>
)
}
</div>
</div>
</div>
</section> </section>

View File

@ -3,9 +3,9 @@ import { Icon } from 'astro-icon';
--- ---
<section class="bg-primary-50 dark:bg-slate-800"> <section class="bg-primary-50 dark:bg-slate-800">
<div class="max-w-6xl mx-auto px-4 sm:px-6 py-4 text-md text-center font-medium"> <div class="max-w-6xl mx-auto px-4 sm:px-6 py-4 text-md text-center font-medium">
<span class="font-bold"> <span class="font-bold">
<Icon name="tabler:info-square" class="w-5 h-5 inline-block align-text-bottom" /> Philosophy:</span <Icon name="tabler:info-square" class="w-5 h-5 inline-block align-text-bottom" /> Philosophy:</span
> Simplicity, Best Practices and High Performance > Simplicity, Best Practices and High Performance
</div> </div>
</section> </section>

View File

@ -3,272 +3,272 @@
<!-- Pricing --> <!-- Pricing -->
<div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto"> <div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto">
<!-- Title --> <!-- Title -->
<div class="max-w-2xl mx-auto text-center mb-10 lg:mb-14"> <div class="max-w-2xl mx-auto text-center mb-10 lg:mb-14">
<h2 class="text-2xl font-bold md:text-4xl md:leading-tight dark:text-white">Pricing</h2> <h2 class="text-2xl font-bold md:text-4xl md:leading-tight dark:text-white">Pricing</h2>
<p class="mt-1 text-gray-600 dark:text-gray-400"> <p class="mt-1 text-gray-600 dark:text-gray-400">
Whatever your status, our offers evolve according to your needs. Whatever your status, our offers evolve according to your needs.
</p> </p>
</div> </div>
<!-- End Title --> <!-- End Title -->
<!-- Grid --> <!-- Grid -->
<div class="mt-12 grid sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:items-center"> <div class="mt-12 grid sm:grid-cols-2 lg:grid-cols-4 gap-6 lg:items-center">
<!-- Card --> <!-- Card -->
<div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700"> <div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700">
<h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Free</h4> <h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Free</h4>
<span class="mt-7 font-bold text-5xl text-gray-800 dark:text-gray-200">Free</span> <span class="mt-7 font-bold text-5xl text-gray-800 dark:text-gray-200">Free</span>
<p class="mt-2 text-sm text-gray-500">Forever free</p> <p class="mt-2 text-sm text-gray-500">Forever free</p>
<ul class="mt-7 space-y-2.5 text-sm"> <ul class="mt-7 space-y-2.5 text-sm">
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> 1 user</span> <span class="text-gray-800 dark:text-gray-400"> 1 user</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Plan features</span> <span class="text-gray-800 dark:text-gray-400"> Plan features</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Product support</span> <span class="text-gray-800 dark:text-gray-400"> Product support</span>
</li> </li>
</ul> </ul>
<button class="mt-5 btn">Sign up</button> <button class="mt-5 btn">Sign up</button>
</div> </div>
<!-- End Card --> <!-- End Card -->
<!-- Card --> <!-- Card -->
<div class="flex flex-col border-2 border-blue-600 text-center shadow-xl rounded-xl p-8 dark:border-blue-500"> <div class="flex flex-col border-2 border-blue-600 text-center shadow-xl rounded-xl p-8 dark:border-blue-500">
<p class="mb-3"> <p class="mb-3">
<span <span
class="inline-flex items-center gap-1.5 py-1.5 px-3 rounded-md text-xs uppercase font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-white" class="inline-flex items-center gap-1.5 py-1.5 px-3 rounded-md text-xs uppercase font-semibold bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-white"
>Most popular</span >Most popular</span
> >
</p> </p>
<h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Startup</h4> <h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Startup</h4>
<span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200"> <span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200">
<span class="font-bold text-2xl -mr-2">$</span> <span class="font-bold text-2xl -mr-2">$</span>
39 39
</span> </span>
<p class="mt-2 text-sm text-gray-500">All the basics for starting a new business</p> <p class="mt-2 text-sm text-gray-500">All the basics for starting a new business</p>
<ul class="mt-7 space-y-2.5 text-sm"> <ul class="mt-7 space-y-2.5 text-sm">
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> 2 users</span> <span class="text-gray-800 dark:text-gray-400"> 2 users</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Plan features</span> <span class="text-gray-800 dark:text-gray-400"> Plan features</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Product support</span> <span class="text-gray-800 dark:text-gray-400"> Product support</span>
</li> </li>
</ul> </ul>
<button class="mt-5 btn">Sign up</button> <button class="mt-5 btn">Sign up</button>
</div> </div>
<!-- End Card --> <!-- End Card -->
<!-- Card --> <!-- Card -->
<div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700"> <div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700">
<h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Team</h4> <h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Team</h4>
<span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200"> <span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200">
<span class="font-bold text-2xl -mr-2">$</span> <span class="font-bold text-2xl -mr-2">$</span>
89 89
</span> </span>
<p class="mt-2 text-sm text-gray-500">Everything you need for a growing business</p> <p class="mt-2 text-sm text-gray-500">Everything you need for a growing business</p>
<ul class="mt-7 space-y-2.5 text-sm"> <ul class="mt-7 space-y-2.5 text-sm">
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> 5 users</span> <span class="text-gray-800 dark:text-gray-400"> 5 users</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Plan features</span> <span class="text-gray-800 dark:text-gray-400"> Plan features</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Product support</span> <span class="text-gray-800 dark:text-gray-400"> Product support</span>
</li> </li>
</ul> </ul>
<button class="mt-5 btn">Sign up</button> <button class="mt-5 btn">Sign up</button>
</div> </div>
<!-- End Card --> <!-- End Card -->
<!-- Card --> <!-- Card -->
<div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700"> <div class="flex flex-col border border-gray-200 text-center rounded-xl p-8 dark:border-gray-700">
<h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Enterprise</h4> <h4 class="font-medium text-lg text-gray-800 dark:text-gray-200">Enterprise</h4>
<span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200"> <span class="mt-5 font-bold text-5xl text-gray-800 dark:text-gray-200">
<span class="font-bold text-2xl -mr-2">$</span> <span class="font-bold text-2xl -mr-2">$</span>
149 149
</span> </span>
<p class="mt-2 text-sm text-gray-500">Advanced features for scaling your business</p> <p class="mt-2 text-sm text-gray-500">Advanced features for scaling your business</p>
<ul class="mt-7 space-y-2.5 text-sm"> <ul class="mt-7 space-y-2.5 text-sm">
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> 10 users</span> <span class="text-gray-800 dark:text-gray-400"> 10 users</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Plan features</span> <span class="text-gray-800 dark:text-gray-400"> Plan features</span>
</li> </li>
<li class="flex space-x-2"> <li class="flex space-x-2">
<svg <svg
class="flex-shrink-0 h-5 w-5 text-blue-600" class="flex-shrink-0 h-5 w-5 text-blue-600"
width="16" width="16"
height="16" height="16"
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
> >
<path <path
d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z" d="M11.5219 4.0949C11.7604 3.81436 12.181 3.78025 12.4617 4.01871C12.7422 4.25717 12.7763 4.6779 12.5378 4.95844L6.87116 11.6251C6.62896 11.91 6.1998 11.94 5.9203 11.6916L2.9203 9.02494C2.64511 8.78033 2.62032 8.35894 2.86493 8.08375C3.10955 7.80856 3.53092 7.78378 3.80611 8.02839L6.29667 10.2423L11.5219 4.0949Z"
fill="currentColor"></path> fill="currentColor"></path>
</svg> </svg>
<span class="text-gray-800 dark:text-gray-400"> Product support</span> <span class="text-gray-800 dark:text-gray-400"> Product support</span>
</li> </li>
</ul> </ul>
<button class="mt-5 btn">Sign up</button> <button class="mt-5 btn">Sign up</button>
</div> </div>
<!-- End Card --> <!-- End Card -->
</div> </div>
<!-- End Grid --> <!-- End Grid -->
</div> </div>
<!-- End Pricing --> <!-- End Pricing -->

View File

@ -1,55 +1,57 @@
--- ---
interface Item { interface Item {
name: string; name: string;
value: string; value: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
items?: Array<Item>; items?: Array<Item>;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
items = [], items = [],
} = Astro.props; } = Astro.props;
--- ---
<div class="px-4 py-4 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl"> <div class="px-4 py-4 md:py-16 sm:px-6 mx-auto md:px-24 lg:px-8 lg:py-20 max-w-6xl">
{ {
(title || subtitle || highlight) && ( (title || subtitle || highlight) && (
<div class="max-w-xl mb-10 md:mx-auto sm:text-center lg:max-w-2xl md:mb-12"> <div class="max-w-xl mb-10 md:mx-auto sm:text-center lg:max-w-2xl md:mb-12">
{highlight && ( {highlight && (
<p class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"> <p class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase">
{highlight} {highlight}
</p> </p>
)} )}
{title && ( {title && (
<h2 <h2
class="max-w-lg mb-4 text-3xl font-bold leading-none tracking-tight sm:text-4xl md:mx-auto font-heading" class="max-w-lg mb-4 text-3xl font-bold leading-none tracking-tight sm:text-4xl md:mx-auto font-heading"
set:html={title} set:html={title}
/> />
)} )}
{subtitle && ( {subtitle && (
<p class="max-w-3xl mx-auto text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} /> <p class="max-w-3xl mx-auto text-center text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />
)} )}
</div> </div>
) )
} }
<div class="grid grid-cols-2 row-gap-8 md:grid-cols-4"> <div class="grid grid-cols-2 row-gap-8 md:grid-cols-4">
{ {
items.map(({ name, value }) => ( items.map(({ name, value }) => (
<div class="text-center md:border-r md:last:border-none dark:md:border-slate-500 mb-12 md:mb-0"> <div class="text-center md:border-r md:last:border-none dark:md:border-slate-500 mb-12 md:mb-0">
<div class="text-[2.6rem] font-bold lg:text-5xl xl:text-6xl text-primary-800 dark:text-primary-600 font-heading">{value}</div> <div class="text-[2.6rem] font-bold lg:text-5xl xl:text-6xl text-primary-800 dark:text-primary-600 font-heading">
<p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base"> {value}
{name} </div>
</p> <p class="text-sm font-medium tracking-widest text-gray-800 dark:text-slate-400 uppercase lg:text-base">
</div> {name}
)) </p>
} </div>
</div> ))
}
</div>
</div> </div>

View File

@ -3,70 +3,70 @@ import { Icon } from 'astro-icon';
import { Picture } from '@astrojs/image/components'; import { Picture } from '@astrojs/image/components';
interface Item { interface Item {
title: string; title: string;
description?: string; description?: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
items: Array<Item>; items: Array<Item>;
image?: string | any; // TODO: find HTMLElementProps image?: string | any; // TODO: find HTMLElementProps
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
items = [], items = [],
image = await Astro.slots.render('image'), image = await Astro.slots.render('image'),
} = Astro.props; } = Astro.props;
--- ---
<section class="px-4 py-16 sm:px-6 mx-auto lg:px-8 lg:py-20 max-w-6xl"> <section class="px-4 py-16 sm:px-6 mx-auto lg:px-8 lg:py-20 max-w-6xl">
<div class="grid gap-6 row-gap-10 md:grid-cols-2"> <div class="grid gap-6 row-gap-10 md:grid-cols-2">
<div class="md:py-4 md:pr-16 mb-4 md:mb-0"> <div class="md:py-4 md:pr-16 mb-4 md:mb-0">
{title && <h2 class="mb-8 text-3xl lg:text-4xl font-bold font-heading" set:html={title} />} {title && <h2 class="mb-8 text-3xl lg:text-4xl font-bold font-heading" set:html={title} />}
{ {
items && items &&
items.length && items.length &&
items.map(({ title, description, icon }, index) => ( items.map(({ title, description, icon }, index) => (
<div class="flex"> <div class="flex">
<div class="flex flex-col items-center mr-4"> <div class="flex flex-col items-center mr-4">
<div> <div>
{index !== items.length - 1 ? ( {index !== items.length - 1 ? (
<div class="flex items-center justify-center w-10 h-10 rounded-full border-primary-800 dark:border-primary-700 border-2"> <div class="flex items-center justify-center w-10 h-10 rounded-full border-primary-800 dark:border-primary-700 border-2">
{icon && <Icon name={icon} class="w-6 h-6 text-primary-800 dark:text-slate-200" />} {icon && <Icon name={icon} class="w-6 h-6 text-primary-800 dark:text-slate-200" />}
</div> </div>
) : ( ) : (
<div class="flex items-center justify-center w-10 h-10 rounded-full border-primary-800 border-2 bg-primary-800 dark:bg-primary-700 dark:border-primary-700"> <div class="flex items-center justify-center w-10 h-10 rounded-full border-primary-800 border-2 bg-primary-800 dark:bg-primary-700 dark:border-primary-700">
<Icon name={icon} class="w-6 h-6 text-white dark:text-slate-200" /> <Icon name={icon} class="w-6 h-6 text-white dark:text-slate-200" />
</div> </div>
)} )}
</div> </div>
<div class="w-px h-full bg-gray-300 dark:bg-slate-500" /> <div class="w-px h-full bg-gray-300 dark:bg-slate-500" />
</div> </div>
<div class={`pt-1 ${index !== items.length - 1 ? 'pb-8' : ''}`}> <div class={`pt-1 ${index !== items.length - 1 ? 'pb-8' : ''}`}>
{title && <p class="mb-2 text-xl font-bold text-gray-900 dark:text-slate-300" set:html={title} />} {title && <p class="mb-2 text-xl font-bold text-gray-900 dark:text-slate-300" set:html={title} />}
{description && <p class="text-gray-600 dark:text-slate-400" set:html={description} />} {description && <p class="text-gray-600 dark:text-slate-400" set:html={description} />}
</div> </div>
</div> </div>
)) ))
} }
</div> </div>
<div class="relative"> <div class="relative">
{ {
image && image &&
(typeof image === 'string' ? ( (typeof image === 'string' ? (
<Fragment set:html={image} /> <Fragment set:html={image} />
) : ( ) : (
<Picture <Picture
class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700" class="inset-0 object-cover object-top w-full rounded-md shadow-lg md:absolute md:h-full bg-gray-400 dark:bg-slate-700"
widths={[400, 768]} widths={[400, 768]}
sizes="(max-width: 768px) 100vw, 432px" sizes="(max-width: 768px) 100vw, 432px"
aspectRatio="432:768" aspectRatio="432:768"
{...image} {...image}
/> />
)) ))
} }
</div> </div>
</div> </div>
</section> </section>

View File

@ -2,31 +2,31 @@
import { Icon } from 'astro-icon'; import { Icon } from 'astro-icon';
interface Item { interface Item {
title: string; title: string;
description: string; description: string;
icon?: string; icon?: string;
} }
interface CallToAction { interface CallToAction {
text: string; text: string;
href: string; href: string;
icon?: string; icon?: string;
} }
export interface Props { export interface Props {
title?: string; title?: string;
subtitle?: string; subtitle?: string;
highlight?: string; highlight?: string;
callToAction?: string | CallToAction; callToAction?: string | CallToAction;
items: Array<Item>; items: Array<Item>;
} }
const { const {
title = await Astro.slots.render('title'), title = await Astro.slots.render('title'),
subtitle = await Astro.slots.render('subtitle'), subtitle = await Astro.slots.render('subtitle'),
highlight, highlight,
callToAction = await Astro.slots.render('callToAction'), callToAction = await Astro.slots.render('callToAction'),
items = [], items = [],
} = Astro.props; } = Astro.props;
/** /**
@ -64,64 +64,64 @@ const {
--- ---
<section> <section>
<div class="max-w-6xl mx-auto px-4 sm:px-6 overflow-hidden"> <div class="max-w-6xl mx-auto px-4 sm:px-6 overflow-hidden">
<div class="py-12 md:py-20"> <div class="py-12 md:py-20">
<div class="py-4 sm:py-6 lg:py-8"> <div class="py-4 sm:py-6 lg:py-8">
<div class="flex flex-wrap md:-mx-8"> <div class="flex flex-wrap md:-mx-8">
<div class="w-full lg:w-1/2 px-0 sm:px-8 mb-12"> <div class="w-full lg:w-1/2 px-0 sm:px-8 mb-12">
<div> <div>
{ {
highlight && ( highlight && (
<p <p
class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase" class="text-base text-primary-800 dark:text-primary-200 font-semibold tracking-wide uppercase"
set:html={highlight} set:html={highlight}
/> />
) )
} }
{title && <h2 class="mb-4 text-3xl lg:text-4xl font-bold font-heading" set:html={title} />} {title && <h2 class="mb-4 text-3xl lg:text-4xl font-bold font-heading" set:html={title} />}
{subtitle && <p class="mb-8 text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />} {subtitle && <p class="mb-8 text-xl text-gray-600 dark:text-slate-400" set:html={subtitle} />}
<div class="w-full"> <div class="w-full">
{ {
typeof callToAction === 'string' ? ( typeof callToAction === 'string' ? (
<Fragment set:html={callToAction} /> <Fragment set:html={callToAction} />
) : ( ) : (
callToAction && callToAction &&
callToAction.text && callToAction.text &&
callToAction.href && ( callToAction.href && (
<a class="btn btn-primary mb-4 sm:mb-0" href={callToAction.href} target="_blank" rel="noopener"> <a class="btn btn-primary mb-4 sm:mb-0" href={callToAction.href} target="_blank" rel="noopener">
{callToAction.icon && <Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />} {callToAction.icon && <Icon name={callToAction.icon} class="w-5 h-5 mr-1 -ml-1.5" />}
{callToAction.text} {callToAction.text}
</a> </a>
) )
) )
} }
</div> </div>
</div> </div>
</div> </div>
<div class="w-full lg:w-1/2 px-0 sm:px-8"> <div class="w-full lg:w-1/2 px-0 sm:px-8">
<ul class="space-y-10"> <ul class="space-y-10">
{ {
items && items.length items && items.length
? items.map(({ title: title2, description, icon }, index) => ( ? items.map(({ title: title2, description, icon }, index) => (
<li class="flex md:-mx-4"> <li class="flex md:-mx-4">
<div class="pr-4 sm:pl-4"> <div class="pr-4 sm:pl-4">
<span class="flex w-16 h-16 mx-auto items-center justify-center text-2xl font-bold rounded-full bg-primary-100 text-primary-800"> <span class="flex w-16 h-16 mx-auto items-center justify-center text-2xl font-bold rounded-full bg-primary-100 text-primary-800">
{icon ? <Icon name={icon} class="w-6 h-6 icon-bold" /> : index + 1} {icon ? <Icon name={icon} class="w-6 h-6 icon-bold" /> : index + 1}
</span> </span>
</div> </div>
<div class="pl-4"> <div class="pl-4">
<h3 class="mb-4 text-xl font-semibold font-heading" set:html={title2} /> <h3 class="mb-4 text-xl font-semibold font-heading" set:html={title2} />
<p class="text-gray-500 dark:text-gray-400" set:html={description} /> <p class="text-gray-500 dark:text-gray-400" set:html={description} />
</div> </div>
</li> </li>
)) ))
: '' : ''
} }
</ul> </ul>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,61 +1,62 @@
import defaultImage from './assets/images/default.png'; import defaultImage from './assets/images/default.png';
const CONFIG = { const CONFIG = {
name: 'AstroWind', name: 'AstroWind',
origin: 'https://astrowind.vercel.app', origin: 'https://astrowind.vercel.app',
basePathname: '/', basePathname: '/',
trailingSlash: false, trailingSlash: false,
title: 'AstroWind — Free template for create a website with Astro + Tailwind CSS', title: 'AstroWind — Free template for create a website with Astro + Tailwind CSS',
description: '🚀 Suitable for Startups, Small Business, Sass Websites, Professional Portfolios, Marketing Websites, Landing Pages & Blogs.', description:
defaultImage: defaultImage, '🚀 Suitable for Startups, Small Business, Sass Websites, Professional Portfolios, Marketing Websites, Landing Pages & Blogs.',
defaultImage: defaultImage,
defaultTheme: 'system', // Values: "system" | "light" | "dark" | "light:only" | "dark:only" defaultTheme: 'system', // Values: "system" | "light" | "dark" | "light:only" | "dark:only"
language: 'en', language: 'en',
textDirection: 'ltr', textDirection: 'ltr',
dateFormatter: new Intl.DateTimeFormat('en', { dateFormatter: new Intl.DateTimeFormat('en', {
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
timeZone: 'UTC', timeZone: 'UTC',
}), }),
googleAnalyticsId: false, // or "G-XXXXXXXXXX", googleAnalyticsId: false, // or "G-XXXXXXXXXX",
googleSiteVerificationId: 'orcPxI47GSa-cRvY11tUe6iGg2IO_RPvnA1q95iEM3M', googleSiteVerificationId: 'orcPxI47GSa-cRvY11tUe6iGg2IO_RPvnA1q95iEM3M',
blog: { blog: {
disabled: false, disabled: false,
postsPerPage: 4, postsPerPage: 4,
list: { list: {
pathname: 'blog', // blog main path, you can change this to "articles" (/articles) pathname: 'blog', // blog main path, you can change this to "articles" (/articles)
noindex: false, noindex: false,
disabled: false, disabled: false,
}, },
post: { post: {
pathname: '', // empty for /some-post, value for /pathname/some-post pathname: '', // empty for /some-post, value for /pathname/some-post
noindex: false, noindex: false,
disabled: false, disabled: false,
}, },
category: { category: {
pathname: 'category', // set empty to change from /category/some-category to /some-category pathname: 'category', // set empty to change from /category/some-category to /some-category
noindex: true, noindex: true,
disabled: false, disabled: false,
}, },
tag: { tag: {
pathname: 'tag', // set empty to change from /tag/some-tag to /some-tag pathname: 'tag', // set empty to change from /tag/some-tag to /some-tag
noindex: true, noindex: true,
disabled: false, disabled: false,
}, },
}, },
}; };
export const SITE = { ...CONFIG, blog: undefined }; export const SITE = { ...CONFIG, blog: undefined };
export const BLOG = CONFIG.blog; export const BLOG = CONFIG.blog;
export const DATE_FORMATTER = CONFIG.dateFormatter; export const DATE_FORMATTER = CONFIG.dateFormatter;

View File

@ -184,7 +184,7 @@ import Logo from "~/components/atoms/Logo.astro";
``` ```
<div style="background:#eee;padding: 10px 5px"> <div style="background:#eee;padding: 10px 5px">
<Logo /> <Logo />
</div> </div>
[[Top]](#top) [[Top]](#top)

View File

@ -1,29 +1,27 @@
import { z, defineCollection } from 'astro:content'; import { z, defineCollection } from 'astro:content';
const blog = defineCollection({ const blog = defineCollection({
schema: { schema: {
title: z.string(), title: z.string(),
description: z.string().optional(), description: z.string().optional(),
image: z.string().optional(), image: z.string().optional(),
canonical: z.string().url().optional(), canonical: z.string().url().optional(),
permalink: z.string().optional(), permalink: z.string().optional(),
publishDate: z publishDate: z.date().or(z.string()).optional(),
.date().or(z.string()) draft: z.boolean().optional(),
.optional(),
draft: z.boolean().optional(),
excerpt: z.string().optional(), excerpt: z.string().optional(),
category: z.string().optional(), category: z.string().optional(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
author: z.string().optional(), author: z.string().optional(),
}, },
slug: ({ defaultSlug, data }) => { slug: ({ defaultSlug, data }) => {
return data.permalink || defaultSlug; return data.permalink || defaultSlug;
}, },
}); });
export const collections = { export const collections = {
blog: blog, blog: blog,
}; };

View File

@ -8,7 +8,7 @@ import { MetaSEO } from '~/types';
import { SITE } from '~/config.mjs'; import { SITE } from '~/config.mjs';
export interface Props { export interface Props {
meta?: MetaSEO; meta?: MetaSEO;
} }
const { meta = {} } = Astro.props; const { meta = {} } = Astro.props;
@ -17,17 +17,17 @@ const { language = 'en', textDirection = 'ltr' } = SITE;
<!DOCTYPE html> <!DOCTYPE html>
<html lang={language} dir={textDirection} class="2xl:text-[20px]"> <html lang={language} dir={textDirection} class="2xl:text-[20px]">
<head> <head>
<MetaTags {...meta} /> <MetaTags {...meta} />
</head> </head>
<body class="antialiased text-gray-900 dark:text-slate-300 tracking-tight bg-white dark:bg-slate-900"> <body class="antialiased text-gray-900 dark:text-slate-300 tracking-tight bg-white dark:bg-slate-900">
<slot /> <slot />
<BasicScripts /> <BasicScripts />
<style is:global> <style is:global>
img { img {
content-visibility: auto; content-visibility: auto;
} }
</style> </style>
</body> </body>
</html> </html>

View File

@ -11,22 +11,22 @@ export interface Props {}
const { frontmatter } = Astro.props; const { frontmatter } = Astro.props;
const meta: MetaSEO = { const meta: MetaSEO = {
title: frontmatter?.title, title: frontmatter?.title,
}; };
--- ---
<Layout {meta}> <Layout {meta}>
<Announcement /> <Announcement />
<Header /> <Header />
<main> <main>
<section class="px-4 py-16 sm:px-6 mx-auto lg:px-8 lg:py-20 max-w-4xl"> <section class="px-4 py-16 sm:px-6 mx-auto lg:px-8 lg:py-20 max-w-4xl">
<h1 class="font-bold font-heading text-3xl md:text-4xl leading-tighter tracking-tighter">{frontmatter.title}</h1> <h1 class="font-bold font-heading text-3xl md:text-4xl leading-tighter tracking-tighter">{frontmatter.title}</h1>
<div <div
class="mx-auto prose prose-lg max-w-4xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-img:rounded-md prose-img:shadow-lg mt-8" class="mx-auto prose prose-lg max-w-4xl dark:prose-invert dark:prose-headings:text-slate-300 prose-md prose-headings:font-heading prose-headings:leading-tighter prose-headings:tracking-tighter prose-headings:font-bold prose-a:text-primary-600 dark:prose-a:text-primary-400 prose-img:rounded-md prose-img:shadow-lg mt-8"
> >
<slot /> <slot />
</div> </div>
</section> </section>
</main> </main>
<Footer /> <Footer />
</Layout> </Layout>

View File

@ -7,17 +7,17 @@ import Announcement from '~/components/widgets/Announcement.astro';
import { MetaSEO } from '~/types'; import { MetaSEO } from '~/types';
export interface Props { export interface Props {
meta?: MetaSEO; meta?: MetaSEO;
} }
const { meta } = Astro.props; const { meta } = Astro.props;
--- ---
<Layout {meta}> <Layout {meta}>
<Announcement /> <Announcement />
<Header /> <Header />
<main> <main>
<slot /> <slot />
</main> </main>
<Footer /> <Footer />
</Layout> </Layout>

View File

@ -6,19 +6,19 @@ const title = `Error 404`;
--- ---
<Layout meta={{ title }}> <Layout meta={{ title }}>
<section class="flex items-center h-full p-16"> <section class="flex items-center h-full p-16">
<div class="container flex flex-col items-center justify-center px-5 mx-auto my-8"> <div class="container flex flex-col items-center justify-center px-5 mx-auto my-8">
<div class="max-w-md text-center"> <div class="max-w-md text-center">
<h2 class="mb-8 font-bold text-9xl"> <h2 class="mb-8 font-bold text-9xl">
<span class="sr-only">Error</span> <span class="sr-only">Error</span>
<span class="bg-clip-text text-transparent bg-gradient-to-r from-primary-500 to-secondary-500">404</span> <span class="bg-clip-text text-transparent bg-gradient-to-r from-primary-500 to-secondary-500">404</span>
</h2> </h2>
<p class="text-3xl font-semibold md:text-3xl">Sorry, we couldn't find this page.</p> <p class="text-3xl font-semibold md:text-3xl">Sorry, we couldn't find this page.</p>
<p class="mt-4 mb-8 text-lg text-gray-600 dark:text-slate-400"> <p class="mt-4 mb-8 text-lg text-gray-600 dark:text-slate-400">
But dont worry, you can find plenty of other things on our homepage. But dont worry, you can find plenty of other things on our homepage.
</p> </p>
<a rel="noopener noreferrer" href={getHomePermalink()} class="btn ml-4">Back to homepage</a> <a rel="noopener noreferrer" href={getHomePermalink()} class="btn ml-4">Back to homepage</a>
</div> </div>
</div> </div>
</section> </section>
</Layout> </Layout>

View File

@ -10,30 +10,32 @@ import { fetchPosts } from '~/utils/blog';
import { BLOG_BASE } from '~/utils/permalinks'; import { BLOG_BASE } from '~/utils/permalinks';
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
if (BLOG?.disabled || BLOG?.list?.disabled) return []; if (BLOG?.disabled || BLOG?.list?.disabled) return [];
return paginate(await fetchPosts(), { return paginate(await fetchPosts(), {
params: { blog: BLOG_BASE || undefined }, params: { blog: BLOG_BASE || undefined },
pageSize: BLOG.postsPerPage, pageSize: BLOG.postsPerPage,
}); });
} }
const { page } = Astro.props; const { page } = Astro.props;
const currentPage = page.currentPage ?? 1; const currentPage = page.currentPage ?? 1;
const meta = { const meta = {
title: `Blog${currentPage > 1 ? ` — Page ${currentPage}` : ''}`, title: `Blog${currentPage > 1 ? ` — Page ${currentPage}` : ''}`,
description: SITE.description, description: SITE.description,
noindex: BLOG?.list?.noindex || currentPage > 1, noindex: BLOG?.list?.noindex || currentPage > 1,
ogType: 'blog', ogType: 'blog',
}; };
--- ---
<Layout {meta}> <Layout {meta}>
<section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-4xl"> <section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-4xl">
<Headline subtitle="A statically generated blog example with news, tutorials, resources and other interesting content related to AstroWind"> <Headline
The Blog subtitle="A statically generated blog example with news, tutorials, resources and other interesting content related to AstroWind"
</Headline> >
<BlogList posts={page.data} /> The Blog
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} /> </Headline>
</section> <BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
</section>
</Layout> </Layout>

View File

@ -10,40 +10,40 @@ import { fetchPosts } from '~/utils/blog';
import { CATEGORY_BASE } from '~/utils/permalinks'; import { CATEGORY_BASE } from '~/utils/permalinks';
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
if (BLOG?.disabled || BLOG?.category?.disabled) return []; if (BLOG?.disabled || BLOG?.category?.disabled) return [];
const posts = await fetchPosts(); const posts = await fetchPosts();
const categories = new Set(); const categories = new Set();
posts.map((post) => { posts.map((post) => {
typeof post.category === 'string' && categories.add(post.category.toLowerCase()); typeof post.category === 'string' && categories.add(post.category.toLowerCase());
}); });
return Array.from(categories).map((category: string) => return Array.from(categories).map((category: string) =>
paginate( paginate(
posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()), posts.filter((post) => typeof post.category === 'string' && category === post.category.toLowerCase()),
{ {
params: { category: category, blog: CATEGORY_BASE || undefined }, params: { category: category, blog: CATEGORY_BASE || undefined },
pageSize: BLOG.postsPerPage, pageSize: BLOG.postsPerPage,
props: { category }, props: { category },
} }
) )
); );
} }
const { page, category } = Astro.props; const { page, category } = Astro.props;
const currentPage = page.currentPage ?? 1; const currentPage = page.currentPage ?? 1;
const meta = { const meta = {
title: `Category'${category}' ${currentPage > 1 ? ` — Page ${currentPage}` : ''}`, title: `Category'${category}' ${currentPage > 1 ? ` — Page ${currentPage}` : ''}`,
description: SITE.description, description: SITE.description,
noindex: BLOG?.category?.noindex, noindex: BLOG?.category?.noindex,
}; };
--- ---
<Layout meta={meta}> <Layout meta={meta}>
<section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-3xl"> <section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-3xl">
<Headline>Category {category}</Headline> <Headline>Category {category}</Headline>
<BlogList posts={page.data} /> <BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} /> <Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
</section> </section>
</Layout> </Layout>

View File

@ -10,30 +10,30 @@ import { fetchPosts } from '~/utils/blog';
import { findImage } from '~/utils/images'; import { findImage } from '~/utils/images';
export async function getStaticPaths() { export async function getStaticPaths() {
if (BLOG?.disabled || BLOG?.post?.disabled) return []; if (BLOG?.disabled || BLOG?.post?.disabled) return [];
return (await fetchPosts()).map((post) => ({ return (await fetchPosts()).map((post) => ({
params: { params: {
slug: post.slug, slug: post.slug,
blog: POST_BASE || undefined, blog: POST_BASE || undefined,
}, },
props: { post }, props: { post },
})); }));
} }
const { post } = Astro.props; const { post } = Astro.props;
const url = getCanonical(getPermalink(post.slug, 'post')); const url = getCanonical(getPermalink(post.slug, 'post'));
const meta = { const meta = {
title: post.title, title: post.title,
description: post.description, description: post.description,
canonical: post.canonical || undefined, canonical: post.canonical || undefined,
image: await findImage(post.image), image: await findImage(post.image),
noindex: BLOG?.post?.noindex, noindex: BLOG?.post?.noindex,
ogType: 'article', ogType: 'article',
}; };
--- ---
<Layout {meta}> <Layout {meta}>
<SinglePost post={{ ...post, image: meta.image }} url={url} /> <SinglePost post={{ ...post, image: meta.image }} url={url} />
<ToBlogLink /> <ToBlogLink />
</Layout> </Layout>

View File

@ -10,40 +10,40 @@ import { TAG_BASE } from '~/utils/permalinks';
import Headline from '~/components/blog/Headline.astro'; import Headline from '~/components/blog/Headline.astro';
export async function getStaticPaths({ paginate }) { export async function getStaticPaths({ paginate }) {
if (BLOG?.disabled || BLOG?.tag?.disabled) return []; if (BLOG?.disabled || BLOG?.tag?.disabled) return [];
const posts = await fetchPosts(); const posts = await fetchPosts();
const tags = new Set(); const tags = new Set();
posts.map((post) => { posts.map((post) => {
Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase())); Array.isArray(post.tags) && post.tags.map((tag) => tags.add(tag.toLowerCase()));
}); });
return Array.from(tags).map((tag: string) => return Array.from(tags).map((tag: string) =>
paginate( paginate(
posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)), posts.filter((post) => Array.isArray(post.tags) && post.tags.find((elem) => elem.toLowerCase() === tag)),
{ {
params: { tag: tag, blog: TAG_BASE || undefined }, params: { tag: tag, blog: TAG_BASE || undefined },
pageSize: BLOG.postsPerPage, pageSize: BLOG.postsPerPage,
props: { tag }, props: { tag },
} }
) )
); );
} }
const { page, tag } = Astro.props; const { page, tag } = Astro.props;
const currentPage = page.currentPage ?? 1; const currentPage = page.currentPage ?? 1;
const meta = { const meta = {
title: `Posts by tag '${tag}'${currentPage > 1 ? ` — Page ${currentPage} ` : ''}`, title: `Posts by tag '${tag}'${currentPage > 1 ? ` — Page ${currentPage} ` : ''}`,
description: SITE.description, description: SITE.description,
noindex: BLOG?.tag?.noindex, noindex: BLOG?.tag?.noindex,
}; };
--- ---
<Layout meta={meta}> <Layout meta={meta}>
<section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-3xl"> <section class="px-6 sm:px-6 py-12 sm:py-16 lg:py-20 mx-auto max-w-3xl">
<Headline>Tag: {tag}</Headline> <Headline>Tag: {tag}</Headline>
<BlogList posts={page.data} /> <BlogList posts={page.data} />
<Pagination prevUrl={page.url.prev} nextUrl={page.url.next} /> <Pagination prevUrl={page.url.prev} nextUrl={page.url.next} />
</section> </section>
</Layout> </Layout>

View File

@ -14,341 +14,335 @@ import Stats from '~/components/widgets/Stats.astro';
import CallToAction from '~/components/widgets/CallToAction.astro'; import CallToAction from '~/components/widgets/CallToAction.astro';
const meta = { const meta = {
title: SITE.title, title: SITE.title,
description: SITE.description, description: SITE.description,
dontUseTitleTemplate: true, dontUseTitleTemplate: true,
}; };
--- ---
<Layout {meta}> <Layout {meta}>
<!-- Hero Widget ******************* --> <!-- Hero Widget ******************* -->
<Hero <Hero
callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }} callToAction={{ text: 'Get template', href: 'https://github.com/onwidget/astrowind', icon: 'tabler:download' }}
callToAction2={{ text: 'Learn more', href: '#features' }} callToAction2={{ text: 'Learn more', href: '#features' }}
image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }} image={{ src: import('~/assets/images/hero.png'), alt: 'AstroWind Hero Image' }}
> >
<Fragment slot="title"> <Fragment slot="title">
Your website with <span>Astro</span> + <span class="sm:whitespace-nowrap">Tailwind CSS</span> Your website with <span>Astro</span> + <span class="sm:whitespace-nowrap">Tailwind CSS</span>
</Fragment> </Fragment>
<Fragment slot="subtitle"> <Fragment slot="subtitle">
<span class="hidden sm:inline"> <span class="hidden sm:inline">
<span class="font-semibold underline decoration-wavy decoration-1 decoration-primary-600 underline-offset-2" <span class="font-semibold underline decoration-wavy decoration-1 decoration-primary-600 underline-offset-2"
>AstroWind</span >AstroWind</span
> is a free, production-ready template to start your new website using <em>Astro</em> + <em>Tailwind CSS</em > is a free, production-ready template to start your new website using <em>Astro</em> + <em>Tailwind CSS</em
>.</span >.</span
> >
<span class="block mb-1 sm:hidden font-bold text-primary-600">AstroWind: Free template.</span> Suitable for Startups, <span class="block mb-1 sm:hidden font-bold text-primary-600">AstroWind: Free template.</span> Suitable for Startups,
Small Business, Sass Websites, Professional Portfolios, Marketing Websites, Landing Pages & Blogs. Small Business, Sass Websites, Professional Portfolios, Marketing Websites, Landing Pages & Blogs.
</Fragment> </Fragment>
</Hero> </Hero>
<!-- Note Widget ******************* --> <!-- Note Widget ******************* -->
<Note /> <Note />
<!-- Features Widget *************** --> <!-- Features Widget *************** -->
<Features <Features
highlight="Features" highlight="Features"
title="What you get with AstroWind" title="What you get with AstroWind"
subtitle="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque rem aperiam, eaque ipsa quae." subtitle="Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque rem aperiam, eaque ipsa quae."
items={[ items={[
[ [
{ {
title: 'Astro + Tailwind CSS Integration', title: 'Astro + Tailwind CSS Integration',
description: description:
'A seamless integration between two great frameworks that offer high productivity, performance and versatility.', 'A seamless integration between two great frameworks that offer high productivity, performance and versatility.',
icon: 'tabler:brand-tailwind', icon: 'tabler:brand-tailwind',
}, },
{ {
title: 'Ready-to-use Components', title: 'Ready-to-use Components',
description: description:
'Widgets made with Tailwind CSS ready to be used in Marketing Websites, SaaS, Blogs, Personal Profiles, Small Business...', 'Widgets made with Tailwind CSS ready to be used in Marketing Websites, SaaS, Blogs, Personal Profiles, Small Business...',
icon: 'tabler:components', icon: 'tabler:components',
}, },
{ {
title: 'Best Practices', title: 'Best Practices',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.',
icon: 'tabler:list-check', icon: 'tabler:list-check',
}, },
], ],
[ [
{ {
title: 'Excellent Page Speed', title: 'Excellent Page Speed',
description: description:
'Having a good page speed impacts organic search ranking, improves user experience (UI/UX) and increase conversion rates.', 'Having a good page speed impacts organic search ranking, improves user experience (UI/UX) and increase conversion rates.',
icon: 'tabler:rocket', icon: 'tabler:rocket',
}, },
{ {
title: 'Search Engine Optimization (SEO)', title: 'Search Engine Optimization (SEO)',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.',
icon: 'tabler:arrows-right-left', icon: 'tabler:arrows-right-left',
}, },
{ {
title: 'Open to new ideas and contributions', title: 'Open to new ideas and contributions',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mi risus tempus nulla.',
icon: 'tabler:bulb', icon: 'tabler:bulb',
}, },
], ],
]} ]}
/> />
<!-- Content Widget **************** --> <!-- Content Widget **************** -->
<Content <Content
highlight="Inside template" highlight="Inside template"
title="And what's inside? ..." title="And what's inside? ..."
content="Ne dicta praesent ocurreret has, diam theophrastus at pro. Eos etiam regione ut, persius eripuit quo id. Sit te euismod tacimates." content="Ne dicta praesent ocurreret has, diam theophrastus at pro. Eos etiam regione ut, persius eripuit quo id. Sit te euismod tacimates."
items={[ items={[
{ {
title: 'Per ei quaeque sensibus', title: 'Per ei quaeque sensibus',
description: description:
'Ex usu illum iudico molestie. Pro ne agam facete mediocritatem, ridens labore facete mea ei. Pro id apeirian dignissim.', 'Ex usu illum iudico molestie. Pro ne agam facete mediocritatem, ridens labore facete mea ei. Pro id apeirian dignissim.',
}, },
{ {
title: 'Cu imperdiet posidonium sed', title: 'Cu imperdiet posidonium sed',
description: description:
'Amet utinam aliquando ut mea, malis admodum ocurreret nec et, elit tibique cu nec. Nec ex maluisset inciderint, ex quis.', 'Amet utinam aliquando ut mea, malis admodum ocurreret nec et, elit tibique cu nec. Nec ex maluisset inciderint, ex quis.',
}, },
{ {
title: 'Nulla omittam sadipscing mel ne', title: 'Nulla omittam sadipscing mel ne',
description: description:
'At sed possim oporteat probatus, justo graece ne nec, minim commodo legimus ut vix. Ut eos iudico quando soleat, nam modus.', 'At sed possim oporteat probatus, justo graece ne nec, minim commodo legimus ut vix. Ut eos iudico quando soleat, nam modus.',
}, },
]} ]}
image={{ image={{
src: import('~/assets/images/caos.jpg'), src: import('~/assets/images/caos.jpg'),
alt: 'Colorful Image', alt: 'Colorful Image',
}} }}
/> />
<!-- Content Widget **************** --> <!-- Content Widget **************** -->
<Content <Content
isReversed isReversed
isAfterContent isAfterContent
items={[ items={[
{ {
title: 'Per ei quaeque sensibus', title: 'Per ei quaeque sensibus',
},
}, {
{ title: 'Cu imperdiet posidonium sed',
title: 'Cu imperdiet posidonium sed', },
{
}, title: 'Nulla omittam sadipscing mel ne',
{ },
title: 'Nulla omittam sadipscing mel ne', {
title: 'Per ei quaeque sensibus',
}, },
{ {
title: 'Per ei quaeque sensibus', title: 'Cu imperdiet posidonium sed',
},
}, {
{ title: 'Nulla omittam sadipscing mel ne',
title: 'Cu imperdiet posidonium sed', },
]}
}, image={{
{ src: import('~/assets/images/vintage.jpg'),
title: 'Nulla omittam sadipscing mel ne', alt: 'Vintage Image',
}}
}, />
]}
image={{
src: import('~/assets/images/vintage.jpg'),
alt: 'Vintage Image',
}}
/>
<!-- Steps Widget ****************** --> <!-- Steps Widget ****************** -->
<Steps <Steps
title="Get your dream website up and running in no time with AstroWind." title="Get your dream website up and running in no time with AstroWind."
items={[ items={[
{ {
title: 'Step 1: <span class="font-medium">Download</span>', title: 'Step 1: <span class="font-medium">Download</span>',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula.',
icon: 'tabler:package', icon: 'tabler:package',
}, },
{ {
title: 'Step 2: <span class="font-medium">Add content</em>', title: 'Step 2: <span class="font-medium">Add content</em>',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla.',
icon: 'tabler:letter-case', icon: 'tabler:letter-case',
}, },
{ {
title: 'Step 3: <span class="font-medium">Customize styles</span>', title: 'Step 3: <span class="font-medium">Customize styles</span>',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula. Fusce sit amet dui tellus.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi sagittis, quam nec venenatis lobortis, mirisus tempus nulla, sed porttitor est nibh at nulla. Praesent placerat enim ut ex tincidunt vehicula. Fusce sit amet dui tellus.',
icon: 'tabler:paint', icon: 'tabler:paint',
}, },
{ {
title: 'Ready!', title: 'Ready!',
icon: 'tabler:check', icon: 'tabler:check',
}, },
]} ]}
image={{ image={{
src: import('~/assets/images/creativity.jpg'), src: import('~/assets/images/creativity.jpg'),
alt: 'Steps image', alt: 'Steps image',
}} }}
/> />
<!-- Features2 Widget ************** --> <!-- Features2 Widget ************** -->
<Features2 <Features2
title="Most used widgets" title="Most used widgets"
subtitle="Provides frequently used components for building websites using Tailwind CSS" subtitle="Provides frequently used components for building websites using Tailwind CSS"
highlight="Components" highlight="Components"
items={[ items={[
{ {
title: 'Headers', title: 'Headers',
description: description:
'In general, Headers contain information that makes it easier for visitors to interact with the website.', 'In general, Headers contain information that makes it easier for visitors to interact with the website.',
icon: 'flat-color-icons:template', icon: 'flat-color-icons:template',
}, },
{ {
title: 'Heros', title: 'Heros',
description: description:
'If you want your website to get more than its fair share of visitors, the Hero section needs to be stellar.', 'If you want your website to get more than its fair share of visitors, the Hero section needs to be stellar.',
icon: 'flat-color-icons:gallery', icon: 'flat-color-icons:gallery',
}, },
{ {
title: 'Features', title: 'Features',
description: description:
'Display your product in action and how the Features actually create a solution for your target customer.', 'Display your product in action and how the Features actually create a solution for your target customer.',
icon: 'flat-color-icons:todo-list', icon: 'flat-color-icons:todo-list',
}, },
{ {
title: 'Content', title: 'Content',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'flat-color-icons:document', icon: 'flat-color-icons:document',
}, },
{ {
title: 'Call-to-Action', title: 'Call-to-Action',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'flat-color-icons:advertising', icon: 'flat-color-icons:advertising',
}, },
{ {
title: 'Pricing', title: 'Pricing',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'flat-color-icons:currency-exchange', icon: 'flat-color-icons:currency-exchange',
}, },
{ {
title: 'Testimonial', title: 'Testimonial',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'flat-color-icons:voice-presentation', icon: 'flat-color-icons:voice-presentation',
}, },
{ {
title: 'Contact', title: 'Contact',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'flat-color-icons:business-contact', icon: 'flat-color-icons:business-contact',
}, },
{ {
title: 'Footers', title: 'Footers',
description: description:
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore.',
icon: 'icon-park:page-template', icon: 'icon-park:page-template',
}, },
]} ]}
/> />
<!-- HighlightedPosts Widget ******* --> <!-- HighlightedPosts Widget ******* -->
<HighlightedPosts <HighlightedPosts
title="Find out more content in our Blog" title="Find out more content in our Blog"
information={`The blog is used to display AstroWind documentation. information={`The blog is used to display AstroWind documentation.
Each new article will be an important step that you will need to know to be an expert in creating a website using Astro + Tailwind CSS. Each new article will be an important step that you will need to know to be an expert in creating a website using Astro + Tailwind CSS.
Astro is a very interesting technology. Thanks. Astro is a very interesting technology. Thanks.
`} `}
postIds={[ postIds={[
'get-started-website-with-astro-tailwind-css.md', 'get-started-website-with-astro-tailwind-css.md',
'how-to-customize-astrowind-to-your-brand.md', 'how-to-customize-astrowind-to-your-brand.md',
'useful-resources-to-create-websites.md', 'useful-resources-to-create-websites.md',
'astrowind-template-in-depth.md', 'astrowind-template-in-depth.md',
]} ]}
/> />
<!-- FAQs Widget ******************* --> <!-- FAQs Widget ******************* -->
<FAQs <FAQs
title="Frequently Asked Questions" title="Frequently Asked Questions"
subtitle="Duis turpis dui, fringilla mattis sem nec, fringilla euismod neque. Morbi tincidunt lacus nec tortor scelerisque pulvinar." subtitle="Duis turpis dui, fringilla mattis sem nec, fringilla euismod neque. Morbi tincidunt lacus nec tortor scelerisque pulvinar."
highlight="FAQs" highlight="FAQs"
items={[ items={[
[ [
{ {
question: 'What do I need to start?', question: 'What do I need to start?',
answer: answer:
'Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds. Many say exploration is part of our destiny, but its actually our duty to future generations.', 'Space, the final frontier. These are the voyages of the Starship Enterprise. Its five-year mission: to explore strange new worlds. Many say exploration is part of our destiny, but its actually our duty to future generations.',
}, },
{ {
question: 'How to install the Astro + Tailwind CSS template?', question: 'How to install the Astro + Tailwind CSS template?',
answer: answer:
"Well, the way they make shows is, they make one show. That show's called a pilot. Then they show that show to the people who make shows, and on the strength of that one show they decide if they're going to make more shows. Some pilots get picked and become television programs. Some don't, become nothing. She starred in one of the ones that became nothing.", "Well, the way they make shows is, they make one show. That show's called a pilot. Then they show that show to the people who make shows, and on the strength of that one show they decide if they're going to make more shows. Some pilots get picked and become television programs. Some don't, become nothing. She starred in one of the ones that became nothing.",
}, },
{ {
question: "What's something that you don't understand?", question: "What's something that you don't understand?",
answer: answer:
"A flower in my garden, a mystery in my panties. Heart attack never stopped old Big Bear. I didn't even know we were calling him Big Bear.", "A flower in my garden, a mystery in my panties. Heart attack never stopped old Big Bear. I didn't even know we were calling him Big Bear.",
}, },
], ],
[ [
{ {
question: "What's an example of when you changed your mind?", question: "What's an example of when you changed your mind?",
answer: answer:
"Michael Knight a young loner on a crusade to champion the cause of the innocent. The helpless. The powerless in a world of criminals who operate above the law. Here he comes Here comes Speed Racer. He's a demon on wheels.", "Michael Knight a young loner on a crusade to champion the cause of the innocent. The helpless. The powerless in a world of criminals who operate above the law. Here he comes Here comes Speed Racer. He's a demon on wheels.",
}, },
{ {
question: 'What is something that you would like to try again?', question: 'What is something that you would like to try again?',
answer: answer:
"A business big enough that it could be listed on the NASDAQ goes belly up. Disappears! It ceases to exist without me. No, you clearly don't know who you're talking to, so let me clue you in.", "A business big enough that it could be listed on the NASDAQ goes belly up. Disappears! It ceases to exist without me. No, you clearly don't know who you're talking to, so let me clue you in.",
}, },
{ {
question: 'If you could only ask one question to each person you meet, what would that question be?', question: 'If you could only ask one question to each person you meet, what would that question be?',
answer: answer:
"This is not about revenge. This is about justice. A lot of things can change in twelve years, Admiral. Well, that's certainly good to know. About four years. I got tired of hearing how young I looked.", "This is not about revenge. This is about justice. A lot of things can change in twelve years, Admiral. Well, that's certainly good to know. About four years. I got tired of hearing how young I looked.",
}, },
], ],
]} ]}
/> />
<!-- Stats Widget ****************** --> <!-- Stats Widget ****************** -->
<Stats <Stats
items={[ items={[
{ name: 'Downloads', value: '132K' }, { name: 'Downloads', value: '132K' },
{ name: 'Stars', value: '24.8K' }, { name: 'Stars', value: '24.8K' },
{ name: 'Forks', value: '10.3K' }, { name: 'Forks', value: '10.3K' },
{ name: 'Users', value: '48.4K' }, { name: 'Users', value: '48.4K' },
]} ]}
/> />
<!-- CallToAction Widget *********** --> <!-- CallToAction Widget *********** -->
<CallToAction <CallToAction
callToAction={{ callToAction={{
text: 'Get template', text: 'Get template',
href: 'https://github.com/onwidget/astrowind', href: 'https://github.com/onwidget/astrowind',
icon: 'tabler:download', icon: 'tabler:download',
}} }}
> >
<Fragment slot="title"> <Fragment slot="title">
Astro + <br class="block sm:hidden" /><span class="sm:whitespace-nowrap">Tailwind CSS</span> Astro + <br class="block sm:hidden" /><span class="sm:whitespace-nowrap">Tailwind CSS</span>
</Fragment> </Fragment>
<Fragment slot="subtitle"> <Fragment slot="subtitle">
Be very surprised by these huge fake numbers you are seeing on this page. <br class="hidden md:inline" />Don't Be very surprised by these huge fake numbers you are seeing on this page. <br class="hidden md:inline" />Don't
waste more time! :P waste more time! :P
</Fragment> </Fragment>
</CallToAction> </CallToAction>
</Layout> </Layout>

View File

@ -5,25 +5,25 @@ import { fetchPosts } from '~/utils/blog';
import { getPermalink } from '~/utils/permalinks'; import { getPermalink } from '~/utils/permalinks';
export const get = async () => { export const get = async () => {
if (BLOG.disabled) { if (BLOG.disabled) {
return new Response(null, { return new Response(null, {
status: 404, status: 404,
statusText: 'Not found', statusText: 'Not found',
}); });
} }
const posts = await fetchPosts(); const posts = await fetchPosts();
return rss({ return rss({
title: `${SITE.name}s Blog`, title: `${SITE.name}s Blog`,
description: SITE.description, description: SITE.description,
site: import.meta.env.SITE, site: import.meta.env.SITE,
items: posts.map((post) => ({ items: posts.map((post) => ({
link: getPermalink(post.slug, 'post'), link: getPermalink(post.slug, 'post'),
title: post.title, title: post.title,
description: post.description, description: post.description,
pubDate: post.publishDate, pubDate: post.publishDate,
})), })),
}); });
}; };

View File

@ -1,37 +1,37 @@
export interface Post { export interface Post {
id: string; id: string;
slug: string; slug: string;
publishDate: Date; publishDate: Date;
title: string; title: string;
description?: string; description?: string;
image?: string; image?: string;
canonical?: string | URL; canonical?: string | URL;
permalink?: string; permalink?: string;
draft?: boolean; draft?: boolean;
excerpt?: string; excerpt?: string;
category?: string; category?: string;
tags?: Array<string>; tags?: Array<string>;
author?: string; author?: string;
Content: unknown; Content: unknown;
content?: string; content?: string;
readingTime: number; readingTime: number;
} }
export interface MetaSEO { export interface MetaSEO {
title?: string; title?: string;
description?: string; description?: string;
image?: string; image?: string;
canonical?: string | URL; canonical?: string | URL;
noindex?: boolean; noindex?: boolean;
nofollow?: boolean; nofollow?: boolean;
ogTitle?: string; ogTitle?: string;
ogType?: string; ogType?: string;
} }

View File

@ -4,81 +4,81 @@ import type { Post } from '~/types';
import { cleanSlug } from './permalinks'; import { cleanSlug } from './permalinks';
const getNormalizedPost = async (post: CollectionEntry<'blog'>): Promise<Post> => { const getNormalizedPost = async (post: CollectionEntry<'blog'>): Promise<Post> => {
const { id, slug = '', data } = post; const { id, slug = '', data } = post;
const { Content, injectedFrontmatter } = await post.render(); const { Content, injectedFrontmatter } = await post.render();
const { tags = [], category = 'default', author = 'Anonymous', publishDate = new Date(), ...rest } = data; const { tags = [], category = 'default', author = 'Anonymous', publishDate = new Date(), ...rest } = data;
return { return {
id: id, id: id,
slug: cleanSlug(slug.split('/').pop() ?? ''), slug: cleanSlug(slug.split('/').pop() ?? ''),
publishDate: new Date(publishDate), publishDate: new Date(publishDate),
category: cleanSlug(category), category: cleanSlug(category),
tags: tags.map((tag: string) => cleanSlug(tag)), tags: tags.map((tag: string) => cleanSlug(tag)),
author, author,
...rest, ...rest,
Content: Content, Content: Content,
// or 'body' in case you consume from API // or 'body' in case you consume from API
readingTime: injectedFrontmatter.readingTime, readingTime: injectedFrontmatter.readingTime,
}; };
}; };
const load = async function (): Promise<Array<Post>> { const load = async function (): Promise<Array<Post>> {
const posts = await getCollection('blog'); const posts = await getCollection('blog');
const normalizedPosts = posts.map(async (post) => await getNormalizedPost(post)); const normalizedPosts = posts.map(async (post) => await getNormalizedPost(post));
const results = (await Promise.all(normalizedPosts)) const results = (await Promise.all(normalizedPosts))
.sort((a, b) => b.publishDate.valueOf() - a.publishDate.valueOf()) .sort((a, b) => b.publishDate.valueOf() - a.publishDate.valueOf())
.filter((post) => !post.draft); .filter((post) => !post.draft);
return results; return results;
}; };
let _posts: Array<Post>; let _posts: Array<Post>;
/** */ /** */
export const fetchPosts = async (): Promise<Array<Post>> => { export const fetchPosts = async (): Promise<Array<Post>> => {
if (!_posts) { if (!_posts) {
_posts = await load(); _posts = await load();
} }
return _posts; return _posts;
}; };
/** */ /** */
export const findPostsBySlugs = async (slugs: Array<string>): Promise<Array<Post>> => { export const findPostsBySlugs = async (slugs: Array<string>): Promise<Array<Post>> => {
if (!Array.isArray(slugs)) return []; if (!Array.isArray(slugs)) return [];
const posts = await fetchPosts(); const posts = await fetchPosts();
return slugs.reduce(function (r: Array<Post>, slug: string) { return slugs.reduce(function (r: Array<Post>, slug: string) {
posts.some(function (post: Post) { posts.some(function (post: Post) {
return slug === post.slug && r.push(post); return slug === post.slug && r.push(post);
}); });
return r; return r;
}, []); }, []);
}; };
/** */ /** */
export const findPostsByIds = async (ids: Array<string>): Promise<Array<Post>> => { export const findPostsByIds = async (ids: Array<string>): Promise<Array<Post>> => {
if (!Array.isArray(ids)) return []; if (!Array.isArray(ids)) return [];
return await Promise.all( return await Promise.all(
ids.map(async (id: never) => { ids.map(async (id: never) => {
const post = await getEntry('blog', id); const post = await getEntry('blog', id);
return await getNormalizedPost(post); return await getNormalizedPost(post);
}) })
); );
}; };
/** */ /** */
export const findLatestPosts = async ({ count }: { count?: number }): Promise<Array<Post>> => { export const findLatestPosts = async ({ count }: { count?: number }): Promise<Array<Post>> => {
const _count = count || 4; const _count = count || 4;
const posts = await fetchPosts(); const posts = await fetchPosts();
return posts ? posts.slice(_count * -1) : []; return posts ? posts.slice(_count * -1) : [];
}; };

View File

@ -5,14 +5,14 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
/** */ /** */
export const getProjectRootDir = (): string => { export const getProjectRootDir = (): string => {
const mode = import.meta.env.MODE; const mode = import.meta.env.MODE;
return mode === 'production' ? path.join(__dirname, '../') : path.join(__dirname, '../../'); return mode === 'production' ? path.join(__dirname, '../') : path.join(__dirname, '../../');
}; };
const __srcFolder = path.join(getProjectRootDir(), '/src'); const __srcFolder = path.join(getProjectRootDir(), '/src');
/** */ /** */
export const getRelativeUrlByFilePath = (filepath: string): string => { export const getRelativeUrlByFilePath = (filepath: string): string => {
return filepath.replace(__srcFolder, ''); return filepath.replace(__srcFolder, '');
}; };

View File

@ -2,10 +2,10 @@ import getReadingTime from 'reading-time';
import { toString } from 'mdast-util-to-string'; import { toString } from 'mdast-util-to-string';
export function remarkReadingTime() { export function remarkReadingTime() {
return function (tree, { data }) { return function (tree, { data }) {
const text = toString(tree); const text = toString(tree);
const readingTime = Math.ceil(getReadingTime(text).minutes); const readingTime = Math.ceil(getReadingTime(text).minutes);
data.astro.frontmatter.readingTime = readingTime; data.astro.frontmatter.readingTime = readingTime;
}; };
} }

View File

@ -1,37 +1,37 @@
const load = async function () { const load = async function () {
let images: Record<string, () => Promise<unknown>> | undefined = undefined; let images: Record<string, () => Promise<unknown>> | undefined = undefined;
try { try {
images = import.meta.glob('~/assets/images/**'); images = import.meta.glob('~/assets/images/**');
} catch (e) { } catch (e) {
// continue regardless of error // continue regardless of error
} }
return images; return images;
}; };
let _images; let _images;
/** */ /** */
export const fetchLocalImages = async () => { export const fetchLocalImages = async () => {
_images = _images || load(); _images = _images || load();
return await _images; return await _images;
}; };
/** */ /** */
export const findImage = async (imagePath?: string) => { export const findImage = async (imagePath?: string) => {
if (typeof imagePath !== 'string') { if (typeof imagePath !== 'string') {
return null; return null;
} }
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) { if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
return imagePath; return imagePath;
} }
if (!imagePath.startsWith('~/assets')) { if (!imagePath.startsWith('~/assets')) {
return null; return null;
} // For now only consume images using ~/assets alias (or absolute) } // For now only consume images using ~/assets alias (or absolute)
const images = await fetchLocalImages(); const images = await fetchLocalImages();
const key = imagePath.replace('~/', '/src/'); const key = imagePath.replace('~/', '/src/');
return typeof images[key] === 'function' ? (await images[key]())['default'] : null; return typeof images[key] === 'function' ? (await images[key]())['default'] : null;
}; };

View File

@ -5,20 +5,20 @@ import { trim } from '~/utils/utils';
const trimSlash = (s: string) => trim(trim(s, '/')); const trimSlash = (s: string) => trim(trim(s, '/'));
const createPath = (...params: string[]) => { const createPath = (...params: string[]) => {
const paths = params const paths = params
.map((el) => trimSlash(el)) .map((el) => trimSlash(el))
.filter((el) => !!el) .filter((el) => !!el)
.join('/'); .join('/');
return '/' + paths + (SITE.trailingSlash && paths ? '/' : ''); return '/' + paths + (SITE.trailingSlash && paths ? '/' : '');
}; };
const BASE_PATHNAME = SITE.basePathname; const BASE_PATHNAME = SITE.basePathname;
export const cleanSlug = (text: string) => export const cleanSlug = (text: string) =>
trimSlash(text) trimSlash(text)
.split('/') .split('/')
.map((slug) => slugify(slug)) .map((slug) => slugify(slug))
.join('/'); .join('/');
export const BLOG_BASE = cleanSlug(BLOG?.list?.pathname); export const BLOG_BASE = cleanSlug(BLOG?.list?.pathname);
export const POST_BASE = cleanSlug(BLOG?.post?.pathname); export const POST_BASE = cleanSlug(BLOG?.post?.pathname);
@ -30,28 +30,28 @@ export const getCanonical = (path = ''): string | URL => new URL(path, SITE.orig
/** */ /** */
export const getPermalink = (slug = '', type = 'page'): string => { export const getPermalink = (slug = '', type = 'page'): string => {
let permalink: string; let permalink: string;
switch (type) { switch (type) {
case 'category': case 'category':
permalink = createPath(CATEGORY_BASE, cleanSlug(slug)); permalink = createPath(CATEGORY_BASE, cleanSlug(slug));
break; break;
case 'tag': case 'tag':
permalink = createPath(TAG_BASE, cleanSlug(slug)); permalink = createPath(TAG_BASE, cleanSlug(slug));
break; break;
case 'post': case 'post':
permalink = createPath(POST_BASE, cleanSlug(slug)); permalink = createPath(POST_BASE, cleanSlug(slug));
break; break;
case 'page': case 'page':
default: default:
permalink = createPath(slug); permalink = createPath(slug);
break; break;
} }
return definitivePermalink(permalink); return definitivePermalink(permalink);
}; };
/** */ /** */

View File

@ -1,21 +1,21 @@
import { DATE_FORMATTER } from '~/config.mjs'; import { DATE_FORMATTER } from '~/config.mjs';
const formatter = const formatter =
DATE_FORMATTER || DATE_FORMATTER ||
new Intl.DateTimeFormat('en', { new Intl.DateTimeFormat('en', {
year: 'numeric', year: 'numeric',
month: 'short', month: 'short',
day: 'numeric', day: 'numeric',
timeZone: 'UTC' timeZone: 'UTC',
}); });
/* eslint-disable no-mixed-spaces-and-tabs */ /* eslint-disable no-mixed-spaces-and-tabs */
export const getFormattedDate = (date: Date) => (date ? formatter.format(date) : ''); export const getFormattedDate = (date: Date) => (date ? formatter.format(date) : '');
export const trim = (str = '', ch?: string) => { export const trim = (str = '', ch?: string) => {
let start = 0, let start = 0,
end = str.length || 0; end = str.length || 0;
while (start < end && str[start] === ch) ++start; while (start < end && str[start] === ch) ++start;
while (end > start && str[end - 1] === ch) --end; while (end > start && str[end - 1] === ch) --end;
return start > 0 || end < str.length ? str.substring(start, end) : str; return start > 0 || end < str.length ? str.substring(start, end) : str;
}; };

View File

@ -2,20 +2,20 @@ const defaultTheme = require('tailwindcss/defaultTheme');
const colors = require('tailwindcss/colors'); const colors = require('tailwindcss/colors');
module.exports = { module.exports = {
content: ['./src/**/*.{astro,html,js,jsx,md,svelte,ts,tsx,vue}'], content: ['./src/**/*.{astro,html,js,jsx,md,svelte,ts,tsx,vue}'],
theme: { theme: {
extend: { extend: {
colors: { colors: {
primary: colors.blue, primary: colors.blue,
secondary: colors.pink, secondary: colors.pink,
}, },
fontFamily: { fontFamily: {
sans: ["'InterVariable'", ...defaultTheme.fontFamily.sans], sans: ["'InterVariable'", ...defaultTheme.fontFamily.sans],
}, },
}, },
}, },
plugins: [require('@tailwindcss/typography')], plugins: [require('@tailwindcss/typography')],
darkMode: 'class', darkMode: 'class',
}; };
/* /*