Compare commits

..

6 Commits

Author SHA1 Message Date
843991f0ba Fix pnpm 2026-06-16 23:32:08 +03:00
3ee285dec0 Update image backgrounds 2026-06-16 23:22:48 +03:00
1fff7e0704 Add subtle animation on the homepage 2026-06-16 23:19:14 +03:00
32dbece9e1 Remove start script 2026-06-15 18:25:05 +03:00
2ff9139f73 upgrade deps to latest & migrate to astro:assets Image
- bump astro 5→6, @astrojs/mdx 4→6, astro-seo 0.8→1.1, sharp 0.33→0.35, plus minor updates
- move local images from public/ to src/assets/
- replace <img> with <Image> from astro:assets (inferSize for remote URLs)
- content schema uses image() helper for local covers
- eager-load above-the-fold images (article covers, hero avatars)
2026-06-15 18:00:21 +03:00
4192a19fe8 Rewrite been-building-with section: replace bullets with prose, use natural tone 2026-06-15 15:38:56 +03:00
25 changed files with 637 additions and 1042 deletions

View File

@@ -7,6 +7,14 @@ import icon from "astro-icon";
export default defineConfig({ export default defineConfig({
site: "https://mariosant.dev", site: "https://mariosant.dev",
integrations: [mdx(), sitemap(), icon()], integrations: [mdx(), sitemap(), icon()],
image: {
domains: [
"images.unsplash.com",
"app.ghostwriter.rocks",
"990141.apps.zdusercontent.com",
"cdn.livechat-files.com",
],
},
vite: { vite: {
plugins: [tailwindcss()], plugins: [tailwindcss()],
}, },

View File

@@ -5,26 +5,26 @@
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro" "astro": "astro"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.2.0", "@astrojs/mdx": "^6.0.3",
"@astrojs/rss": "^4.0.11", "@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.2.1", "@astrojs/sitemap": "^3.7.3",
"@fontsource-variable/inter": "^5.2.5", "@fontsource-variable/inter": "^5.2.8",
"@iconify-json/heroicons": "^1.2.1", "@iconify-json/heroicons": "^1.2.3",
"@iconify-json/hugeicons": "^1.2.29", "@iconify-json/hugeicons": "^1.2.29",
"@iconify-json/mynaui": "^1.2.5", "@iconify-json/mynaui": "^1.2.17",
"@iconify-json/simple-icons": "^1.2.28", "@iconify-json/simple-icons": "^1.2.86",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.20",
"@tailwindcss/vite": "^4.0.14", "@tailwindcss/vite": "^4.3.1",
"astro": "^5.5.2", "astro": "^6.4.7",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"astro-seo": "^0.8.4", "astro-seo": "^1.1.0",
"sharp": "^0.33.5", "sharp": "^0.35.1",
"tailwindcss": "^4.0.14" "tailwind-animations": "^1.0.1",
"tailwindcss": "^4.3.1"
} }
} }

1430
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

6
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,6 @@
packages:
- .
onlyBuiltDependencies:
- esbuild
- sharp

View File

Before

Width:  |  Height:  |  Size: 599 KiB

After

Width:  |  Height:  |  Size: 599 KiB

View File

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 5.2 KiB

After

Width:  |  Height:  |  Size: 5.2 KiB

View File

