Rotate coin-face images from folder, enforce ≥2 headshots, add pre-deploy validation#16
Rotate coin-face images from folder, enforce ≥2 headshots, add pre-deploy validation#16brianruggieri with Copilot wants to merge 5 commits into
Conversation
- Create static/img/coin-faces/ folder for coin face images - Move me-photo.png/webp to coin-faces/ folder - Update nav.html to auto-discover images via Hugo readDir - Pass image list as data-coin-images JSON attribute - Update coin-flip.js to randomly rotate back face images - Each flip to the illustration triggers a new random photo swap Co-authored-by: brianruggieri <2104689+brianruggieri@users.noreply.github.com>
… check Co-authored-by: brianruggieri <2104689+brianruggieri@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Updates the site’s “profile coin” to rotate through a build-time discovered set of face images (from static/img/coin-faces/) instead of relying on a single hardcoded back image, with JS selecting/swapping the back face on flips.
Changes:
- Add Hugo build-time discovery of coin face images and expose them via a
data-coin-imagesattribute on both coin elements. - Update
coin-flip.jsto parse the image list, randomize the initial back face, and rotate to a new back face after flips. - Add/move coin face image assets into
static/img/coin-faces/.
Reviewed changes
Copilot reviewed 2 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| static/js/coin-flip.js | Parse data-coin-images, randomize initial back image, and rotate back-face images across flips. |
| layouts/partials/nav.html | Build-time readDir of static/img/coin-faces and emit discovered image list + initial back-face style. |
| static/img/coin-faces/me-photo.webp | Adds a WebP coin face asset in the new folder. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // Coin face image rotation — images discovered at build time by Hugo | ||
| const coinImages = JSON.parse(coin?.dataset?.coinImages || '[]'); | ||
| let currentBackImage = coinImages.length > 0 ? coinImages[0] : null; | ||
|
|
There was a problem hiding this comment.
This PR adds new behavior (parsing data-coin-images, randomizing the back face on load, and swapping it after flips), but the existing Puppeteer tests don’t appear to assert any of it. Consider adding an e2e/visual test that validates the data-coin-images JSON and that the .coin-back background-image changes after flipping back to the front (and remains stable for the single-image case).
| <div id="mobileCoin" class="mobile-brand__coin" data-coin-images='{{ $coinFaces | jsonify }}'> | ||
| <div class="coin-side coin-front" style="background-image: image-set(url('{{ $frontWebp }}') type('image/webp'), url('{{ $frontImg }}') type('image/png'))"></div> | ||
| <div class="coin-side coin-back" style="background-image: image-set(url('/img/me-photo.webp') type('image/webp'), url('/img/me-photo.png') type('image/png'))"></div> | ||
| <div class="coin-side coin-back" style="background-image: image-set(url('{{ $backWebp }}') type('image/webp'), url('{{ $backImg }}') type('image/png'))"></div> |
There was a problem hiding this comment.
The back-face inline style always uses type('image/png'), but $backImg can be .jpg/.jpeg (since those are included in $coinFaces). This makes the declared MIME type incorrect and can cause browsers to ignore that source in image-set(). Consider deriving the correct type from the extension (or omit the type(...) for the fallback URL).
| <div id="profileCoin" class="mx-auto mb-2" data-coin-images='{{ $coinFaces | jsonify }}'> | ||
| <div class="coin-side coin-front" style="background-image: image-set(url('{{ $frontWebp }}') type('image/webp'), url('{{ $frontImg }}') type('image/png'))"></div> | ||
| <div class="coin-side coin-back" style="background-image: image-set(url('/img/me-photo.webp') type('image/webp'), url('/img/me-photo.png') type('image/png'))"></div> | ||
| <div class="coin-side coin-back" style="background-image: image-set(url('{{ $backWebp }}') type('image/webp'), url('{{ $backImg }}') type('image/png'))"></div> |
There was a problem hiding this comment.
The back-face inline style always uses type('image/png'), but $backImg can be .jpg/.jpeg (since those are included in $coinFaces). This makes the declared MIME type incorrect and can cause browsers to ignore that source in image-set(). Consider deriving the correct type from the extension (or omit the type(...) for the fallback URL).
| {{ $backImg := "" }} | ||
| {{ if gt (len $coinFaces) 0 }} | ||
| {{ $backImg = index $coinFaces 0 }} | ||
| {{ end }} | ||
| {{ $backWebp := $backImg | replaceRE `\.(png|jpg|jpeg)$` ".webp" }} |
There was a problem hiding this comment.
When static/img/coin-faces/ is empty, $backImg stays "" and $backWebp is also empty. The generated inline image-set(url('')...) can resolve to the current document URL and trigger an unintended request / broken background. Consider conditionally emitting the background-image only when a back image exists (or providing a safe placeholder).
| */ | ||
| function setFaceImage(face, imgPath) { | ||
| if (!face || !imgPath) return; | ||
| const webpPath = imgPath.replace(/\.(png|jpg|jpeg)$/, '.webp'); |
There was a problem hiding this comment.
setFaceImage() derives webpPath via a case-sensitive \.(png|jpg|jpeg)$ replace. Because the Hugo template includes files by checking a lowercased name, filenames like ME.PNG would be accepted but the .webp path would not be generated correctly here. Consider making the replace regex case-insensitive (and/or normalizing the emitted paths) so mixed-case extensions work reliably.
| const webpPath = imgPath.replace(/\.(png|jpg|jpeg)$/, '.webp'); | |
| const webpPath = imgPath.replace(/\.(png|jpg|jpeg)$/i, '.webp'); |
- Add second headshot (me-alt.png/webp) to coin-faces folder - Add scripts/validate-coin-faces.sh to verify ≥2 images exist - Add validation step to deploy workflow before Hugo build - Add console.warn in coin-flip.js when fewer than 2 images Co-authored-by: brianruggieri <2104689+brianruggieri@users.noreply.github.com>
…itespace in validation script Co-authored-by: brianruggieri <2104689+brianruggieri@users.noreply.github.com>
The coin flip was hardcoded to toggle between exactly 2 static images. This converts it to dynamically discover images from a
static/img/coin-faces/folder, rotate randomly on each flip (never repeating back-to-back), and enforce a minimum of 2 headshots via a CI gate.Image discovery at build time
readDirscansstatic/img/coin-faces/for.png/.jpg/.jpegfiles, passes the list asdata-coin-imagesJSON attribute on both desktop and mobile coin elementsme-photo.{png,webp}intocoin-faces/, addedme-alt.{png,webp}as a second headshotNo-repeat rotation in JS
pickRandomImage(excludePath)guarantees a different image on every back-face reveal via do-while exclusionconsole.warnif< 2images detected (degraded experience)Pre-deploy validation
scripts/validate-coin-faces.sh— counts source images (png/jpg/jpeg, not webp companions), exits non-zero if< 2deploy.ymlbeforehugo --minify, blocking deploy on violationTemplate MIME type fix
image-set()now uses conditional blocks for jpeg vs png MIME type instead of a template variable (avoids Hugo minifier CSS-escapingimage/png→image\2fpng)💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.