@@ -76,6 +80,7 @@ const { post, url } = Astro.props;
alt={post?.excerpt || ''}
width={900}
height={506}
+ format="webp"
loading="eager"
decoding="async"
/>
diff --git a/src/components/blog/SinglePost2.astro b/src/components/blog/SinglePost2.astro
index a15ce534..18f47f1a 100644
--- a/src/components/blog/SinglePost2.astro
+++ b/src/components/blog/SinglePost2.astro
@@ -20,7 +20,11 @@ const { post, url } = Astro.props;
-
+
diff --git a/src/components/common/Analytics.astro b/src/components/common/Analytics.astro
deleted file mode 100644
index a1a553dd..00000000
--- a/src/components/common/Analytics.astro
+++ /dev/null
@@ -1,13 +0,0 @@
----
-import { GoogleAnalytics } from '@astrolib/analytics';
-import { ANALYTICS } from 'astrowind:config';
----
-
-{
- ANALYTICS?.vendors?.googleAnalytics?.id ? (
-
- ) : null
-}
diff --git a/src/components/common/BasicScripts.astro b/src/components/common/BasicScripts.astro
index 4ee6a640..95520122 100644
--- a/src/components/common/BasicScripts.astro
+++ b/src/components/common/BasicScripts.astro
@@ -68,6 +68,9 @@ import { UI } from 'astrowind:config';
if (defaultTheme.endsWith(':only')) {
return;
}
+
+ Observer.removeAnimationDelay();
+
document.documentElement.classList.toggle('dark');
localStorage.theme = document.documentElement.classList.contains('dark') ? 'dark' : 'light';
});
@@ -160,3 +163,134 @@ import { UI } from 'astrowind:config';
onPageShow();
});
+
+
diff --git a/src/components/common/Image.astro b/src/components/common/Image.astro
index 80dab3f3..a48affbf 100644
--- a/src/components/common/Image.astro
+++ b/src/components/common/Image.astro
@@ -1,64 +1,192 @@
---
+/**
+ * AstroWind wrapper.
+ *
+ * Behaviour:
+ * • Detectable CDN URL (Unsplash, Cloudinary, Imgix…) → rewritten with
+ * CDN-side parameters via `transformUrl` from `unpic` and emitted as a
+ * plain
served straight from the source. No download, no Sharp.
+ * • Local image (ESM import or `~/assets/...` path) → delegated to Astro's
+ * native from `astro:assets`, which optimizes through Sharp.
+ *
+ * Layout modes (control how the image fills its container):
+ * • `constrained` (default) — image scales down to fit, never above its own
+ * pixel dimensions. Heroes, brand logos, blog thumbnails.
+ * • `responsive` — image always 100% of container width, height auto from
+ * aspect ratio. Side-by-side feature illustrations.
+ * • `fullWidth` — image spans the container width, fixed pixel height.
+ * Full-bleed banners.
+ * • `fixed` — exact pixel dimensions, no scaling. Avatars and icons.
+ * • `cover` — fills the parent (max 100%) and crops with object-fit.
+ * Absolutely-positioned overlays (cards, step illustrations).
+ *
+ * Accessibility:
+ * `alt` is required. Pass `alt=""` explicitly for purely decorative images.
+ */
+import { Image as AstroImage } from 'astro:assets';
+import type { ImageMetadata } from 'astro';
+import type { HTMLAttributes } from 'astro/types';
+import { transformUrl, parseUrl } from 'unpic';
import { findImage } from '~/utils/images';
-import {
- getImagesOptimized,
- astroAsseetsOptimizer,
- unpicOptimizer,
- isUnpicCompatible,
- type ImageProps,
- type AttributesProps,
-} from '~/utils/images-optimization';
-
-type Props = ImageProps;
-type ImageType = {
- src: string;
- attributes: AttributesProps;
-};
-const props = Astro.props;
+type Layout = 'constrained' | 'responsive' | 'fullWidth' | 'fixed' | 'cover';
+type Format = 'avif' | 'jpeg' | 'jpg' | 'png' | 'webp';
-if (props.alt === undefined || props.alt === null) {
- throw new Error();
+interface Props extends Omit, 'src' | 'width' | 'height'> {
+ src?: string | ImageMetadata | null;
+ width?: number | string;
+ height?: number | string;
+ widths?: number[];
+ format?: Format;
+ layout?: Layout;
}
-if (typeof props.width === 'string') {
- props.width = parseInt(props.width);
-}
+const { src, alt, width, height, widths, format, layout = 'constrained', ...rest } = Astro.props;
-if (typeof props.height === 'string') {
- props.height = parseInt(props.height);
+// --- Accessibility ---------------------------------------------------------
+if (alt === undefined || alt === null) {
+ throw new Error(
+ `Image: missing required \`alt\` prop (src=${String(src)}). ` + `Pass alt="" explicitly for decorative images.`
+ );
}
-if (!props.loading) {
- props.loading = 'lazy';
-}
+// --- Prop coercion (tolerate stringified numbers) --------------------------
+const toNumber = (v: number | string | undefined): number | undefined =>
+ typeof v === 'string' ? (Number.isFinite(Number(v)) ? Number(v) : undefined) : v;
+let w = toNumber(width);
+let h = toNumber(height);
-if (!props.decoding) {
- props.decoding = 'async';
-}
+// --- Defaults --------------------------------------------------------------
+rest.loading ??= 'lazy';
+rest.decoding ??= 'async';
-const _image = await findImage(props.src);
+// --- Resolve src (handles `~/assets/...` paths) ----------------------------
+const resolved = await findImage(src);
-let image: ImageType | undefined = undefined;
+const isCdnUrl =
+ typeof resolved === 'string' &&
+ (resolved.startsWith('http://') || resolved.startsWith('https://')) &&
+ parseUrl(resolved) !== undefined;
-if (typeof _image === 'string') {
- if ((_image.startsWith('http://') || _image.startsWith('https://')) && isUnpicCompatible(_image)) {
- image = await getImagesOptimized(_image, props, unpicOptimizer);
- } else {
- image = {
- src: _image,
- attributes: { ...props, src: undefined },
- };
+// Infer missing dimension(s) from ESM-imported ImageMetadata. If only one of
+// width/height is provided, derive the other from the source aspect ratio.
+if (resolved && typeof resolved === 'object' && 'width' in resolved && 'height' in resolved) {
+ const { width: srcW, height: srcH } = resolved;
+ if (!w && !h) {
+ w = srcW;
+ h = srcH;
+ } else if (w && !h) {
+ h = Math.round((w * srcH) / srcW);
+ } else if (h && !w) {
+ w = Math.round((h * srcW) / srcH);
}
-} else if (_image) {
- image = await getImagesOptimized(_image, props, astroAsseetsOptimizer);
}
+
+const aspectRatio = w && h ? w / h : undefined;
+
+// --- Layout → inline style mapping -----------------------------------------
+const px = (v?: number) => (v == null ? undefined : `${v}px`);
+const declarations: Array<[string, string | undefined]> = [
+ ['object-fit', 'cover'],
+ ['object-position', 'center'],
+];
+
+switch (layout) {
+ case 'fixed':
+ declarations.push(['width', px(w)], ['height', px(h)], ['object-position', 'top left']);
+ break;
+ case 'constrained':
+ declarations.push(
+ ['max-width', px(w)],
+ ['max-height', px(h)],
+ ['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined],
+ ['width', '100%']
+ );
+ break;
+ case 'fullWidth':
+ declarations.push(['width', '100%'], ['height', px(h)]);
+ break;
+ case 'responsive':
+ declarations.push(
+ ['width', '100%'],
+ ['height', 'auto'],
+ ['aspect-ratio', aspectRatio ? `${aspectRatio}` : undefined]
+ );
+ break;
+ case 'cover':
+ declarations.push(['max-width', '100%'], ['max-height', '100%']);
+ break;
+}
+
+const layoutStyle = declarations
+ .filter(([, value]) => value)
+ .map(([key, value]) => `${key}:${value}`)
+ .join(';');
+const finalStyle = layoutStyle + (rest.style ? `;${rest.style}` : '');
+
+// --- CDN-side srcset -------------------------------------------------------
+const buildCdnUrl = (variantWidth?: number, variantHeight?: number): string | undefined =>
+ transformUrl({
+ url: resolved as string,
+ width: variantWidth,
+ height: variantHeight,
+ fallback: 'astro',
+ })?.toString();
+
+/**
+ * Densify the user-provided srcset breakpoints so the browser can match the
+ * actual display size more closely. Free for CDN URLs (variants are computed
+ * provider-side, no local CPU cost).
+ */
+const CDN_BREAKPOINTS = [320, 400, 500, 640, 720, 828, 960, 1080, 1200, 1440, 1600, 1920, 2048];
+const expandWidths = (raw?: number[]): number[] | undefined => {
+ if (!Array.isArray(raw) || raw.length === 0) return raw;
+ const min = Math.min(...raw);
+ const max = Math.max(...raw);
+ const extras = CDN_BREAKPOINTS.filter((bp) => bp > min && bp < max);
+ return [...new Set([...raw, ...extras])].sort((a, b) => a - b);
+};
+
+const heightFromWidth = (variantWidth: number): number | undefined =>
+ aspectRatio ? Math.round(variantWidth / aspectRatio) : undefined;
+
+const cdnSrc = isCdnUrl ? buildCdnUrl(w, h) : undefined;
+const cdnWidths = isCdnUrl ? expandWidths(widths) : undefined;
+const cdnSrcSet =
+ isCdnUrl && cdnWidths?.length
+ ? cdnWidths.map((bw) => `${buildCdnUrl(bw, heightFromWidth(bw))} ${bw}w`).join(', ')
+ : undefined;
---
{
- !image ? (
-
+ isCdnUrl ? (
+
) : (
-
+ /* The native typing is overly strict for our forwarding wrapper;
+ we cast through `unknown` once so each call site keeps clean types. */
+ resolved && (
+ [0])}
+ />
+ )
)
}
diff --git a/src/components/common/Metadata.astro b/src/components/common/Metadata.astro
index a4c573eb..b4dc9d6e 100644
--- a/src/components/common/Metadata.astro
+++ b/src/components/common/Metadata.astro
@@ -1,11 +1,11 @@
---
import merge from 'lodash.merge';
-import { AstroSeo } from '@astrolib/seo';
+import { SEO } from 'astro-seo';
-import type { Props as AstroSeoProps } from '@astrolib/seo';
+import type { SEOProps } from 'astro-seo';
import { SITE, METADATA, I18N } from 'astrowind:config';
-import type { MetaData } from '~/types';
+import type { MetaData, MetaDataOpenGraph } from '~/types';
import { getCanonical } from '~/utils/permalinks';
import { adaptOpenGraphImages } from '~/utils/images';
@@ -24,45 +24,90 @@ const {
twitter = {},
} = Astro.props;
-const seoProps: AstroSeoProps = merge(
+const mergedOg: MetaDataOpenGraph = merge(
{
- title: '',
- titleTemplate: '%s',
- canonical: canonical,
- noindex: true,
- nofollow: true,
- description: undefined,
- openGraph: {
- url: canonical,
- site_name: SITE?.name,
- images: [],
- locale: I18N?.language || 'en',
- type: 'website',
+ url: canonical,
+ siteName: SITE?.name,
+ images: [],
+ locale: I18N?.language || 'en',
+ type: 'website',
+ },
+ METADATA?.openGraph,
+ { url: canonical, ...openGraph }
+);
+
+const adaptedOg = await adaptOpenGraphImages(mergedOg, Astro.site);
+
+const firstImage = adaptedOg?.images?.[0];
+
+function buildOpenGraph(og: MetaDataOpenGraph): SEOProps['openGraph'] | undefined {
+ if (!og) return undefined;
+
+ const imageUrl = firstImage?.url || '';
+ if (!imageUrl) return undefined;
+
+ return {
+ basic: {
+ title: title || METADATA?.title?.default || '',
+ type: og.type || 'website',
+ image: imageUrl,
+ url: og.url,
},
- twitter: {
- cardType: openGraph?.images?.length ? 'summary_large_image' : 'summary',
+ optional: {
+ description: description || METADATA?.description,
+ locale: og.locale,
+ siteName: og.siteName,
},
- },
+ image: {
+ url: imageUrl,
+ width: firstImage?.width,
+ height: firstImage?.height,
+ alt: title || METADATA?.title?.default || '',
+ },
+ };
+}
+
+const mergedTwitter = merge(
{
- title: METADATA?.title?.default,
- titleTemplate: METADATA?.title?.template,
- noindex: typeof METADATA?.robots?.index !== 'undefined' ? !METADATA.robots.index : undefined,
- nofollow: typeof METADATA?.robots?.follow !== 'undefined' ? !METADATA.robots.follow : undefined,
- description: METADATA?.description,
- openGraph: METADATA?.openGraph,
- twitter: METADATA?.twitter,
+ card: (adaptedOg?.images?.length ? 'summary_large_image' : 'summary') as 'summary_large_image' | 'summary',
},
- {
- title: title,
- titleTemplate: ignoreTitleTemplate ? '%s' : undefined,
- canonical: canonical,
- noindex: typeof robots?.index !== 'undefined' ? !robots.index : undefined,
- nofollow: typeof robots?.follow !== 'undefined' ? !robots.follow : undefined,
- description: description,
- openGraph: { url: canonical, ...openGraph },
- twitter: twitter,
- }
+ METADATA?.twitter
+ ? {
+ card: METADATA.twitter.cardType as 'summary_large_image' | 'summary' | undefined,
+ site: METADATA.twitter.site,
+ creator: METADATA.twitter.handle,
+ }
+ : {},
+ twitter
+ ? {
+ card: twitter.cardType as 'summary_large_image' | 'summary' | undefined,
+ site: twitter.site,
+ creator: twitter.handle,
+ }
+ : {}
);
+
+const seoProps: SEOProps = {
+ title: title || METADATA?.title?.default || '',
+ titleTemplate: ignoreTitleTemplate ? '%s' : METADATA?.title?.template || '%s',
+ canonical: canonical,
+ noindex:
+ typeof robots?.index !== 'undefined'
+ ? !robots.index
+ : typeof METADATA?.robots?.index !== 'undefined'
+ ? !METADATA.robots.index
+ : true,
+ nofollow:
+ typeof robots?.follow !== 'undefined'
+ ? !robots.follow
+ : typeof METADATA?.robots?.follow !== 'undefined'
+ ? !METADATA.robots.follow
+ : true,
+ description: description || METADATA?.description,
+ openGraph: buildOpenGraph(adaptedOg),
+ twitter: mergedTwitter,
+ surpressWarnings: true,
+};
---
-
+
diff --git a/src/components/ui/WidgetWrapper.astro b/src/components/ui/WidgetWrapper.astro
index a53df1d7..3adb0bee 100644
--- a/src/components/ui/WidgetWrapper.astro
+++ b/src/components/ui/WidgetWrapper.astro
@@ -2,7 +2,6 @@
import type { HTMLTag } from 'astro/types';
import type { Widget } from '~/types';
import { twMerge } from 'tailwind-merge';
-import Background from './Background.astro';
export interface Props extends Widget {
containerClass?: string;
@@ -17,12 +16,21 @@ const WrapperTag = as;
- {bg ? : }
+ {
+ bg ? (
+
+ ) : (
+
+ )
+ }
diff --git a/src/components/widgets/Announcement.astro b/src/components/widgets/Announcement.astro
index 2d036bab..6fe4710c 100644
--- a/src/components/widgets/Announcement.astro
+++ b/src/components/widgets/Announcement.astro
@@ -3,7 +3,7 @@
---
+ z
+ .object({
+ title: z.string().optional(),
+ ignoreTitleTemplate: z.boolean().optional(),
+
+ canonical: z.url().optional(),
+
+ robots: z
+ .object({
+ index: z.boolean().optional(),
+ follow: z.boolean().optional(),
+ })
+ .optional(),
+
+ description: z.string().optional(),
+
+ openGraph: z
+ .object({
+ url: z.string().optional(),
+ siteName: z.string().optional(),
+ images: z
+ .array(
+ z.object({
+ url: z.string(),
+ width: z.number().optional(),
+ height: z.number().optional(),
+ })
+ )
+ .optional(),
+ locale: z.string().optional(),
+ type: z.string().optional(),
+ })
+ .optional(),
+
+ twitter: z
+ .object({
+ handle: z.string().optional(),
+ site: z.string().optional(),
+ cardType: z.string().optional(),
+ })
+ .optional(),
+ })
+ .optional();
+
+const postCollection = defineCollection({
+ loader: glob({ pattern: ['*.md', '*.mdx'], base: 'src/data/post' }),
+ schema: z.object({
+ publishDate: z.date().optional(),
+ updateDate: z.date().optional(),
+ draft: z.boolean().optional(),
+
+ title: z.string(),
+ excerpt: z.string().optional(),
+ image: z.string().optional(),
+
+ category: z.string().optional(),
+ tags: z.array(z.string()).optional(),
+ author: z.string().optional(),
+
+ metadata: metadataDefinition(),
+ }),
+});
+
+export const collections = {
+ post: postCollection,
+};
diff --git a/src/content/config.ts b/src/data/config.ts
similarity index 100%
rename from src/content/config.ts
rename to src/data/config.ts
diff --git a/src/content/post/2024-06-25-performance-problems.mdx b/src/data/post/2024-06-25-performance-problems.mdx
similarity index 100%
rename from src/content/post/2024-06-25-performance-problems.mdx
rename to src/data/post/2024-06-25-performance-problems.mdx
diff --git a/src/content/post/2024-06-29-Future-of-lychee.mdx b/src/data/post/2024-06-29-Future-of-lychee.mdx
similarity index 100%
rename from src/content/post/2024-06-29-Future-of-lychee.mdx
rename to src/data/post/2024-06-29-Future-of-lychee.mdx
diff --git a/src/content/post/2024-07-02-v6-landing-page.mdx b/src/data/post/2024-07-02-v6-landing-page.mdx
similarity index 100%
rename from src/content/post/2024-07-02-v6-landing-page.mdx
rename to src/data/post/2024-07-02-v6-landing-page.mdx
diff --git a/src/content/post/2024-07-06-v6-about.mdx b/src/data/post/2024-07-06-v6-about.mdx
similarity index 100%
rename from src/content/post/2024-07-06-v6-about.mdx
rename to src/data/post/2024-07-06-v6-about.mdx
diff --git a/src/content/post/2024-07-07-v6-gallery-1.mdx b/src/data/post/2024-07-07-v6-gallery-1.mdx
similarity index 100%
rename from src/content/post/2024-07-07-v6-gallery-1.mdx
rename to src/data/post/2024-07-07-v6-gallery-1.mdx
diff --git a/src/content/post/2024-07-09-v6-gallery-2.mdx b/src/data/post/2024-07-09-v6-gallery-2.mdx
similarity index 100%
rename from src/content/post/2024-07-09-v6-gallery-2.mdx
rename to src/data/post/2024-07-09-v6-gallery-2.mdx
diff --git a/src/content/post/2024-07-12-v6-gallery-3.mdx b/src/data/post/2024-07-12-v6-gallery-3.mdx
similarity index 100%
rename from src/content/post/2024-07-12-v6-gallery-3.mdx
rename to src/data/post/2024-07-12-v6-gallery-3.mdx
diff --git a/src/content/post/2024-07-13-v6-gallery-4.mdx b/src/data/post/2024-07-13-v6-gallery-4.mdx
similarity index 100%
rename from src/content/post/2024-07-13-v6-gallery-4.mdx
rename to src/data/post/2024-07-13-v6-gallery-4.mdx
diff --git a/src/content/post/2024-07-16-v6-album-1.mdx b/src/data/post/2024-07-16-v6-album-1.mdx
similarity index 100%
rename from src/content/post/2024-07-16-v6-album-1.mdx
rename to src/data/post/2024-07-16-v6-album-1.mdx
diff --git a/src/content/post/2024-07-17-v6-album-2.mdx b/src/data/post/2024-07-17-v6-album-2.mdx
similarity index 100%
rename from src/content/post/2024-07-17-v6-album-2.mdx
rename to src/data/post/2024-07-17-v6-album-2.mdx
diff --git a/src/content/post/2024-07-18-v6-tests-1.mdx b/src/data/post/2024-07-18-v6-tests-1.mdx
similarity index 100%
rename from src/content/post/2024-07-18-v6-tests-1.mdx
rename to src/data/post/2024-07-18-v6-tests-1.mdx
diff --git a/src/content/post/2024-07-21-v6-tests-2.mdx b/src/data/post/2024-07-21-v6-tests-2.mdx
similarity index 100%
rename from src/content/post/2024-07-21-v6-tests-2.mdx
rename to src/data/post/2024-07-21-v6-tests-2.mdx
diff --git a/src/content/post/2024-07-23-v6-tests-3.mdx b/src/data/post/2024-07-23-v6-tests-3.mdx
similarity index 100%
rename from src/content/post/2024-07-23-v6-tests-3.mdx
rename to src/data/post/2024-07-23-v6-tests-3.mdx
diff --git a/src/content/post/2024-07-24-v6-profile-1.mdx b/src/data/post/2024-07-24-v6-profile-1.mdx
similarity index 100%
rename from src/content/post/2024-07-24-v6-profile-1.mdx
rename to src/data/post/2024-07-24-v6-profile-1.mdx
diff --git a/src/content/post/2024-07-26-v6-profile-2.mdx b/src/data/post/2024-07-26-v6-profile-2.mdx
similarity index 100%
rename from src/content/post/2024-07-26-v6-profile-2.mdx
rename to src/data/post/2024-07-26-v6-profile-2.mdx
diff --git a/src/content/post/2024-07-28-v6-settings-1.mdx b/src/data/post/2024-07-28-v6-settings-1.mdx
similarity index 100%
rename from src/content/post/2024-07-28-v6-settings-1.mdx
rename to src/data/post/2024-07-28-v6-settings-1.mdx
diff --git a/src/content/post/2024-07-29-v6-settings-2.mdx b/src/data/post/2024-07-29-v6-settings-2.mdx
similarity index 100%
rename from src/content/post/2024-07-29-v6-settings-2.mdx
rename to src/data/post/2024-07-29-v6-settings-2.mdx
diff --git a/src/content/post/2024-07-30-v6-settings-3.mdx b/src/data/post/2024-07-30-v6-settings-3.mdx
similarity index 100%
rename from src/content/post/2024-07-30-v6-settings-3.mdx
rename to src/data/post/2024-07-30-v6-settings-3.mdx
diff --git a/src/content/post/2024-08-02-v6-settings-4.mdx b/src/data/post/2024-08-02-v6-settings-4.mdx
similarity index 100%
rename from src/content/post/2024-08-02-v6-settings-4.mdx
rename to src/data/post/2024-08-02-v6-settings-4.mdx
diff --git a/src/content/post/2024-08-03-v6-users.mdx b/src/data/post/2024-08-03-v6-users.mdx
similarity index 100%
rename from src/content/post/2024-08-03-v6-users.mdx
rename to src/data/post/2024-08-03-v6-users.mdx
diff --git a/src/content/post/2024-08-04-v6-diagnostics.mdx b/src/data/post/2024-08-04-v6-diagnostics.mdx
similarity index 100%
rename from src/content/post/2024-08-04-v6-diagnostics.mdx
rename to src/data/post/2024-08-04-v6-diagnostics.mdx
diff --git a/src/content/post/2024-08-05-v6-jobs.mdx b/src/data/post/2024-08-05-v6-jobs.mdx
similarity index 100%
rename from src/content/post/2024-08-05-v6-jobs.mdx
rename to src/data/post/2024-08-05-v6-jobs.mdx
diff --git a/src/content/post/2024-08-08-v6-maintenance.mdx b/src/data/post/2024-08-08-v6-maintenance.mdx
similarity index 100%
rename from src/content/post/2024-08-08-v6-maintenance.mdx
rename to src/data/post/2024-08-08-v6-maintenance.mdx
diff --git a/src/content/post/2024-08-09-v6-light-mode.mdx b/src/data/post/2024-08-09-v6-light-mode.mdx
similarity index 100%
rename from src/content/post/2024-08-09-v6-light-mode.mdx
rename to src/data/post/2024-08-09-v6-light-mode.mdx
diff --git a/src/content/post/2024-08-10-v6-move-album-panel.mdx b/src/data/post/2024-08-10-v6-move-album-panel.mdx
similarity index 100%
rename from src/content/post/2024-08-10-v6-move-album-panel.mdx
rename to src/data/post/2024-08-10-v6-move-album-panel.mdx
diff --git a/src/content/post/2024-08-11-v6-transfer-album-panel.mdx b/src/data/post/2024-08-11-v6-transfer-album-panel.mdx
similarity index 100%
rename from src/content/post/2024-08-11-v6-transfer-album-panel.mdx
rename to src/data/post/2024-08-11-v6-transfer-album-panel.mdx
diff --git a/src/content/post/2024-08-14-v6-share-album-panel.mdx b/src/data/post/2024-08-14-v6-share-album-panel.mdx
similarity index 100%
rename from src/content/post/2024-08-14-v6-share-album-panel.mdx
rename to src/data/post/2024-08-14-v6-share-album-panel.mdx
diff --git a/src/content/post/2024-08-17-v6-upload-dialog.mdx b/src/data/post/2024-08-17-v6-upload-dialog.mdx
similarity index 100%
rename from src/content/post/2024-08-17-v6-upload-dialog.mdx
rename to src/data/post/2024-08-17-v6-upload-dialog.mdx
diff --git a/src/content/post/2024-08-18-v6-upload-dialog.mdx b/src/data/post/2024-08-18-v6-upload-dialog.mdx
similarity index 100%
rename from src/content/post/2024-08-18-v6-upload-dialog.mdx
rename to src/data/post/2024-08-18-v6-upload-dialog.mdx
diff --git a/src/content/post/2024-08-27-v6-edit-photo.mdx b/src/data/post/2024-08-27-v6-edit-photo.mdx
similarity index 100%
rename from src/content/post/2024-08-27-v6-edit-photo.mdx
rename to src/data/post/2024-08-27-v6-edit-photo.mdx
diff --git a/src/content/post/2024-08-31-v6-help.mdx b/src/data/post/2024-08-31-v6-help.mdx
similarity index 100%
rename from src/content/post/2024-08-31-v6-help.mdx
rename to src/data/post/2024-08-31-v6-help.mdx
diff --git a/src/content/post/2024-09-14-v6-settings.mdx b/src/data/post/2024-09-14-v6-settings.mdx
similarity index 100%
rename from src/content/post/2024-09-14-v6-settings.mdx
rename to src/data/post/2024-09-14-v6-settings.mdx
diff --git a/src/content/post/2024-09-15-v6-menus.mdx b/src/data/post/2024-09-15-v6-menus.mdx
similarity index 100%
rename from src/content/post/2024-09-15-v6-menus.mdx
rename to src/data/post/2024-09-15-v6-menus.mdx
diff --git a/src/content/post/2024-09-23-v6-multiple.mdx b/src/data/post/2024-09-23-v6-multiple.mdx
similarity index 100%
rename from src/content/post/2024-09-23-v6-multiple.mdx
rename to src/data/post/2024-09-23-v6-multiple.mdx
diff --git a/src/content/post/2024-09-24-v6-frame.mdx b/src/data/post/2024-09-24-v6-frame.mdx
similarity index 100%
rename from src/content/post/2024-09-24-v6-frame.mdx
rename to src/data/post/2024-09-24-v6-frame.mdx
diff --git a/src/content/post/2024-09-24-v6-oauth.mdx b/src/data/post/2024-09-24-v6-oauth.mdx
similarity index 100%
rename from src/content/post/2024-09-24-v6-oauth.mdx
rename to src/data/post/2024-09-24-v6-oauth.mdx
diff --git a/src/content/post/2024-09-24-v6-scramble.mdx b/src/data/post/2024-09-24-v6-scramble.mdx
similarity index 100%
rename from src/content/post/2024-09-24-v6-scramble.mdx
rename to src/data/post/2024-09-24-v6-scramble.mdx
diff --git a/src/content/post/2024-09-25-v6-map.mdx b/src/data/post/2024-09-25-v6-map.mdx
similarity index 100%
rename from src/content/post/2024-09-25-v6-map.mdx
rename to src/data/post/2024-09-25-v6-map.mdx
diff --git a/src/content/post/2024-09-29-v6-alpha.mdx b/src/data/post/2024-09-29-v6-alpha.mdx
similarity index 100%
rename from src/content/post/2024-09-29-v6-alpha.mdx
rename to src/data/post/2024-09-29-v6-alpha.mdx
diff --git a/src/content/post/2024-09-30-v6-header.mdx b/src/data/post/2024-09-30-v6-header.mdx
similarity index 100%
rename from src/content/post/2024-09-30-v6-header.mdx
rename to src/data/post/2024-09-30-v6-header.mdx
diff --git a/src/content/post/2024-10-05-v6-beta.mdx b/src/data/post/2024-10-05-v6-beta.mdx
similarity index 100%
rename from src/content/post/2024-10-05-v6-beta.mdx
rename to src/data/post/2024-10-05-v6-beta.mdx
diff --git a/src/content/post/2024-10-07-v6-beta2.mdx b/src/data/post/2024-10-07-v6-beta2.mdx
similarity index 100%
rename from src/content/post/2024-10-07-v6-beta2.mdx
rename to src/data/post/2024-10-07-v6-beta2.mdx
diff --git a/src/content/post/2024-10-14-v6-beta3.mdx b/src/data/post/2024-10-14-v6-beta3.mdx
similarity index 100%
rename from src/content/post/2024-10-14-v6-beta3.mdx
rename to src/data/post/2024-10-14-v6-beta3.mdx
diff --git a/src/content/post/2024-10-26-v6.mdx b/src/data/post/2024-10-26-v6.mdx
similarity index 100%
rename from src/content/post/2024-10-26-v6.mdx
rename to src/data/post/2024-10-26-v6.mdx
diff --git a/src/content/post/2024-12-13-what-is-next.mdx b/src/data/post/2024-12-13-what-is-next.mdx
similarity index 100%
rename from src/content/post/2024-12-13-what-is-next.mdx
rename to src/data/post/2024-12-13-what-is-next.mdx
diff --git a/src/content/post/2025-06-07-six-month-later.mdx b/src/data/post/2025-06-07-six-month-later.mdx
similarity index 100%
rename from src/content/post/2025-06-07-six-month-later.mdx
rename to src/data/post/2025-06-07-six-month-later.mdx
diff --git a/src/content/post/2025-06-11-version-6-6-6.mdx b/src/data/post/2025-06-11-version-6-6-6.mdx
similarity index 100%
rename from src/content/post/2025-06-11-version-6-6-6.mdx
rename to src/data/post/2025-06-11-version-6-6-6.mdx
diff --git a/src/content/post/2025-07-01-version-6-7-0.mdx b/src/data/post/2025-07-01-version-6-7-0.mdx
similarity index 100%
rename from src/content/post/2025-07-01-version-6-7-0.mdx
rename to src/data/post/2025-07-01-version-6-7-0.mdx
diff --git a/src/content/post/2025-07-29-finding-duplicates.mdx b/src/data/post/2025-07-29-finding-duplicates.mdx
similarity index 100%
rename from src/content/post/2025-07-29-finding-duplicates.mdx
rename to src/data/post/2025-07-29-finding-duplicates.mdx
diff --git a/src/content/post/2025-08-13-Opensource-processes-documentation.mdx b/src/data/post/2025-08-13-Opensource-processes-documentation.mdx
similarity index 100%
rename from src/content/post/2025-08-13-Opensource-processes-documentation.mdx
rename to src/data/post/2025-08-13-Opensource-processes-documentation.mdx
diff --git a/src/content/post/2025-09-13-Code-Rabbit.mdx b/src/data/post/2025-09-13-Code-Rabbit.mdx
similarity index 100%
rename from src/content/post/2025-09-13-Code-Rabbit.mdx
rename to src/data/post/2025-09-13-Code-Rabbit.mdx
diff --git a/src/content/post/2025-12-31-version-7.mdx b/src/data/post/2025-12-31-version-7.mdx
similarity index 100%
rename from src/content/post/2025-12-31-version-7.mdx
rename to src/data/post/2025-12-31-version-7.mdx
diff --git a/src/env.d.ts b/src/env.d.ts
index 45786fb4..a2468e05 100644
--- a/src/env.d.ts
+++ b/src/env.d.ts
@@ -3,3 +3,8 @@
///
///
///
+
+// Fontsource packages ship CSS only (no type declarations); declare them so
+// side-effect imports type-check under TypeScript 6 strict (ts2882).
+declare module '@fontsource-variable/*';
+declare module '@fontsource/*';
diff --git a/src/layouts/LandingLayout.astro b/src/layouts/LandingLayout.astro
index 0554afa4..4bafcf79 100644
--- a/src/layouts/LandingLayout.astro
+++ b/src/layouts/LandingLayout.astro
@@ -23,7 +23,7 @@ const { metadata } = Astro.props;
actions={[
{
text: 'Download',
- href: 'https://github.com/onwidget/astrowind',
+ href: 'https://github.com/arthelokyo/astrowind',
},
]}
showToggleTheme
diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro
index ec3b577c..758bd020 100644
--- a/src/layouts/Layout.astro
+++ b/src/layouts/Layout.astro
@@ -9,11 +9,10 @@ import CustomStyles from '~/components/CustomStyles.astro';
import ApplyColorMode from '~/components/common/ApplyColorMode.astro';
import Metadata from '~/components/common/Metadata.astro';
import SiteVerification from '~/components/common/SiteVerification.astro';
-import Analytics from '~/components/common/Analytics.astro';
import BasicScripts from '~/components/common/BasicScripts.astro';
// Comment the line below to disable View Transitions
-import { ViewTransitions } from 'astro:transitions';
+import { ClientRouter } from 'astro:transitions';
import type { MetaData as MetaDataType } from '~/types';
@@ -34,21 +33,16 @@ const { language, textDirection } = I18N;
-
+
+
-
+
-
-