@@ -1,5 +1,10 @@
--- ---
import PreviousRole from "@/components/previous-role.astro"; import PreviousRole from "@/components/previous-role.astro";
import lateralusLogo from "@/assets/lateralus-logo.jpeg";
import celonisLogo from "@/assets/celonis-logo.webp";
import lensesLogo from "@/assets/lenses-logo.webp";
import theChatShopLogo from "@/assets/thechatshop-logo.jpg";
import commversionLogo from "@/assets/commversion-logo.webp";
--- ---
<section class="flex flex-col gap-5 p-4 sm:p-6 lg:p-8 max-w-3xl mx-auto w-full"> <section class="flex flex-col gap-5 p-4 sm:p-6 lg:p-8 max-w-3xl mx-auto w-full">
@@ -7,50 +12,42 @@ import PreviousRole from "@/components/previous-role.astro";
<PreviousRole <PreviousRole
company="Lateralus Ventures" company="Lateralus Ventures"
logo="/lateralus-logo.jpeg" logo={lateralusLogo}
href="https://lateralus.ventures" href="https://lateralus.ventures"
> >
Led frontend development for Ask Chief, shaping AI product experiences with scalable interfaces Frontend work on Ask Chief, building interfaces for AI-powered products that needed to feel simple
that could evolve alongside intelligent workflows. while handling complex workflows underneath.
</PreviousRole> </PreviousRole>
<PreviousRole <PreviousRole
company="Celonis" company="Celonis"
logo="/celonis-logo.webp" logo={celonisLogo}
href="https://celonis.com" href="https://celonis.com"
> >
Built analytical product interfaces for OCPM, alongside shared component systems, build pipelines, Building out analytical interfaces for OCPM, a shared component library, and tooling that made it
and platform foundations that helped teams move faster together. easier for the whole team to ship.
</PreviousRole> </PreviousRole>
<PreviousRole <PreviousRole
company="Lenses.io" company="Lenses.io"
logo="/lenses-logo.webp" logo={lensesLogo}
href="https://lenses.io" href="https://lenses.io"
> >
<ul class="list-disc list-inside space-y-1"> Product interfaces for SQL Processors and Data Catalog, a shared component library, and developer
<li>Engineered product interfaces for SQL Processors and Data Catalog.</li> tooling including the CLI.
<li>Architected the shared components library.</li>
<li>Worked on developer tooling, including the CLI.</li>
</ul>
</PreviousRole> </PreviousRole>
<PreviousRole company="The Chat Shop" logo="/thechatshop-logo.jpg"> <PreviousRole company="The Chat Shop" logo={theChatShopLogo}>
<ul class="list-disc list-inside space-y-1"> Streaming data infrastructure built from scratch, along with a processing pipeline that kept delivery
<li>Spearheaded end-to-end processing architecture.</li> running reliably at scale.
<li>Established the company's streaming data infrastructure.</li>
<li>Built the operational foundations for reliable delivery and automation.</li>
</ul>
</PreviousRole> </PreviousRole>
<PreviousRole <PreviousRole
company="Commversion" company="Commversion"
logo="/commversion-logo.webp" logo={commversionLogo}
href="https://commversion.com" href="https://commversion.com"
> >
<ul class="list-disc list-inside space-y-1"> Customer delivery platform and its public-facing side, plus InstantConnect, taken from design through
<li>Developed the customer delivery platform and its public-facing infrastructure.</li> to implementation.
<li>Designed and implemented the initial InstantConnect system.</li>
</ul>
</PreviousRole> </PreviousRole>
</section> </section>

View File

@@ -1,16 +1,18 @@
--- ---
import { Image } from "astro:assets";
import Button from "@/components/button.astro"; import Button from "@/components/button.astro";
import Link from "@/components/link.astro"; import Link from "@/components/link.astro";
import mariosant from "@/assets/mariosant.webp";
--- ---
<header class="flex flex-col md:gap-12 gap-5 p-4 sm:p-6 lg:p-8 max-w-3xl mx-auto w-full"> <header class="flex flex-col md:gap-12 gap-5 p-4 sm:p-6 lg:p-8 max-w-3xl mx-auto w-full">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<a href="/" aria-label="Home"> <a href="/" aria-label="Home">
<img <Image
src={mariosant}
alt="Marios Antonoudiou" alt="Marios Antonoudiou"
src="/mariosant.webp" width={64}
width="64" height={64}
height="64"
class="w-16 h-16 rounded-full border border-gray-200 dark:border-gray-800 dark:bg-slate-300 block" class="w-16 h-16 rounded-full border border-gray-200 dark:border-gray-800 dark:bg-slate-300 block"
loading="eager" loading="eager"
/> />

View File

