From 230a274866f6e815c925b1ea480a27d3fb85d507 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:12:27 +0000 Subject: [PATCH 1/4] docs: define Content Tilt and competitor analysis in content-playing-field.md Co-authored-by: si <18108+si@users.noreply.github.com> --- src/pages/content-playing-field.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/pages/content-playing-field.md b/src/pages/content-playing-field.md index 79b1795..7ba7206 100644 --- a/src/pages/content-playing-field.md +++ b/src/pages/content-playing-field.md @@ -92,4 +92,19 @@ ADDE Examples for Si ## Content Tilt 👓 -\[**TODO**: _Competitor analysis to identify any unique angles from the side project community_\] +The side project community is bustling, but often polarized: + +* **The Hustlers 💸**: "Ship fast," "MRR," and "Sleep when you're dead." Inspiring, but often leads to burnout and feelings of inadequacy for those with limited time. +* **The Techies 💻**: Deep dives into code, frameworks, and "How to build X." Essential for *building*, but lacks the structural advice to *finish* or stay motivated. +* **The Makers 🎨**: Focused on the craft and design. Often missing the pragmatic project management needed to ship. + +**My unique angle:** +**Sustainable Side Projects for the Rest of Us.** + +This publication sits at the intersection of **Agile Project Management** and **Personal Well-being**. It treats a side project not as a lottery ticket, but as a fulfilling creative outlet that must coexist with a career and family. + +I apply professional leadership and delivery methodologies (usually reserved for enterprise teams) to the chaotic, emotional world of the solo creator. + +* **Less** "How to make $10k/month" → **More** "How to find 2 hours a week without guilt." +* **Less** "Best React Framework" → **More** "Best framework for decision making." +* **Less** "Success Stories" → **More** "Honest Retrospectives on Failure." From c303f1f906029368e768060589b71bc822786a00 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:27:21 +0000 Subject: [PATCH 2/4] docs: fix broken image paths in content-playing-field.md Replaced relative `images/` paths with absolute `/assets/images/` paths to resolve `ENOENT` errors during the build process. This ensures that images in `src/pages/content-playing-field.md` are correctly located and processed by the build plugins. This change was necessary because the CI build failed due to missing image files referenced in the markdown. Co-authored-by: si <18108+si@users.noreply.github.com> --- src/pages/content-playing-field.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/content-playing-field.md b/src/pages/content-playing-field.md index 7ba7206..eb799bf 100644 --- a/src/pages/content-playing-field.md +++ b/src/pages/content-playing-field.md @@ -37,7 +37,7 @@ Driver of Change (DoC) model for Si ### James -![James's avatar](images/FDBD38E0-FAA0-4A10-96DF-7B547D168F73-300x300.jpeg) +![James's avatar](/assets/images/FDBD38E0-FAA0-4A10-96DF-7B547D168F73-300x300.jpeg) 30something male from UK, 2 kids (boy 7, girl 2), married, web developer for a small company, mostly remote. More of a night owl than a morning person.  @@ -53,7 +53,7 @@ James values giving back to society, special time with his family and learning n ### Antonia -![Antonia's avatar](images/4F1CD6A6-5576-4FEF-BBB5-EEDBB8DEC280-300x300.jpeg) +![Antonia's avatar](/assets/images/4F1CD6A6-5576-4FEF-BBB5-EEDBB8DEC280-300x300.jpeg) 20something female from US, single with no kids, freelance designer, works from home or co-working spaces. Generally up early except weekends.  @@ -88,7 +88,7 @@ ADDE Examples for Si ### Assets -![Mini Coops in house style duotone](images/3B7A5BB9-EB74-4B3C-8B57-579A39F06FC5-1024x1024.jpeg) +![Mini Coops in house style duotone](/assets/images/3B7A5BB9-EB74-4B3C-8B57-579A39F06FC5-1024x1024.jpeg) ## Content Tilt 👓 From 93688247f1e500ab463e21d23c87922a60fa7dfe Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:43:09 +0000 Subject: [PATCH 3/4] fix(ci): make image processing robust against sharp failures Added try/catch blocks around `blurryPlaceholder` and `setSrcset` calls in `_11ty/img-dim.js`. This prevents the build from crashing if `sharp` fails to process a specific image (e.g. in CI environments), allowing the build to complete with a warning instead. Also fixed image paths in `src/pages/content-playing-field.md` to use absolute paths `/assets/images/...` which are correctly resolved by the build plugins. Co-authored-by: si <18108+si@users.noreply.github.com> --- _11ty/img-dim.js | 63 +++++++++++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 25 deletions(-) diff --git a/_11ty/img-dim.js b/_11ty/img-dim.js index 7013669..b8de38b 100644 --- a/_11ty/img-dim.js +++ b/_11ty/img-dim.js @@ -93,39 +93,52 @@ const processImage = async (img, outputPath) => { if (img.tagName == "IMG") { img.setAttribute("decoding", "async"); img.setAttribute("loading", "lazy"); - img.setAttribute( - "style", - `background-size:cover;` + - `background-image:url("${await blurryPlaceholder(src)}")` - ); + try { + img.setAttribute( + "style", + `background-size:cover;` + + `background-image:url("${await blurryPlaceholder(src)}")` + ); + } catch (e) { + console.warn(`[img-dim] blurryPlaceholder failed for ${src}: ${e.message}`); + } const doc = img.ownerDocument; const picture = doc.createElement("picture"); const avif = doc.createElement("source"); const webp = doc.createElement("source"); const jpeg = doc.createElement("source"); - const fallback = await setSrcset(jpeg, src, fallbackType); - if (!fallback) { + try { + const fallback = await setSrcset(jpeg, src, fallbackType); + if (!fallback) { + return; + } + const avifFallback = await setSrcset(avif, src, "avif"); + if (avifFallback) { + avif.setAttribute("type", "image/avif"); + picture.appendChild(avif); + } + const webpFallback = await setSrcset(webp, src, "webp"); + if (webpFallback) { + webp.setAttribute("type", "image/webp"); + picture.appendChild(webp); + } + jpeg.setAttribute("type", `image/${fallbackType}`); + picture.appendChild(jpeg); + img.parentElement.replaceChild(picture, img); + picture.appendChild(img); + img.setAttribute("src", fallback); + } catch (e) { + console.warn(`[img-dim] setSrcset failed for ${src}: ${e.message}`); return; } - const avifFallback = await setSrcset(avif, src, "avif"); - if (avifFallback) { - avif.setAttribute("type", "image/avif"); - picture.appendChild(avif); - } - const webpFallback = await setSrcset(webp, src, "webp"); - if (webpFallback) { - webp.setAttribute("type", "image/webp"); - picture.appendChild(webp); - } - jpeg.setAttribute("type", `image/${fallbackType}`); - picture.appendChild(jpeg); - img.parentElement.replaceChild(picture, img); - picture.appendChild(img); - img.setAttribute("src", fallback); } else if (!img.getAttribute("srcset")) { - const fallback = await setSrcset(img, src, fallbackType); - if (fallback) { - img.setAttribute("src", fallback); + try { + const fallback = await setSrcset(img, src, fallbackType); + if (fallback) { + img.setAttribute("src", fallback); + } + } catch (e) { + console.warn(`[img-dim] setSrcset (fallback) failed for ${src}: ${e.message}`); } } }; From 1dd6500cab1b0ded59b38ebc09ec01d43e7cbd45 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 11:54:18 +0000 Subject: [PATCH 4/4] fix(ci): handle race condition in image processing Updated `_11ty/img-dim.js`, `_11ty/blurry-placeholder.js`, and `_11ty/srcset.js` to handle race conditions where the output file in `_site` might not exist yet when the transform runs. The fix involves checking the source directory (`src/assets/`) if the file is missing in `_site/` for paths starting with `/assets/`. This prevents `sharp` and `image-size` failures during the build process, ensuring robust image processing even if passthrough copy hasn't completed. Co-authored-by: si <18108+si@users.noreply.github.com> --- _11ty/blurry-placeholder.js | 5 ++++- _11ty/img-dim.js | 7 ++++++- _11ty/srcset.js | 8 +++++++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/_11ty/blurry-placeholder.js b/_11ty/blurry-placeholder.js index f6aa321..57b183e 100644 --- a/_11ty/blurry-placeholder.js +++ b/_11ty/blurry-placeholder.js @@ -92,7 +92,10 @@ function getBitmapDimensions_(imgWidth, imgHeight) { } module.exports = async function (src) { - const filename = "_site/" + src; + let filename = "_site/" + src; + if (!await exists(filename) && src.startsWith("/assets/")) { + filename = "src" + src; + } const cachedName = filename + ".blurred"; if (await exists(cachedName)) { return readFile(cachedName, { diff --git a/_11ty/img-dim.js b/_11ty/img-dim.js index b8de38b..65423c8 100644 --- a/_11ty/img-dim.js +++ b/_11ty/img-dim.js @@ -21,6 +21,7 @@ const { JSDOM } = require("jsdom"); const { promisify } = require("util"); +const fs = require("fs"); const sizeOf = promisify(require("image-size")); const blurryPlaceholder = require("./blurry-placeholder"); const srcset = require("./srcset"); @@ -54,7 +55,11 @@ const processImage = async (img, outputPath) => { } let dimensions; try { - dimensions = await sizeOf("_site/" + src); + let inputPath = "_site/" + src; + if (!fs.existsSync(inputPath) && src.startsWith("/assets/")) { + inputPath = "src" + src; + } + dimensions = await sizeOf(inputPath); } catch (e) { console.warn(e.message, src); return; diff --git a/_11ty/srcset.js b/_11ty/srcset.js index 639e87b..2e34fd7 100644 --- a/_11ty/srcset.js +++ b/_11ty/srcset.js @@ -79,7 +79,13 @@ async function resize(filename, width, format) { if (await exists("_site" + out)) { return out; } - await sharp("_site" + filename) + + let inputPath = "_site" + filename; + if (!await exists(inputPath) && filename.startsWith("/assets/")) { + inputPath = "src" + filename; + } + + await sharp(inputPath) .rotate() // Manifest rotation from metadata .resize(width) [format]({