Compare commits

..

5 Commits

Author SHA1 Message Date
patrick 19f1ab3996 fix: remove deprecated link property from project template
Generate a build and push to Cloudflare Pages / Build and Deploy to Cloudflare Pages (push) Successful in 1m27s
2026-05-14 13:34:11 -04:00
patrick 98dbf0e885 content: fix error in CF Pages CI/CD post
Generate a build and push to Cloudflare Pages / Build and Deploy to Cloudflare Pages (push) Failing after 46s
Update the post to mention that the Cloudflare Pages project should
exist before running the workflow - the wrangler action does not create
it as previously stated.
2026-05-14 13:21:58 -04:00
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
12 changed files with 91 additions and 22 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

+5 -1
View File
@@ -24,7 +24,11 @@ const projects = defineCollection({
description: z.string(), description: z.string(),
tags: z.array(z.string()), tags: z.array(z.string()),
image: image(), image: image(),
link: z.url().optional(), link: z.string().url().optional(),
links: z.array(z.object({
label: z.string(),
url: z.string().url()
})).optional(),
order: z.number().optional(), order: z.number().optional(),
startDate: z.coerce.date().optional(), startDate: z.coerce.date().optional(),
endDate: z.coerce.date().optional(), endDate: z.coerce.date().optional(),
+5 -3
View File
@@ -24,6 +24,7 @@ Before getting started, you'll need:
- A Gitea instance with Actions enabled ([documentation](https://docs.gitea.com/usage/actions/overview)) - A Gitea instance with Actions enabled ([documentation](https://docs.gitea.com/usage/actions/overview))
- A registered Gitea runner (ubuntu-latest or equivalent) - A registered Gitea runner (ubuntu-latest or equivalent)
- A Cloudflare account with access to Pages - A Cloudflare account with access to Pages
- An existing Cloudflare Pages project
- Your project set up with pnpm (adjust commands for npm/yarn as needed) - Your project set up with pnpm (adjust commands for npm/yarn as needed)
--- ---
@@ -74,6 +75,10 @@ jobs:
command: pages deploy ./dist --project-name="your-project-name" command: pages deploy ./dist --project-name="your-project-name"
``` ```
> [!WARNING]
> If your Cloudflare Pages project doesn't exist yet, create it before running the workflow, otherwise it will fail.
The workflow triggers on pushes to `main`, installs dependencies, builds the site, and deploys using Cloudflare's Wrangler action. Every push to main goes straight to production. The workflow triggers on pushes to `main`, installs dependencies, builds the site, and deploys using Cloudflare's Wrangler action. Every push to main goes straight to production.
> [!NOTE] > [!NOTE]
@@ -83,9 +88,6 @@ The workflow triggers on pushes to `main`, installs dependencies, builds the sit
## Required Secrets ## Required Secrets
> [!TIP]
> If your Cloudflare Pages project doesn't exist yet, Wrangler will create it automatically on the first deployment.
You'll need to configure two secrets in your Gitea repository (Settings → Actions → Secrets): You'll need to configure two secrets in your Gitea repository (Settings → Actions → Secrets):
- `CLOUDFLARE_API_TOKEN` — Create this in the Cloudflare dashboard: Profile → API Tokens → Create Token → Use the "Edit Cloudflare Pages" template (or create a custom token with **Account: Cloudflare Pages: Edit** permission) - `CLOUDFLARE_API_TOKEN` — Create this in the Cloudflare dashboard: Profile → API Tokens → Create Token → Use the "Edit Cloudflare Pages" template (or create a custom token with **Account: Cloudflare Pages: Edit** permission)
+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.' description: 'Bullseye is a self-hosted offline-first personal utilities app with habit tracking, tasks, and notes.'
tags: ['typescript', 'svelte', 'sqlite'] tags: ['typescript', 'svelte', 'sqlite']
image: '../../../public/static/bullseye.webp' 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 order: 6
startDate: '2025-03-30' startDate: '2025-03-30'
--- ---
+3 -1
View File
@@ -3,7 +3,9 @@ name: 'Chitai'
description: 'Chitai is a self-hosted eBook management application' description: 'Chitai is a self-hosted eBook management application'
tags: ['python', 'typescript', 'postgresql', 'svelte', 'open-source'] tags: ['python', 'typescript', 'postgresql', 'svelte', 'open-source']
image: '../../../public/static/chitai.webp' 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 order: 2
startDate: '2025-03-30' 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!" > <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/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/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" /> <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.' description: 'A simple marketing site built for a small business.'
tags: ['astro', 'tailwindcss'] tags: ['astro', 'tailwindcss']
image: '../../../public/static/druksubli.webp' image: '../../../public/static/druksubli.webp'
link: 'https://druksubli.pl' links:
- label: 'Live Site'
url: 'https://druksubli.pl'
order: 5 order: 5
startDate: '2025-04-16' startDate: '2025-04-16'
endDate: '2025-05-29' 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.' description: 'My homelab is where I tinker and learn new technologies.'
tags: ['proxmox', 'networking', 'hardware'] tags: ['proxmox', 'networking', 'hardware']
image: '../../../public/static/homelab.webp' image: '../../../public/static/homelab.webp'
link: 'https://git.jaroszew.ski/ansible' links:
- label: 'Ansible Configs'
url: 'https://git.jaroszew.ski/ansible'
order: 1 order: 1
startDate: '2023-05-15' 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.' 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'] tags: ['astro', 'tailwindcss', 'typescript', 'svelte']
image: '../../../public/static/portfolio.webp' 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 order: 4
startDate: '2026-03-10' startDate: '2026-03-10'
--- ---
+32 -12
View File
@@ -126,18 +126,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p> <p>{project.data.description}</p>
</div> </div>
{project.data.link && ( {(project.data.links || project.data.link) && (
<> <>
<hr class="my-4 border-t" /> <hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground"> <div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3> <h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4"> <ul class="list-disc pl-4">
<li> {project.data.links ? (
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link"> project.data.links.map((link) => (
{project.data.link} <li>
</a> <a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
</li> {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> </ul>
</div> </div>
</> </>
@@ -155,18 +165,28 @@ const currentUrl = Astro.url;
<p>{project.data.description}</p> <p>{project.data.description}</p>
</div> </div>
{project.data.link && ( {(project.data.links || project.data.link) && (
<> <>
<hr class="my-4 border-t" /> <hr class="my-4 border-t" />
<div class="flex flex-col gap-2 text-sm text-muted-foreground"> <div class="flex flex-col gap-2 text-sm text-muted-foreground">
<h3 class="text-base font-semibold">Project Links</h3> <h3 class="text-base font-semibold">Project Links</h3>
<ul class="list-disc pl-4"> <ul class="list-disc pl-4">
<li> {project.data.links ? (
<a href={project.data.link} target="_blank" rel="noopener noreferrer" aria-label="Project link"> project.data.links.map((link) => (
{project.data.link} <li>
</a> <a href={link.url} target="_blank" rel="noopener noreferrer" aria-label={link.label}>
</li> {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> </ul>
</div> </div>
</> </>