Skip to content

Commit aef2534

Browse files
authored
✨ Add blog collection and individual blog post pages; enhance typography and layout across components for improved readability and user experience
1 parent 82baa65 commit aef2534

12 files changed

Lines changed: 499 additions & 17 deletions

File tree

src/components/solid/Navbar.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ const Navbar: Component<NavbarProps> = (props) => {
185185
const baseClass =
186186
"inline-flex items-center justify-center rounded-[0.65rem] border border-transparent px-[0.7rem] py-[0.42rem] text-[0.73rem] tracking-[0.06em] text-[var(--text-muted)] hover:border-[var(--border-strong)] hover:bg-[color-mix(in_srgb,var(--surface-panel)_76%,transparent)] hover:text-[var(--text-strong)]";
187187
const activeClass =
188-
"text-[var(--accent-contrast)] border-[color-mix(in_srgb,var(--accent-strong)_70%,var(--border-strong))] bg-[color-mix(in_srgb,var(--accent-strong)_86%,#10243d)] shadow-[0_0_0_1px_color-mix(in_srgb,var(--accent-strong)_55%,transparent)]";
188+
"!text-white border-[color-mix(in_srgb,var(--accent-strong)_70%,var(--border-strong))] bg-[color-mix(in_srgb,var(--accent-strong)_86%,#10243d)] shadow-[0_0_0_1px_color-mix(in_srgb,var(--accent-strong)_55%,transparent)]";
189189

190190
return (
191191
<ul class="ml-auto flex h-full items-center justify-end gap-1 rounded-xl border border-[var(--border-strong)] bg-[color-mix(in_srgb,var(--surface-panel)_84%,transparent)] px-1 py-1 font-bold uppercase shadow-[0_10px_24px_rgba(15,23,42,0.08)]">
@@ -194,6 +194,7 @@ const Navbar: Component<NavbarProps> = (props) => {
194194
<li>
195195
<a
196196
class={`${baseClass}${current() === link ? activeClass : ""}`}
197+
style={current() === link ? { color: "white" } : undefined}
197198
href={`#${link}`}
198199
onClick={() => setCurrent(link)}
199200
aria-current={current() === link ? "page" : undefined}

src/content.config.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,28 @@ const portfolioCollection = defineCollection({
3434
}),
3535
});
3636

37+
const blogCollection = defineCollection({
38+
loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content/blog" }),
39+
schema: z.object({
40+
title: z.string(),
41+
description: z.string(),
42+
publishDate: z.coerce.date(),
43+
updatedDate: z.coerce.date().optional(),
44+
author: z.string().default("Avaab Razzaq"),
45+
image: z.object({
46+
src: z.string(),
47+
alt: z.string(),
48+
}).optional(),
49+
tags: z.array(z.string()).default([]),
50+
category: z.enum(["AI", "Marketing", "Development", "Growth", "Analytics", "Tutorial"]),
51+
draft: z.boolean().default(false),
52+
featured: z.boolean().default(false),
53+
readingTime: z.number().optional(),
54+
}),
55+
});
56+
3757
export const collections = {
3858
site: siteCollection,
3959
portfolio: portfolioCollection,
60+
blog: blogCollection,
4061
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"order": 5,
3+
"title": "Content Marketing & SEO Strategy",
4+
"category": "Marketing",
5+
"summary": "Built comprehensive content strategy combining SEO research, thought leadership articles, case studies, and email nurture sequences to drive organic acquisition.",
6+
"impact": "Increased organic traffic by 425%, generated 180+ qualified leads/month, achieved #1-3 rankings for 12 target keywords.",
7+
"timeframe": "2024-2025",
8+
"stack": ["SEO", "Content Strategy", "Ahrefs", "WordPress", "Email Marketing"],
9+
"proofUrl": "https://github.com/AR10Dev",
10+
"proofLabel": "See content results"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"order": 6,
3+
"title": "Paid Social Media Campaigns",
4+
"category": "Marketing",
5+
"summary": "Managed $45K+ monthly ad spend across Meta, LinkedIn, and Twitter with audience segmentation, creative testing, and conversion optimization.",
6+
"impact": "Achieved 3.2x ROAS, reduced CAC by 58%, scaled spend 4x while maintaining profitability targets.",
7+
"timeframe": "2024-2025",
8+
"stack": ["Meta Ads", "LinkedIn Campaign Manager", "Google Analytics", "Looker Studio", "A/B Testing"],
9+
"proofUrl": "https://github.com/AR10Dev",
10+
"proofLabel": "View campaign data"
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"order": 4,
3+
"title": "Social Media Growth Campaign",
4+
"category": "Marketing",
5+
"summary": "Developed and executed multi-platform social media strategy with data-driven content calendar, engagement tactics, and influencer partnerships.",
6+
"impact": "Grew combined following by 340% in 6 months, achieved 8.2% avg. engagement rate vs. 2.1% industry baseline.",
7+
"timeframe": "2024-2025",
8+
"stack": ["Meta Business Suite", "LinkedIn Analytics", "Buffer", "Canva", "Instagram Growth"],
9+
"proofUrl": "https://github.com/AR10Dev",
10+
"proofLabel": "View growth metrics"
11+
}

src/content/site/homepage.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,5 @@
99
"location": "Miami, Florida",
1010
"availability": "Available for freelance and advisory engagements",
1111
"contactEmail": "hello@ar10.dev",
12-
"bookingUrl": "https://cal.com/ar10dev"
12+
"bookingUrl": "https://cal.com/avaabrazzaq"
1313
}

