chore: remove unused images, update project image paths to webp format, and enhance accessibility with aria-labels across components

This commit is contained in:
cojocaru-david
2025-08-14 03:21:50 +03:00
parent 57dc793005
commit 865b182062
37 changed files with 152 additions and 88 deletions

View File

@@ -7,7 +7,7 @@ function BuyMeCoffee({ classname }: { classname?: string }) {
href='https://ko-fi.com/cojoo'
target='_blank'
className={cn(
'border relative group w-36 mx-auto cursor-pointer h-32 grid place-content-center p-10 py-14 bg-primary rounded-md overflow-hidden',
'border relative group w-36 mx-auto cursor-pointer h-32 grid place-content-center p-10 py-14 bg-primary rounded-md overflow-hidden',
classname
)}
>

View File

@@ -84,7 +84,7 @@ const Navbar = () => {
<>
<motion.header
aria-label="Navigation"
role="navigation"
role="banner"
layout={!isMobile}
initial={sizeVariants[0]}
animate={isMobile ? sizeVariants[0] : sizeVariants[scrollLevel]}
@@ -113,7 +113,6 @@ const Navbar = () => {
className="font-custom flex shrink-0 items-center gap-2 text-xl font-bold"
aria-label="Home"
title="Home"
navigation="true"
>
<Logo className="h-8 w-8" />
<span className={
@@ -123,7 +122,7 @@ const Navbar = () => {
</Link>
<div className="flex items-center gap-2 md:gap-4">
<nav className="hidden items-center gap-6 md:flex" aria-label="Main navigation" role="navigation">
<nav className="hidden items-center gap-6 md:flex" aria-label="Main navigation">
{NAV_LINKS.map((item) => {
const isActive = activePath.startsWith(item.href) && item.href !== "/";
return (

View File

@@ -11,9 +11,8 @@ function Logo({ className }: { className?: string }) {
)}
role="img"
aria-label="Logo"
aria-hidden="true"
name="logo"
focusable="false"
aria-hidden="true"
focusable="false"
>
<path
d="M0 0h216.667C290.305 0 350 59.546 350 133s-59.695 133-133.333 133L0 0zm231.629 231.641c48.131-7.203 85.038-48.623 85.038-98.641 0-55.09-44.772-99.75-100-99.75h-54.862C185.557 59.722 200 94.678 200 133c0 17.331-2.96 33.991-8.405 49.492l40.034 49.149zm-66.243-81.326c.844-5.646 1.281-11.428 1.281-17.315 0-42.333-22.66-79.386-56.541-99.75H70.032l95.354 117.065z"

View File

@@ -8,6 +8,7 @@ export const SITE: Site = {
author: 'Cojocaru David',
locale: 'en-US',
location: 'Romania',
email: 'contact@cojocarudavid.me'
}
export const NAV_LINKS: SocialLink[] = [

View File

@@ -97,7 +97,7 @@ export default function ThemeToggle() {
localStorage.setItem("theme", newTheme);
};
return <button onClick={toggleTheme}>{theme === "light" ? "🌙" : "🌞"}</button>;
return <button onClick={toggleTheme} aria-label="Toggle theme">{theme === "light" ? "🌙" : "🌞"}</button>;
}
```

View File

@@ -123,8 +123,8 @@ Create `index.html` in the same folder. Paste and save.
<body>
<h1>💬 Mini Chat</h1>
<div id="chat"></div>
<input id="msgBox" placeholder="Type and hit Enter">
<button onclick="sendMsg()">Send</button>
<input id="msgBox" placeholder="Type and hit Enter" aria-label="Message input">
<button onclick="sendMsg()" aria-label="Send message">Send</button>
<script>
const socket = new WebSocket('ws://localhost:8080');

View File

@@ -109,7 +109,7 @@ const Task = ({ task, onDelete, onToggle }) => {
/>
<span>{task.text}</span>
</div>
<button onClick={() => onDelete(task.id)} className="delete-btn">
<button onClick={() => onDelete(task.id)} className="delete-btn" aria-label="Delete task">
Delete
</button>
</div>
@@ -187,7 +187,7 @@ const TaskForm = ({ onAdd }) => {
placeholder="What needs to be done?"
className="task-input"
/>
<button type="submit" className="add-btn">
<button type="submit" className="add-btn" aria-label="Add task">
Add Task
</button>
</form>

View File

@@ -127,9 +127,9 @@ Open `index.html` and paste this:
<h1>🌤️ Weather Check</h1>
<div class="search-box">
<input type="text" id="city-input" placeholder="Enter city name...">
<button id="search-btn">Search</button>
<button id="location-btn">Use My Location</button>
<input type="text" id="city-input" placeholder="Enter city name..." aria-label="City input">
<button id="search-btn" aria-label="Search">Search</button>
<button id="location-btn" aria-label="Use my location">Use My Location</button>
</div>
<div id="weather-info" class="hidden">

View File

@@ -51,7 +51,7 @@ Instead of writing CSS like:
I just typed:
```html
<button class="px-8 py-3 bg-blue-500 text-white rounded-lg font-semibold hover:bg-blue-600 transition">
<button class="px-8 py-3 bg-blue-500 text-white rounded-lg font-semibold hover:bg-blue-600 transition" aria-label="Get started">
Get Started
</button>
```
@@ -420,7 +420,7 @@ Let's build something useful. A **responsive card** that looks great everywhere.
<div class="p-6">
<h3 class="text-xl font-semibold text-gray-900">Card Title</h3>
<p class="mt-2 text-gray-600">Brief description here</p>
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
<button class="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" aria-label="Learn more">
Learn More
</button>
</div>
@@ -432,7 +432,7 @@ Let's build something useful. A **responsive card** that looks great everywhere.
```html
<!-- Changes to horizontal layout on desktop -->
<div class="max-w-4xl mx-auto md:flex">
<img class="md:w-48 md:h-auto" src="/img.jpg">
<img class="md:w-48 md:h-auto" src="/img.jpg" alt="Card image">
<div class="p-6">
<!-- Same content, now horizontal -->
</div>

View File

@@ -103,12 +103,12 @@ Without dimensions, the browser can't reserve space. When the image finally load
Good:
```html
<img src="dog.jpg" width="800" height="450" loading="lazy">
<img src="dog.jpg" width="800" height="450" loading="lazy" alt="Dog">
```
Bad:
```html
<img src="dog.jpg" loading="lazy">
<img src="dog.jpg" loading="lazy" alt="Dog">
```
### **Mistake 3: Overdoing Fade-In Animations**

View File

@@ -2,7 +2,7 @@
name: 'cojocarudavid.me (old)'
description: 'Blazing fast personal website built with Astro.js and styled with Tailwind CSS.'
tags: ['astro', 'tailwindcss', 'typescript']
image: '../../../public/static/cojocarudavidme.png'
image: '../../../public/static/cojocarudavidme.webp'
link: 'https://github.com/cojocaru-david/cojocarudavid.me'
startDate: '2023-10-16'
endDate: '2025-04-28'

View File

@@ -2,7 +2,7 @@
name: 'David Dark Code'
description: 'David Dark is a dark theme for VS Code. It is based on the github theme and the default dark theme. It is designed to be easy on the eyes and to be used for long periods of time.'
tags: ['vscode', 'theme', 'dark']
image: '../../../public/static/david-dark-code.png'
image: '../../../public/static/david-dark-code.webp'
link: 'https://github.com/cojocaru-david/david-dark-code'
startDate: '2023-10-17'
endDate: '2023-10-17'

View File

@@ -2,7 +2,7 @@
name: 'Dream Home Template'
description: 'A modern, responsive real estate platform built with React, Vite, and Tailwind CSS. Find your dream home with intuitive search, detailed property listings, and seamless user experience.'
tags: ['react', 'vite', 'tailwindcss']
image: '../../../public/static/dream-home-template.png'
image: '../../../public/static/dream-home-template.webp'
link: 'https://dreamhome.cojocarudavid.me'
startDate: '2025-04-16'
endDate: '2025-05-29'

View File

@@ -2,7 +2,7 @@
name: 'Modern Portfolio'
description: 'Modern Portfolio is a personal website that showcases my work and projects. It is built with Astro.js and styled with Tailwind CSS, providing a fast and responsive user experience. The website features a clean design, easy navigation, and a focus on showcasing my skills and projects.'
tags: ['astro', 'tailwindcss', 'typescript']
image: '../../../public/static/modern-portfolio.png'
image: '../../../public/static/modern-portfolio.webp'
link: 'https://cojocarudavid.me'
startDate: '2025-03-30'
---

View File

@@ -0,0 +1,48 @@
---
name: 'SnippetsLibrary'
description: 'A secure, lightning-fast code snippet manager to store, organize, and share your code with beautiful syntax highlighting.'
tags: ['react', 'vite', 'tailwindcss', 'typescript', 'bun', 'hono', 'drizzle-orm', 'postgresql', 'jwt', 'github-oauth', 'cloudflare-workers']
image: '../../../public/static/snippetslibrary.webp'
link: 'https://snippetslibrary.com'
startDate: '2025-07-23'
---
# SnippetsLibrary
Manage your personal and team code snippets with speed and confidence. SnippetsLibrary makes it easy to create, organize, search, and share snippets with first-class DX and beautiful syntax highlighting.
I have 50+ users and I'm working on a new version with a lot of new features.
And 200+ snippets.
## ✨ Highlights
- **Secure auth**: GitHub OAuth + JWT sessions
- **Sharing**: Public share links with SEO-friendly pages
- **Search & filter**: Language, visibility, keywords, pagination
- **Syntax highlighting**: 20+ languages, themes, copy-preserving formatting
- **Fast**: Bun runtime, Hono server, optimized client with Vite
## 🛠️ Tech Stack
- Frontend: React, Vite, Tailwind CSS, shadcn/ui
- Backend: Bun, Hono, JWT, Drizzle ORM, PostgreSQL
- Infra: Cloudflare Workers, GitHub Actions, Drizzle Kit
## 🚀 Quick Start
1. Clone the repo:
```bash
git clone https://github.com/cojocaru-david/snippetslibrary.com
cd snippetslibrary.com
```
2. Install deps: `bun install`
3. Configure `.env` in `server/` (DB, GitHub OAuth, JWT, FRONTEND_URL)
4. Migrate DB: `cd server && bun run db:migrate`
5. Start dev: `bun run dev` (client on 5173, server on 8000)
## 🔗 Links
- Live: https://snippetslibrary.com
- Repo: https://github.com/cojocaru-david/snippetslibrary.com

View File

@@ -2,7 +2,7 @@
name: 'TailCI'
description: 'TailCI is a lightweight, fast, and modern web application built with CodeIgniter and styled with Tailwind CSS. It combines the simplicity of CodeIgniters PHP framework with the utility-first power of Tailwind CSS for rapid development and clean design.'
tags: ['codeigniter', 'tailwindcss', 'php']
image: '../../../public/static/tailci.png'
image: '../../../public/static/tailci.webp'
link: 'https://tailci.cojocarudavid.me'
startDate: '2025-03-30'
---

View File

@@ -107,7 +107,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
{headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose col-start-2 max-w-none">
<article class="prose col-start-2 max-w-none" aria-label="Blog post">
<Content />
</article>
@@ -137,7 +137,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Facebook"
aria-label="Share on Facebook"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on Facebook">
<Icon name="line-md:facebook" class="size-4" />
</Button>
</Link>
@@ -148,7 +148,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Twitter"
aria-label="Share on Twitter"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on Twitter">
<Icon name="line-md:twitter" class="size-4" />
</Button>
</Link>
@@ -159,7 +159,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on LinkedIn"
aria-label="Share on LinkedIn"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on LinkedIn">
<Icon name="line-md:linkedin" class="size-4" />
</Button>
</Link>
@@ -170,7 +170,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Reddit"
aria-label="Share on Reddit"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on Reddit">
<Icon name="line-md:reddit-loop" class="size-4" />
</Button>
</Link>
@@ -181,7 +181,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on WhatsApp"
aria-label="Share on WhatsApp"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on WhatsApp">
<Icon name="mdi:whatsapp" class="size-4" />
</Button>
</Link>
@@ -192,7 +192,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Telegram"
aria-label="Share on Telegram"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on Telegram">
<Icon name="line-md:telegram" class="size-4" />
</Button>
</Link>
@@ -203,7 +203,7 @@ const canonicalUrl = new URL(`/blog/${post.id}/`, Astro.site).toString();
title="Share on Pinterest"
aria-label="Share on Pinterest"
>
<Button variant="outline" size="icon">
<Button variant="outline" size="icon" aria-label="Share on Pinterest">
<Icon name="mdi:pinterest" class="size-4" />
</Button>
</Link>

View File

@@ -88,16 +88,16 @@ const currentUrl = Astro.url;
<Image
src={project.data.image}
alt={project.data.name}
class="col-start-2 mb-8 h-auto w-full rounded-3xl object-cover"
class="col-start-2 mb-8 h-[500px] w-full rounded-3xl object-cover"
loading="lazy"
fetchpriority="low"
width={1800}
height={1600}
width={1200}
height={800}
/>
{headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose col-start-2 max-w-none">
<article class="prose col-start-2 max-w-none" aria-label="Project">
<Content />
</article>
@@ -116,7 +116,7 @@ const currentUrl = Astro.url;
<h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4">
<li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer">
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link}
</a>
</li>

View File

@@ -58,7 +58,7 @@ const currentUrl = Astro.url;
<div
class="group h-full w-full transition-all duration-300 hover:translate-y-[-4px] even:sm:mt-14"
>
<a class="flex flex-col h-full w-full rounded-2xl overflow-hidden bg-card hover:shadow-lg transition-all duration-300 border border-card-foreground/10" href={`/projects/${project.id}`}>
<a class="flex flex-col h-full w-full rounded-2xl overflow-hidden bg-card hover:shadow-lg transition-all duration-300 border border-card-foreground/10" href={`/projects/${project.id}`} aria-label="Project link">
<div
class="aspect-[16/10] w-full overflow-hidden"
>

View File

@@ -1,43 +1,50 @@
import { SITE } from '@/consts'
import rss from '@astrojs/rss'
import type { APIContext } from 'astro'
import { getAllPosts } from '@/lib/data-utils'
import { SITE } from '@/consts';
import rss from '@astrojs/rss';
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
export async function GET(context: APIContext) {
export const GET: APIRoute = async ({ site }) => {
try {
const posts = await getAllPosts()
const blogPosts = await getCollection('blog');
const posts = blogPosts
.filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
const now = new Date();
const lastBuildDate = posts.length > 0 ? posts[0].data.date : now;
return rss({
title: `${SITE.title} - Tech Blog`,
description: SITE.description,
site: context.site ?? SITE.href,
site: site ?? SITE.href.replace(/\/$/, ''),
trailingSlash: false,
customData: `
<language>${SITE.locale || 'en-us'}</language>
<lastBuildDate>${lastBuildDate.toUTCString()}</lastBuildDate>
<ttl>60</ttl>
<managingEditor>${SITE.author} (${SITE.email || 'contact@cojocarudavid.me'})</managingEditor>
<webMaster>${SITE.author} (${SITE.email || 'contact@cojocarudavid.me'})</webMaster>
<image>
<url>${new URL('/ogImage.png', site ?? SITE.href).href}</url>
<title>${SITE.title}</title>
<link>${site ?? SITE.href}</link>
</image>
<atom:link href="${new URL('/rss.xml', site ?? SITE.href).href}" rel="self" type="application/rss+xml" />
`.trim(),
xmlns: {
atom: 'http://www.w3.org/2005/Atom'
},
items: posts.map((post) => ({
title: post.data.title,
description: post.data.description,
description: post.data.description || '',
pubDate: post.data.date,
link: `/blog/${post.id}/`,
categories: post.data.tags || [],
author: post.data.authors ? post.data.authors.join(', ') : SITE.author,
customData: `
<language>${SITE.locale}</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
${post.data.tags ? `<category>${post.data.tags.join(', ')}</category>` : ''}
`.trim(),
})),
customData: `
<language>${SITE.locale}</language>
<lastBuildDate>${new Date().toUTCString()}</lastBuildDate>
<ttl>60</ttl>
<image>
<url>${new URL('/ogImage.png', SITE.href).toString()}</url>
<title>${SITE.title}</title>
<link>${SITE.href}</link>
</image>
`.trim(),
})
});
} catch (error) {
console.error('Error generating RSS feed:', error)
return new Response('Error generating RSS feed', { status: 500 })
console.error('Error generating RSS feed:', error);
return new Response('Error generating RSS feed', { status: 500 });
}
}
};

View File

@@ -118,17 +118,5 @@
@apply text-foreground bg-muted border-border rounded-md border px-3 py-1 font-mono text-xs font-medium shadow-sm;
@apply [&>span[data-line='']>*]:text-(--shiki-light) dark:[&>span[data-line='']>*]:text-(--shiki-dark);
}
.katex-display {
@apply my-8 overflow-x-auto overflow-y-hidden py-3 tracking-normal;
@apply [&>span[data-line='']>*]:text-(--shiki-light) dark:[&>span[data-line='']>*]:text-(--shiki-dark);
}
.katex {
@apply text-foreground;
}
.katex * {
@apply text-foreground;
}
}
}

View File

@@ -5,6 +5,7 @@ export type Site = {
author: string
locale: string
location: string
email: string
}
export type SocialLink = {