Compare commits

..

3 Commits

Author SHA1 Message Date
patrick 44039f3587 content: add 2eInk project 2026-05-14 13:17:39 -04:00
patrick 38d081f506 chore: update image for marketing site project 2026-05-14 13:16:41 -04:00
patrick 23e5e17be5 feat: add support for multiple links in projects
Projects now support multiple links (source code, demo, etc) each with a label. Migrated all projects to use the new schema.
2026-05-14 12:49:57 -04:00
11 changed files with 85 additions and 19 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 57 KiB

+4 -1
View File
@@ -24,7 +24,10 @@ const projects = defineCollection({
description: z.string(),
tags: z.array(z.string()),
image: image(),
link: z.url().optional(),
links: z.array(z.object({
label: z.string(),
url: z.url()
})).optional(),
order: z.number().optional(),
startDate: z.coerce.date().optional(),
endDate: z.coerce.date().optional(),
+31
View File
@@ -0,0 +1,31 @@
---
name: '2eInk - Image Converter'
description: 'Convert images to optimized screensavers for e-ink devices. Runs entirely in your browser.'
tags: ['svelte', 'web-assembly', 'tailwindcss']
image: '../../../public/static/2e-ink.webp'
links:
- label: 'Live Demo'
url: 'https://2e-ink.jaroszew.ski'
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/2eInk'
order: 6
startDate: '2026-05-12'
endDate: '2026-05-14'
---
<div class="flex flex-wrap gap-2 my-0! [&>img]:my-0! [&>img]:mt-2!" >
<img src="https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="Svelte" />
<img src="https://img.shields.io/badge/WebAssembly-654FF0?style=for-the-badge&logo=webassembly&logoColor=white" alt="WebAssembly" />
<img src="https://img.shields.io/badge/Tailwind_CSS-38B2AC?style=for-the-badge&logo=tailwind-css&logoColor=white" alt="TailwindCSS" />
</div>
A privacy-focused web app that optimizes your photos and artwork for e-ink displays. I built this to create custom screensavers for my Kindle. Built with Svelte and WebAssembly, all image processing happens entirely client-side.
E-ink screens have unique display characteristics: limited grayscale levels, specific resolutions, and slow refresh rates. 2eInk handles the technical complexity of preparing images for these devices, applying professional-grade transformations:
- **Smart Resizing** - Automatically scales images to match your device's exact resolution
- **Grayscale Quantization** - Maps colors to the specific gray levels your display supports (typically 4, 8, or 16 levels)
- **Advanced Dithering** - Uses Floyd-Steinberg dithering to simulate additional gray tones and eliminate banding artifacts
- **Fine-tuning Controls** - Adjust contrast, brightness, gamma, and sharpening for optimal visual quality
Batch process entire photo albums in seconds. Download a zip of optimized images ready to transfer to your e-reader, digital picture frame, or other e-ink device.
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Bullseye'
description: 'Bullseye is a self-hosted offline-first personal utilities app with habit tracking, tasks, and notes.'
tags: ['typescript', 'svelte', 'sqlite']
image: '../../../public/static/bullseye.webp'
link: 'https://git.jaroszew.ski/patrick/bullseye-app'
links:
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/bullseye-app'
order: 6
startDate: '2025-03-30'
---
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Chitai'
description: 'Chitai is a self-hosted eBook management application'
tags: ['python', 'typescript', 'postgresql', 'svelte', 'open-source']
image: '../../../public/static/chitai.webp'
link: 'https://git.jaroszew.ski/patrick/chitai'
links:
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/chitai'
order: 2
startDate: '2025-03-30'
---
+1 -1
View File
@@ -8,7 +8,7 @@ startDate: '2025-09-23'
---
<div class="flex flex-wrap gap-2 my-0! [&>img]:my-0! [&>img]:mt-2!" >
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python" />
<img src="https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=FFD43B" alt="Python" />
<img src="https://img.shields.io/badge/PostgreSQL-336791?style=for-the-badge&logo=postgresql&logoColor=white" alt="PostgreSQL" />
<img src="https://img.shields.io/badge/TypeScript-007ACC?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript" />
<img src="https://img.shields.io/badge/Svelte-FF3E00?style=for-the-badge&logo=svelte&logoColor=white" alt="Svelte" />
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Marketing Site'
description: 'A simple marketing site built for a small business.'
tags: ['astro', 'tailwindcss']
image: '../../../public/static/druksubli.webp'
link: 'https://druksubli.pl'
links:
- label: 'Live Site'
url: 'https://druksubli.pl'
order: 5
startDate: '2025-04-16'
endDate: '2025-05-29'
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Homelab'
description: 'My homelab is where I tinker and learn new technologies.'
tags: ['proxmox', 'networking', 'hardware']
image: '../../../public/static/homelab.webp'
link: 'https://git.jaroszew.ski/ansible'
links:
- label: 'Ansible Configs'
url: 'https://git.jaroszew.ski/ansible'
order: 1
startDate: '2023-05-15'
---
+5 -1
View File
@@ -3,7 +3,11 @@ name: 'Personal Portfolio'
description: 'Personal 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', 'svelte']
image: '../../../public/static/portfolio.webp'
link: 'https://patrick.jaroszew.ski'
links:
- label: 'Live Site'
url: 'https://patrick.jaroszew.ski'
- label: 'Source Code'
url: 'https://git.jaroszew.ski/patrick/portfolio'
order: 4
startDate: '2026-03-10'
---
+22 -2
View File
@@ -126,18 +126,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p>
</div>
{project.data.link && (
{(project.data.links || project.data.link) && (
<>
<hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4">
{project.data.links ? (
project.data.links.map((link) => (
<li>
<a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
{link.label}
</a>
</li>
))
) : project.data.link ? (
<li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link}
</a>
</li>
) : null}
</ul>
</div>
</>
@@ -155,18 +165,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p>
</div>
{project.data.link && (
{(project.data.links || project.data.link) && (
<>
<hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4">
{project.data.links ? (
project.data.links.map((link) => (
<li>
<a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
{link.label}
</a>
</li>
))
) : project.data.link ? (
<li>
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link">
{project.data.link}
</a>
</li>
) : null}
</ul>
</div>
</>