Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions scripts/generate-social-card.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import sharp from 'sharp';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Social card dimensions (recommended for Open Graph)
const WIDTH = 1200;
const HEIGHT = 630;

// Colors matching the site's theme
const BACKGROUND_COLOR = '#faf9f5'; // Light theme background
const TEXT_COLOR = '#141413'; // Dark text

// Create SVG with site branding
const svg = `
<svg width="${WIDTH}" height="${HEIGHT}" xmlns="http://www.w3.org/2000/svg">
<defs>
<style>
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@600;700&amp;display=swap');
</style>
</defs>

<!-- Background -->
<rect width="100%" height="100%" fill="${BACKGROUND_COLOR}"/>

<!-- Subtle pattern/texture -->
<pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
<path d="M 40 0 L 0 0 0 40" fill="none" stroke="${TEXT_COLOR}" stroke-width="0.5" opacity="0.05"/>
</pattern>
<rect width="100%" height="100%" fill="url(#grid)"/>

<!-- Accent line at top -->
<rect x="0" y="0" width="${WIDTH}" height="8" fill="#C9A227"/>

<!-- Main title -->
<text x="600" y="260"
font-family="Poppins, sans-serif"
font-size="80"
font-weight="700"
fill="${TEXT_COLOR}"
text-anchor="middle"
letter-spacing="-2">
Angel Baez
</text>

<!-- Subtitle -->
<text x="600" y="340"
font-family="Poppins, sans-serif"
font-size="36"
font-weight="600"
fill="${TEXT_COLOR}"
text-anchor="middle"
opacity="0.7">
Full Stack Developer
</text>

<!-- Decorative elements -->
<line x1="400" y1="390" x2="800" y2="390" stroke="#C9A227" stroke-width="2"/>

<!-- Website URL -->
<text x="600" y="450"
font-family="Poppins, sans-serif"
font-size="24"
font-weight="600"
fill="${TEXT_COLOR}"
text-anchor="middle"
opacity="0.5">
angel-baez.com
</text>
</svg>
`;

async function generateSocialCard() {
const outputPath = join(__dirname, '..', 'src', 'assets', 'social-card.png');

await sharp(Buffer.from(svg))
.png()
.toFile(outputPath);

console.log(`Social card generated: ${outputPath}`);
}

generateSocialCard().catch(console.error);
Binary file added src/assets/social-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 13 additions & 6 deletions src/components/BaseHead.astro
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
---
import { SITE_URL } from "../const";
import type { Language } from "@/i18n/ui";
import type { ImageMetadata } from "astro";
import { getLocalizedUrl } from "@/i18n/utils";
import defaultSocialCard from "@/assets/social-card.png";

interface Props {
title: string;
description: string;
image?: string;
image?: string | ImageMetadata;
keywords?: string[];
lang: Language;
}
Expand All @@ -16,11 +18,16 @@ const defaultKeywords = ["Angel Baez", "angel-baez.com", "Full Stack Developer",
const {
title,
description,
image = "/favicon.png",
image = defaultSocialCard,
keywords,
lang
} = Astro.props;

// Handle image URL (string or imported image)
const imageUrl = typeof image === 'string'
? new URL(image, SITE_URL)
: new URL(image.src, SITE_URL);

// Merge post-specific keywords with defaults
const allKeywords = keywords && keywords.length > 0
? [...keywords, ...defaultKeywords]
Expand Down Expand Up @@ -57,7 +64,7 @@ const esUrl = new URL(getLocalizedUrl(Astro.url.pathname, 'es'), SITE_URL);
<meta property="og:url" content={canonicalURL} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={new URL(image, SITE_URL)} />
<meta property="og:image" content={imageUrl} />
<meta property="og:site_name" content="Angel Baez" />
<meta property="og:locale" content={lang === 'es' ? 'es_MX' : 'en_US'} />

Expand All @@ -66,7 +73,7 @@ const esUrl = new URL(getLocalizedUrl(Astro.url.pathname, 'es'), SITE_URL);
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={new URL(image, SITE_URL)} />
<meta property="twitter:image" content={imageUrl} />
<meta name="twitter:site" content="@baezor" />
<meta name="twitter:creator" content="@baezor" />

Expand All @@ -83,13 +90,13 @@ const esUrl = new URL(getLocalizedUrl(Astro.url.pathname, 'es'), SITE_URL);
</script>

<!-- Structured Data for Person -->
<script is:inline define:vars={{ siteUrl: SITE_URL, imageUrl: new URL(image, SITE_URL).toString() }} type="application/ld+json">
<script is:inline define:vars={{ siteUrl: SITE_URL, schemaImageUrl: imageUrl.toString() }} type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Person",
"name": "Angel Baez",
"url": siteUrl,
"image": imageUrl,
"image": schemaImageUrl,
"email": "angel@romerobaez.com",
"jobTitle": "Full Stack Developer and Entrepreneur",
"description": "Full Stack Developer and entrepreneur based in Cancun, Mexico",
Expand Down
2 changes: 1 addition & 1 deletion src/layouts/BlogPost.astro
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ blogPostingSchema.speakable = {
};
---

<Layout title={title} description={metaDescription || description} keywords={keywords}>
<Layout title={title} description={metaDescription || description} keywords={keywords} image={heroImage}>
<main>
<article class="blog-post">
<header class="blog-header">
Expand Down
6 changes: 4 additions & 2 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,24 @@ import BaseHead from "@/components/BaseHead.astro";
import Header from "@/components/Header.astro";
import Footer from "@/components/Footer.astro";
import { getLangFromUrl } from "@/i18n/utils";
import type { ImageMetadata } from "astro";

// Define the title and description props
interface Props {
title?: string;
description?: string;
keywords?: string[];
image?: string | ImageMetadata;
}

const lang = getLangFromUrl(Astro.url);
const { title = SITE_TITLE[lang], description = SITE_DESCRIPTION[lang], keywords } = Astro.props;
const { title = SITE_TITLE[lang], description = SITE_DESCRIPTION[lang], keywords, image } = Astro.props;
---

<!doctype html>
<html lang={lang}>
<head>
<BaseHead title={title} description={description} keywords={keywords} lang={lang} />
<BaseHead title={title} description={description} keywords={keywords} lang={lang} image={image} />
<!-- favicon -->
<link rel="icon" type="image/png" href="favicon.png" />

Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"jsxImportSource": "react",
"types": ["vitest/globals"],
"paths": {
"@/assets/*": ["./src/assets/*"],
"@/css/*": ["./src/css/*"],
"@/layouts/*": ["./src/layouts/*"],
"@/pages/*": ["./src/pages/*"],
Expand Down