src/layouts/Layout.astro

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ const {
3535
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
3636
<link rel="apple-touch-icon" href="/apple-touch-icon.png" />
3737
<link rel="canonical" href={canonicalUrl} />
38+
<link rel="preconnect" href="https://fonts.googleapis.com" />
39+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
40+
<link href="https://fonts.googleapis.com/css2?family=Manrope:wght@300;400;600;700;800&family=Sora:wght@300;400;600;700;800&display=swap" rel="stylesheet" />
3841
<SEO title={pageTitle} description={pageDescription} />
3942

4043
<meta property="og:type" content="website" />

src/lib/constants.ts

Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,22 @@
55

66
export const TYPOGRAPHY_CLASSES = {
77
kicker:
8-
"text-[0.75rem] font-bold uppercase tracking-[0.22em] text-[var(--accent-strong)]",
8+
"text-[0.75rem] font-bold uppercase tracking-[0.22em] text-[var(--accent-strong)] leading-[1.4]",
99
title:
10-
"font-['Sora','Segoe_UI',sans-serif] text-[clamp(1.7rem,3vw,2.35rem)] leading-[1.15] tracking-[-0.02em]",
10+
"font-['Sora',-apple-system,BlinkMacSystemFont,'Segoe_UI',sans-serif] text-[clamp(1.7rem,3vw,2.35rem)] leading-[1.2] tracking-[-0.02em] font-bold",
1111
heroTitle:
12-
"mt-4 text-balance font-['Sora','Segoe_UI',sans-serif] text-[clamp(2.1rem,5vw,4.3rem)] leading-[1.06] tracking-[-0.03em]",
13-
subheading: "text-2xl font-semibold text-[var(--text-strong)] md:text-3xl",
14-
body: "text-[clamp(1rem,2vw,1.2rem)] leading-[1.7] text-[var(--text-muted)]",
15-
label: "text-sm text-[var(--accent-strong)]",
16-
labelXs: "text-xs font-semibold text-[var(--accent-strong)]",
17-
cardTitle: "text-xl font-semibold text-[var(--text-strong)]",
18-
cardTitleBold: "text-xl font-bold leading-tight text-[var(--text-strong)]",
19-
cardBody: "text-base font-medium text-[var(--text-strong)]",
20-
cardBodySm: "text-sm text-[var(--text-muted)]",
21-
cardBodyXs: "text-xs text-[var(--text-muted)]",
12+
"mt-4 text-balance font-['Sora',-apple-system,BlinkMacSystemFont,'Segoe_UI',sans-serif] text-[clamp(2.1rem,5vw,4.3rem)] leading-[1.08] tracking-[-0.03em] font-extrabold",
13+
subheading: "text-2xl font-semibold text-[var(--text-strong)] md:text-3xl leading-[1.3] tracking-[-0.015em]",
14+
sectionTitle: "font-['Sora',-apple-system,BlinkMacSystemFont,'Segoe_UI',sans-serif] text-[clamp(1.5rem,2.5vw,2rem)] leading-[1.25] tracking-[-0.02em] font-bold",
15+
body: "text-[clamp(1rem,2vw,1.15rem)] leading-[1.75] text-[var(--text-muted)] font-normal",
16+
label: "text-sm text-[var(--accent-strong)] font-semibold leading-[1.5]",
17+
labelXs: "text-xs font-semibold text-[var(--accent-strong)] leading-[1.4] tracking-[0.02em]",
18+
cardTitle: "text-xl font-semibold text-[var(--text-strong)] leading-[1.3] tracking-[-0.01em]",
19+
cardTitleBold: "text-xl font-bold leading-[1.25] text-[var(--text-strong)] tracking-[-0.01em]",
20+
cardText: "text-base font-normal text-[var(--text-muted)] leading-[1.65]",
21+
cardBody: "text-base font-normal text-[var(--text-strong)] leading-[1.65]",
22+
cardBodySm: "text-sm text-[var(--text-muted)] leading-[1.6]",
23+
cardBodyXs: "text-xs text-[var(--text-muted)] leading-[1.5]",
2224
};
2325

2426
export const BUTTON_CLASSES = {

src/pages/blog/[...slug].astro

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
---
2+
import { getCollection, type CollectionEntry } from 'astro:content';
3+
import Layout from '@layouts/Layout.astro';
4+
import { TYPOGRAPHY_CLASSES } from '@lib/constants';
5+
6+
export async function getStaticPaths() {
7+
const blogPosts = await getCollection('blog', ({ data }) => {
8+
return data.draft !== true;
9+
});
10+
11+
return blogPosts.map(post => ({
12+
params: { slug: post.id },
13+
props: { post },
14+
}));
15+
}
16+
17+
interface Props {
18+
post: CollectionEntry<'blog'>;
19+
}
20+
21+
const { post } = Astro.props;
22+
const { Content, headings } = await post.render();
23+
24+
const title = `${post.data.title} | Avaab Razzaq`;
25+
const description = post.data.description;
26+
const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString();
27+
28+
// Format date helper
29+
function formatDate(date: Date): string {
30+
return new Intl.DateTimeFormat('en-US', {
31+
year: 'numeric',
32+
month: 'long',
33+
day: 'numeric'
34+
}).format(date);
35+
}
36+
37+
// Calculate reading time if not in frontmatter
38+
const readingTime = post.data.readingTime || (() => {
39+
const wordsPerMinute = 200;
40+
const content = post.body;
41+
const wordCount = content.split(/\s+/).length;
42+
return Math.ceil(wordCount / wordsPerMinute);
43+
})();
44+
45+
// Structured data for blog post
46+
const structuredData = {
47+
"@context": "https://schema.org",
48+
"@type": "BlogPosting",
49+
"headline": post.data.title,
50+
"description": post.data.description,
51+
"author": {
52+
"@type": "Person",
53+
"name": post.data.author,
54+
"url": "https://ar10dev.github.io"
55+
},
56+
"datePublished": post.data.publishDate.toISOString(),
57+
...(post.data.updatedDate && { "dateModified": post.data.updatedDate.toISOString() }),
58+
...(post.data.image && {
59+
"image": {
60+
"@type": "ImageObject",
61+
"url": post.data.image.src,
62+
"caption": post.data.image.alt
63+
}
64+
}),
65+
"publisher": {
66+
"@type": "Person",
67+
"name": "Avaab Razzaq"
68+
},
69+
"mainEntityOfPage": {
70+
"@type": "WebPage",
71+
"@id": canonicalUrl
72+
},
73+
"keywords": post.data.tags.join(", "),
74+
"articleSection": post.data.category
75+
};
76+
---
77+
78+
<Layout
79+
title={title}
80+
description={description}
81+
canonicalUrl={canonicalUrl}
82+
ogType="article"
83+
ogImage={post.data.image?.src}
84+
>
85+
<article class="min-h-screen bg-[var(--bg-canvas)] py-20 px-6">
86+
<div class="mx-auto max-w-3xl">
87+
88+
<!-- Back to Blog Link -->
89+
<a
90+
href="/blog/"
91+
class="inline-flex items-center gap-2 text-[var(--accent-strong)] hover:underline mb-8 group"
92+
>
93+
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="group-hover:-translate-x-1 transition-transform">
94+
<path d="M19 12H5M12 19l-7-7 7-7"/>
95+
</svg>
96+
Back to Blog
97+
</a>
98+
99+
<!-- Article Header -->
100+
<header class="mb-12">
101+
<!-- Category Badge -->
102+
<div class="mb-4">
103+
<span class="inline-block px-3 py-1 text-sm font-semibold rounded-full bg-[var(--accent-alpha)] text-[var(--accent-strong)] border border-[var(--accent-strong)]">
104+
{post.data.category}
105+
</span>
106+
</div>
107+
108+
<!-- Title -->
109+
<h1 class={`${TYPOGRAPHY_CLASSES.heroTitle} mb-6`}>
110+
{post.data.title}
111+
</h1>
112+
113+
<!-- Description -->
114+
<p class={`${TYPOGRAPHY_CLASSES.subheading} mb-8`}>
115+
{post.data.description}
116+
</p>
117+
118+
<!-- Meta Info -->
119+
<div class="flex flex-wrap items-center gap-4 text-sm text-[var(--text-base)] border-t border-b border-[var(--border-base)] py-4">
120+
<div class="flex items-center gap-2">
121+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
122+
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
123+
<circle cx="12" cy="7" r="4"></circle>
124+
</svg>
125+
<span>{post.data.author}</span>
126+
</div>
127+
128+
<div class="flex items-center gap-2">
129+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
130+
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect>
131+
<line x1="16" y1="2" x2="16" y2="6"></line>
132+
<line x1="8" y1="2" x2="8" y2="6"></line>
133+
<line x1="3" y1="10" x2="21" y2="10"></line>
134+
</svg>
135+
<time datetime={post.data.publishDate.toISOString()}>
136+
{formatDate(post.data.publishDate)}
137+
</time>
138+
</div>
139+
140+
{post.data.updatedDate && (
141+
<div class="flex items-center gap-2">
142+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
143+
<polyline points="23 4 23 10 17 10"></polyline>
144+
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"></path>
145+
</svg>
146+
<span>Updated {formatDate(post.data.updatedDate)}</span>
147+
</div>
148+
)}
149+
150+
<div class="flex items-center gap-2">
151+
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
152+
<circle cx="12" cy="12" r="10"></circle>
153+
<polyline points="12 6 12 12 16 14"></polyline>
154+
</svg>
155+
<span>{readingTime} min read</span>
156+
</div>
157+
</div>
158+
159+
<!-- Featured Image -->
160+
{post.data.image && (
161+
<div class="mt-8 overflow-hidden rounded-lg aspect-video bg-[var(--surface-strong)]">
162+
<img
163+
src={post.data.image.src}
164+
alt={post.data.image.alt}
165+
class="w-full h-full object-cover"
166+
loading="eager"
167+
/>
168+
</div>
169+
)}
170+
</header>
171+
172+
<!-- Table of Contents (if headings exist) -->
173+
{headings.length > 0 && (
174+
<aside class="mb-12 p-6 rounded-lg bg-[var(--surface-panel)] border border-[var(--border-base)]">
175+
<h2 class="text-lg font-bold text-[var(--text-strong)] mb-4">Table of Contents</h2>
176+
<nav>
177+
<ul class="space-y-2">
178+
{headings.map((heading) => (
179+
<li style={`margin-left: ${(heading.depth - 1) * 1}rem`}>
180+
<a
181+
href={`#${heading.slug}`}
182+
class="text-[var(--text-base)] hover:text-[var(--accent-strong)] transition-colors"
183+
>
184+
{heading.text}
185+
</a>
186+
</li>
187+
))}
188+
</ul>
189+
</nav>
190+
</aside>
191+
)}
192+
193+
<!-- Article Content -->
194+
<div class="prose prose-invert prose-lg max-w-none
195+
prose-headings:text-[var(--text-strong)] prose-headings:font-bold prose-headings:scroll-mt-20
196+
prose-h2:text-3xl prose-h2:mt-12 prose-h2:mb-6
197+
prose-h3:text-2xl prose-h3:mt-8 prose-h3:mb-4
198+
prose-p:text-[var(--text-base)] prose-p:leading-relaxed prose-p:mb-6
199+
prose-a:text-[var(--accent-strong)] prose-a:no-underline hover:prose-a:underline
200+
prose-strong:text-[var(--text-strong)] prose-strong:font-semibold
201+
prose-code:text-[var(--accent-strong)] prose-code:bg-[var(--surface-strong)] prose-code:px-1 prose-code:py-0.5 prose-code:rounded prose-code:before:content-[''] prose-code:after:content-['']
202+
prose-pre:bg-[var(--surface-strong)] prose-pre:border prose-pre:border-[var(--border-base)] prose-pre:rounded-lg
203+
prose-ul:text-[var(--text-base)] prose-ul:mb-6
204+
prose-ol:text-[var(--text-base)] prose-ol:mb-6
205+
prose-li:mb-2
206+
prose-blockquote:border-l-4 prose-blockquote:border-[var(--accent-strong)] prose-blockquote:pl-6 prose-blockquote:italic prose-blockquote:text-[var(--text-base)]
207+
prose-img:rounded-lg prose-img:shadow-lg
208+
">
209+
<Content />
210+
</div>
211+
212+
<!-- Tags -->
213+
{post.data.tags.length > 0 && (
214+
<div class="mt-12 pt-8 border-t border-[var(--border-base)]">
215+
<h3 class="text-sm font-semibold text-[var(--text-base)] mb-4">Tags:</h3>
216+
<div class="flex flex-wrap gap-2">
217+
{post.data.tags.map(tag => (
218+
<span class="px-3 py-1 text-sm rounded-full bg-[var(--surface-panel)] text-[var(--text-base)] border border-[var(--border-base)]">
219+
#{tag}
220+
</span>
221+
))}
222+
</div>
223+
</div>
224+
)}
225+
226+
<!-- Share & CTA Section -->
227+
<div class="mt-16 p-8 rounded-lg bg-[var(--surface-panel)] border border-[var(--border-base)] text-center">
228+
<h3 class={`${TYPOGRAPHY_CLASSES.sectionTitle} mb-4`}>
229+
Found this helpful?
230+
</h3>
231+
<p class={`${TYPOGRAPHY_CLASSES.body} mb-6`}>
232+
Let's work together on your next project. I specialize in AI automation, growth engineering, and full-stack development.
233+
</p>
234+
<a
235+
href="/#contact"
236+
class="inline-block px-6 py-3 rounded-lg bg-[var(--accent-strong)] text-white font-semibold hover:bg-[var(--accent-base)] transition-colors"
237+
>
238+
Get in Touch
239+
</a>
240+
</div>
241+
242+
</div>
243+
</article>
244+
245+
<!-- Structured Data -->
246+
<script type="application/ld+json" is:inline set:html={JSON.stringify(structuredData)} />
247+
</Layout>

0 commit comments

Comments
 (0)