@@ -1,4 +1,5 @@
--- ---
import { Image } from "astro:assets";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import { formatDoMMMMYYYY } from "@/utils/date"; import { formatDoMMMMYYYY } from "@/utils/date";
@@ -12,7 +13,10 @@ if (!article) {
} }
const cover = article.data.coverImage; const cover = article.data.coverImage;
const coverUrl = cover.url.replace("w=1287&h=600", "w=300&h=300"); const isRemote = typeof cover.url === "string";
const coverSrc = isRemote
? cover.url.replace("w=1287&h=600", "w=300&h=300")
: cover.url;
const body = (article.body ?? "").trim(); const body = (article.body ?? "").trim();
const excerpt = body.length > 150 ? `${body.slice(0, 150)}…` : body; const excerpt = body.length > 150 ? `${body.slice(0, 150)}…` : body;
@@ -30,10 +34,11 @@ const excerpt = body.length > 150 ? `${body.slice(0, 150)}…` : body;
<div class="flex gap-6 items-start flex-row-reverse"> <div class="flex gap-6 items-start flex-row-reverse">
{ {
cover.url && ( cover.url && (
<img <Image
class="w-24 h-24 object-cover rounded-lg shrink-0" class="w-24 h-24 object-cover rounded-lg shrink-0"
src={coverUrl} src={coverSrc}
alt={article.data.title} alt={article.data.title}
inferSize={isRemote}
loading="lazy" loading="lazy"
/> />
) )

View File

@@ -1,29 +1,42 @@
--- ---
import { Image, type ImageMetadata } from "astro:assets";
export interface Props { export interface Props {
logo: string; logo: ImageMetadata | string;
href?: string; href?: string;
company: string; company: string;
} }
const { logo, href, company } = Astro.props; const { logo, href, company } = Astro.props;
const isRemote = typeof logo === "string";
--- ---
<div class="flex gap-5"> <div class="flex gap-5">
{ {
href ? ( href ? (
<a href={href} target="_blank" rel="noopener noreferrer" class="block shrink-0"> <a
<img href={href}
target="_blank"
rel="noopener noreferrer"
class="block shrink-0">
<Image
src={logo} src={logo}
alt={company} alt={company}
class="block rounded min-w-[50px] w-[50px] h-[50px] object-contain bg-white" inferSize={isRemote}
width={isRemote ? undefined : 50}
height={isRemote ? undefined : 50}
class="block rounded size-12 object-contain"
loading="lazy" loading="lazy"
/> />
</a> </a>
) : ( ) : (
<img <Image
src={logo} src={logo}
alt={company} alt={company}
class="block rounded min-w-[50px] w-[50px] h-[50px] object-contain bg-white shrink-0" inferSize={isRemote}
width={isRemote ? undefined : 50}
height={isRemote ? undefined : 50}
class="block rounded size-12 object-contain shrink-0"
loading="lazy" loading="lazy"
/> />
) )
@@ -35,8 +48,7 @@ const { logo, href, company } = Astro.props;
href={href} href={href}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="md:text-xl text-lg font-semibold hover:underline hover:underline-offset-4 text-slate-900 dark:text-white" class="md:text-xl text-lg font-semibold hover:underline hover:underline-offset-4 text-slate-900 dark:text-white">
>
{company} {company}
</a> </a>
) : ( ) : (

View File

@@ -1,19 +1,26 @@
--- ---
import { Image, type ImageMetadata } from "astro:assets";
export interface Props { export interface Props {
title: string; title: string;
image: string; image: ImageMetadata | string;
url: string; url: string;
} }
const { title, image, url } = Astro.props; const { title, image, url } = Astro.props;
const isRemote = typeof image === "string";
--- ---
<div class="flex overflow-auto gap-x-5 md:gap-2 items-center md:items-start md:flex-col md:border md:rounded-lg md:border-gray-200 md:p-4 md:dark:border-slate-600"> <div
class="flex overflow-auto gap-x-5 md:gap-2 items-center md:items-start md:flex-col md:border md:rounded-lg md:border-gray-200 md:p-4 md:dark:border-slate-600">
<a href={url} target="_blank" rel="noopener noreferrer" class="shrink-0"> <a href={url} target="_blank" rel="noopener noreferrer" class="shrink-0">
<img <Image
src={image} src={image}
alt={title} alt={title}
class="block min-w-[50px] w-[50px] md:w-16 h-[50px] md:h-16 object-contain bg-white rounded" inferSize={isRemote}
width={isRemote ? undefined : 64}
height={isRemote ? undefined : 64}
class="block min-w-[50px] w-[50px] md:w-16 h-[50px] md:h-16 object-contain rounded"
loading="lazy" loading="lazy"
/> />
</a> </a>
@@ -23,8 +30,7 @@ const { title, image, url } = Astro.props;
href={url} href={url}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="font-semibold hover:underline hover:underline-offset-4 text-slate-900 dark:text-white" class="font-semibold hover:underline hover:underline-offset-4 text-slate-900 dark:text-white">
>
{title} {title}
</a> </a>
<div class="text-sm text-slate-500 dark:text-slate-400 md:text-xs"> <div class="text-sm text-slate-500 dark:text-slate-400 md:text-xs">

View File

@@ -1,5 +1,7 @@
--- ---
import { Image } from "astro:assets";
import Link from "@/components/link.astro"; import Link from "@/components/link.astro";
import mariosant from "@/assets/mariosant.webp";
const path = Astro.url.pathname; const path = Astro.url.pathname;
const isHome = path === "/" || path === ""; const isHome = path === "/" || path === "";
@@ -9,11 +11,11 @@ const isArticles = path === "/articles" || path.startsWith("/articles/");
<div class="sticky top-0 z-10 border-b border-gray-200 dark:border-slate-600 bg-white/85 dark:bg-slate-800/90 backdrop-blur-sm"> <div class="sticky top-0 z-10 border-b border-gray-200 dark:border-slate-600 bg-white/85 dark:bg-slate-800/90 backdrop-blur-sm">
<nav class="grid grid-cols-2 items-center gap-3 !py-2 max-w-3xl mx-auto w-full p-4 sm:p-6 lg:p-8"> <nav class="grid grid-cols-2 items-center gap-3 !py-2 max-w-3xl mx-auto w-full p-4 sm:p-6 lg:p-8">
<Link href="/" class="flex gap-3 items-center" variant="default"> <Link href="/" class="flex gap-3 items-center" variant="default">
<img <Image
src={mariosant}
alt="Marios Antonoudiou" alt="Marios Antonoudiou"
src="/mariosant.webp" width={40}
width="40" height={40}
height="40"
class="w-10 h-10 rounded-full border border-gray-200 dark:border-gray-800 dark:bg-slate-300 block" class="w-10 h-10 rounded-full border border-gray-200 dark:border-gray-800 dark:bg-slate-300 block"
loading="eager" loading="eager"
/> />

View File

@@ -3,14 +3,15 @@ import { glob } from "astro/loaders";
const articles = defineCollection({ const articles = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/articles" }), loader: glob({ pattern: "**/*.md", base: "./src/content/articles" }),
schema: z.object({ schema: ({ image }) =>
z.object({
title: z.string(), title: z.string(),
date: z.coerce.date(), date: z.coerce.date(),
description: z.string().optional(), description: z.string().optional(),
coverImage: z.object({ coverImage: z.object({
author: z.string(), author: z.string(),
authorUrl: z.string().url().optional(), authorUrl: z.string().url().optional(),
url: z.string(), url: z.union([image(), z.string().url()]),
}), }),
}), }),
}); });

View File

@@ -3,7 +3,7 @@ title: Smooth Scrolling in Agentic Chat Interfaces
date: 2025-09-15 date: 2025-09-15
coverImage: coverImage:
author: mariosant author: mariosant
url: "/agentic-ui.png" url: "../../assets/agentic-ui.png"
--- ---
When building conversational UIs, small interaction details can make a huge difference in how natural the experience feels. Recently, while working on an agentic chat interface, I ran into a deceptively simple requirement: When building conversational UIs, small interaction details can make a huge difference in how natural the experience feels. Recently, while working on an agentic chat interface, I ran into a deceptively simple requirement:

View File

@@ -1,10 +1,11 @@
--- ---
import { Image } from "astro:assets";
import { getCollection, render } from "astro:content"; import { getCollection, render } from "astro:content";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import Link from "@/components/link.astro"; import Link from "@/components/link.astro";
import Button from "@/components/button.astro"; import Button from "@/components/button.astro";
import { formatDoMMMMYYYY } from "@/utils/date"; import { formatDoMMMMYYYY } from "@/utils/date";
import { articleJsonLd, breadcrumbJsonLd, deriveDescription } from "@/utils/seo"; import { articleJsonLd, breadcrumbJsonLd, deriveDescription, imageSrc } from "@/utils/seo";
export async function getStaticPaths() { export async function getStaticPaths() {
const articles = await getCollection("articles"); const articles = await getCollection("articles");
@@ -17,7 +18,8 @@ export async function getStaticPaths() {
const { entry } = Astro.props; const { entry } = Astro.props;
const { Content } = await render(entry); const { Content } = await render(entry);
const cover = entry.data.coverImage; const cover = entry.data.coverImage;
const showCover = Boolean(cover?.url); const isRemote = typeof cover.url === "string";
const showCover = isRemote ? Boolean(cover.url) : true;
const description = entry.data.description ?? deriveDescription(entry.body ?? ""); const description = entry.data.description ?? deriveDescription(entry.body ?? "");
--- ---
@@ -25,7 +27,7 @@ const description = entry.data.description ?? deriveDescription(entry.body ?? ""
title={entry.data.title} title={entry.data.title}
description={description} description={description}
ogType="article" ogType="article"
ogImage={cover.url} ogImage={imageSrc(cover.url)}
ogImageAlt={entry.data.title} ogImageAlt={entry.data.title}
jsonLd={[articleJsonLd(entry), breadcrumbJsonLd(entry)]} jsonLd={[articleJsonLd(entry), breadcrumbJsonLd(entry)]}
> >
@@ -48,13 +50,12 @@ const description = entry.data.description ?? deriveDescription(entry.body ?? ""
{ {
showCover && ( showCover && (
<figure class="hidden md:block max-w-3xl p-4 sm:p-6 lg:p-8 mx-auto w-full"> <figure class="hidden md:block max-w-3xl p-4 sm:p-6 lg:p-8 mx-auto w-full">
<img <Image
src={cover.url} src={cover.url}
alt={entry.data.title} alt={entry.data.title}
height="1000" inferSize={isRemote}
width="1700"
class="rounded-lg w-full h-auto" class="rounded-lg w-full h-auto"
loading="lazy" loading="eager"
/> />
{cover.authorUrl && ( {cover.authorUrl && (
<a <a

View File

@@ -1,4 +1,5 @@
--- ---
import { Image } from "astro:assets";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import Layout from "@/layouts/Layout.astro"; import Layout from "@/layouts/Layout.astro";
import { formatDoMMMMYYYY } from "@/utils/date"; import { formatDoMMMMYYYY } from "@/utils/date";
@@ -20,14 +21,18 @@ const articles = (await getCollection("articles")).sort(
<section class="grid grid-cols-12 gap-8 max-w-3xl p-4 sm:p-6 lg:p-8 mx-auto w-full"> <section class="grid grid-cols-12 gap-8 max-w-3xl p-4 sm:p-6 lg:p-8 mx-auto w-full">
{ {
articles.map((article) => ( articles.map((article, index) => {
const url = article.data.coverImage.url;
const isRemote = typeof url === "string";
return (
<article class="col-span-12 md:col-span-6 border border-gray-200 dark:border-slate-600 rounded-lg overflow-hidden !p-0"> <article class="col-span-12 md:col-span-6 border border-gray-200 dark:border-slate-600 rounded-lg overflow-hidden !p-0">
<a href={`/articles/${article.id}`} class="block cursor-pointer"> <a href={`/articles/${article.id}`} class="block cursor-pointer">
<img <Image
class="w-full h-48 object-cover" class="w-full h-48 object-cover"
src={article.data.coverImage.url} src={url}
alt={article.data.title} alt={article.data.title}
loading="lazy" inferSize={isRemote}
loading={index < 2 ? "eager" : "lazy"}
/> />
</a> </a>
<div class="grid gap-4 p-4 md:p-6"> <div class="grid gap-4 p-4 md:p-6">
@@ -52,7 +57,8 @@ const articles = (await getCollection("articles")).sort(
</div> </div>
</div> </div>
</article> </article>
)) );
})
} }
</section> </section>
</Layout> </Layout>

View File

@@ -12,11 +12,18 @@ import { personJsonLd } from "@/utils/seo";
title="Marios Antonoudiou — AI Product Engineer" title="Marios Antonoudiou — AI Product Engineer"
description="AI product engineer building AI-powered products that feel simple, useful, and ready for real users." description="AI product engineer building AI-powered products that feel simple, useful, and ready for real users."
withNav={false} withNav={false}
jsonLd={personJsonLd()} jsonLd={personJsonLd()}>
>
<HomeIntroduction /> <HomeIntroduction />
<div class="animate-fade-in animate-delay-150">
<HomeLatestArticle /> <HomeLatestArticle />
<HomeBeenWorkingWith /> </div>
<HomePersonalProjects /> <div class="animate-fade-in animate-delay-250">
<HomeBeenWorkingWith class="animate-fade-in animate-delay-150" />
</div>
<div class="animate-fade-in animate-delay-350">
<HomePersonalProjects class="animate-fade-in animate-delay-150" />
</div>
<div class="animate-fade-in animate-delay-450">
<HomeConnect /> <HomeConnect />
</div>
</Layout> </Layout>

View File

@@ -1,7 +1,6 @@
import rss from "@astrojs/rss"; import rss from "@astrojs/rss";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import { SITE_URL } from "@/utils/seo"; import { SITE_URL, imageSrc, deriveDescription } from "@/utils/seo";
import { deriveDescription } from "@/utils/seo";
import type { APIContext } from "astro"; import type { APIContext } from "astro";
export async function GET(context: APIContext) { export async function GET(context: APIContext) {
@@ -22,7 +21,7 @@ export async function GET(context: APIContext) {
content: undefined, content: undefined,
author: "mariosant@sent.com (Marios Antonoudiou)", author: "mariosant@sent.com (Marios Antonoudiou)",
categories: [], categories: [],
customData: `<enclosure url="${new URL(entry.data.coverImage.url, context.site ?? SITE_URL).toString()}" type="image/jpeg" />`, customData: `<enclosure url="${new URL(imageSrc(entry.data.coverImage.url), context.site ?? SITE_URL).toString()}" type="image/jpeg" />`,
})), })),
customData: `<language>en-us</language>`, customData: `<language>en-us</language>`,
stylesheet: false, stylesheet: false,

View File

@@ -1,4 +1,5 @@
@import 'tailwindcss'; @import "tailwindcss";
@import "tailwind-animations";
@custom-variant dark (&:where(.dark, .dark *)); @custom-variant dark (&:where(.dark, .dark *));
@@ -7,7 +8,7 @@
@theme { @theme {
--font-sans: --font-sans:
Inter Variable, Inter, ui-sans-serif, system-ui, sans-serif, Inter Variable, Inter, ui-sans-serif, system-ui, sans-serif,
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
} }
@layer base { @layer base {

View File

@@ -1,4 +1,5 @@
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import type { ImageMetadata } from "astro:assets";
export const SITE_URL = "https://mariosant.dev"; export const SITE_URL = "https://mariosant.dev";
export const TWITTER_HANDLE = "@marios_ant"; export const TWITTER_HANDLE = "@marios_ant";
@@ -42,6 +43,9 @@ export const deriveDescription = (body: string, max = 155): string => {
type ArticleEntry = CollectionEntry<"articles">; type ArticleEntry = CollectionEntry<"articles">;
export const imageSrc = (img: ImageMetadata | string): string =>
typeof img === "string" ? img : img.src;
export const personJsonLd = (): string => export const personJsonLd = (): string =>
JSON.stringify({ JSON.stringify({
"@context": "https://schema.org", "@context": "https://schema.org",
@@ -62,7 +66,7 @@ export const articleJsonLd = (entry: ArticleEntry): string => {
"@type": "Article", "@type": "Article",
headline: entry.data.title, headline: entry.data.title,
description, description,
image: entry.data.coverImage.url, image: imageSrc(entry.data.coverImage.url),
datePublished: entry.data.date.toISOString(), datePublished: entry.data.date.toISOString(),
author: { author: {
"@id": PERSON_ID, "@id": PERSON_ID,