From 24de2f988127150d626f6716254e57904e973c0f Mon Sep 17 00:00:00 2001 From: Ronan Laker Date: Fri, 5 Jun 2026 18:18:04 +0100 Subject: [PATCH 1/3] add Unsplash gallery to photos page Fetch photos at build time via GitHub Actions into _data/unsplash.json. The photos page renders them statically with a "Load more" button to reveal beyond the first 12. No API key in client code. Co-Authored-By: Claude Opus 4.6 --- .github/workflows/fetch-unsplash.yml | 53 ++++++ _data/unsplash.json | 242 +++++++++++++++++++++++++++ _pages/photos.md | 27 ++- 3 files changed, 319 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/fetch-unsplash.yml create mode 100644 _data/unsplash.json diff --git a/.github/workflows/fetch-unsplash.yml b/.github/workflows/fetch-unsplash.yml new file mode 100644 index 00000000..04e0cf16 --- /dev/null +++ b/.github/workflows/fetch-unsplash.yml @@ -0,0 +1,53 @@ +name: Fetch Unsplash photos + +on: + schedule: + - cron: "0 6 * * 1" + workflow_dispatch: + +jobs: + fetch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fetch photos from Unsplash + env: + UNSPLASH_ACCESS_KEY: ${{ secrets.UNSPLASH_ACCESS_KEY }} + run: | + photos="[]" + page=1 + while true; do + batch=$(curl -sf "https://api.unsplash.com/users/rlaker/photos?page=${page}&per_page=30&order_by=latest" \ + -H "Authorization: Client-ID ${UNSPLASH_ACCESS_KEY}") + count=$(echo "$batch" | python3 -c "import json,sys; print(len(json.load(sys.stdin)))") + if [ "$count" -eq 0 ]; then + break + fi + photos=$(python3 -c " + import json, sys + existing = json.loads('$photos') if '$photos' != '[]' else [] + batch = json.loads(sys.stdin.read()) + combined = existing + [{ + 'url': p['links']['html'], + 'src': p['urls']['small'], + 'alt': p.get('alt_description') or 'Unsplash photo', + 'description': p.get('description') or p.get('alt_description') or '' + } for p in batch] + print(json.dumps(combined)) + " <<< "$batch") + if [ "$count" -lt 30 ]; then + break + fi + page=$((page + 1)) + done + echo "$photos" | python3 -m json.tool > _data/unsplash.json + + - name: Commit if changed + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add _data/unsplash.json + git diff --cached --quiet && echo "No changes" && exit 0 + git commit -m "update Unsplash photo data" + git push diff --git a/_data/unsplash.json b/_data/unsplash.json new file mode 100644 index 00000000..46b9d83e --- /dev/null +++ b/_data/unsplash.json @@ -0,0 +1,242 @@ +[ + { + "url": "https://unsplash.com/photos/cinderella-carousel-illuminated-at-dusk-with-silhouetted-trees-Lv2ufKIqULE", + "src": "https://images.unsplash.com/photo-1776629116483-6172d7475718?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxfHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Cinderella carousel illuminated at dusk with silhouetted trees.", + "description": "Cinderella carousel illuminated at dusk with silhouetted trees." + }, + { + "url": "https://unsplash.com/photos/toy-story-land-sign-with-rex-dinosaur-statue-i62oyI67Js0", + "src": "https://images.unsplash.com/photo-1776629116552-b13c1e6e5176?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyfHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Toy story land sign with rex dinosaur statue", + "description": "Toy story land sign with rex dinosaur statue" + }, + { + "url": "https://unsplash.com/photos/roller-coaster-ride-with-colorful-lights-and-people-uHe_p948aOQ", + "src": "https://images.unsplash.com/photo-1776629116474-59cebf253850?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzfHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Roller coaster ride with colorful lights and people", + "description": "Roller coaster ride with colorful lights and people" + }, + { + "url": "https://unsplash.com/photos/EOsr_vzKp1E", + "src": "https://images.unsplash.com/photo-1768571940249-e5560a697065?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw0fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Sea of clouds over the Eryanping trail, Taiwan" + }, + { + "url": "https://unsplash.com/photos/eQQR9-XUTBw", + "src": "https://images.unsplash.com/photo-1768571940169-956bf02e2af7?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw1fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Sea of clouds over the Eryanping trail, Taiwan" + }, + { + "url": "https://unsplash.com/photos/people-walk-down-a-narrow-street-with-shops-and-truck-3KWbfPX1u_4", + "src": "https://images.unsplash.com/photo-1767517532812-b9e9fd77ae3f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw2fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "People walk down a narrow street with shops and truck.", + "description": "Street scene in Hong Kong" + }, + { + "url": "https://unsplash.com/photos/ouo8Npd097I", + "src": "https://images.unsplash.com/photo-1766196732968-09bac886399c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw3fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Cathay Pacific flight London to Hong Kong" + }, + { + "url": "https://unsplash.com/photos/deer-running-in-a-grassy-field-with-trees-Lsu4O9cx8os", + "src": "https://images.unsplash.com/photo-1764338434670-6180e03b2478?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw4fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Deer running in a grassy field with trees.", + "description": "Deer running in a grassy field with trees." + }, + { + "url": "https://unsplash.com/photos/-WvKulvgsnE", + "src": "https://images.unsplash.com/photo-1764338159899-ff4cb6441b84?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw5fHx8fHx8Mnx8MTc4MDY3OTQzMXw&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Two families in Richmond park" + }, + { + "url": "https://unsplash.com/photos/HmdoXQczbhg", + "src": "https://images.unsplash.com/photo-1760029924651-cd776de4bc96?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxMHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "View of 120 Holborn in the City of London" + }, + { + "url": "https://unsplash.com/photos/modern-glass-skyscrapers-against-a-clear-blue-sky-MXVVElrij7k", + "src": "https://images.unsplash.com/photo-1758063974173-9478d27a9b4b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxMXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Modern glass skyscrapers against a clear blue sky.", + "description": "Skyscrapers in the City of London" + }, + { + "url": "https://unsplash.com/photos/infinity-pool-overlooking-rolling-green-hills-and-trees-vjA29nXlbPc", + "src": "https://images.unsplash.com/photo-1756726599457-cde7eb8654a4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxMnx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Infinity pool overlooking rolling green hills and trees", + "description": "View from the Il Paluffo hotel pool in Tuscany" + }, + { + "url": "https://unsplash.com/photos/four-chairs-by-a-pool-overlooking-rolling-hills-0qPy8pYlG1s", + "src": "https://images.unsplash.com/photo-1756726597936-199d97b85e3f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxM3x8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Four chairs by a pool overlooking rolling hills", + "description": "View from the Il Paluffo hotel in Tuscany" + }, + { + "url": "https://unsplash.com/photos/four-chairs-by-a-pool-overlooking-rolling-hills-UIjiW0ry6Fo", + "src": "https://images.unsplash.com/photo-1756726598243-a0baacd260b3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxNHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Four chairs by a pool overlooking rolling hills", + "description": "View from the Il Paluffo hotel in Tuscany at sunset" + }, + { + "url": "https://unsplash.com/photos/sunset-over-rolling-hills-and-vineyards-cPU4aHv6JkE", + "src": "https://images.unsplash.com/photo-1756726598422-71308b14a017?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxNXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Sunset over rolling hills and vineyards", + "description": "Sunset over the vines of Tuscany" + }, + { + "url": "https://unsplash.com/photos/leaning-tower-of-pisa-viewed-through-an-open-window-pVf0gRgrePE", + "src": "https://images.unsplash.com/photo-1756726478669-7ff63c6e648e?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxNnx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Leaning tower of pisa viewed through an open window.", + "description": "Unique view of the leaning tower of Pisa from a hotel room" + }, + { + "url": "https://unsplash.com/photos/a-winding-road-through-snowy-mountains-P2HqM8mhsxk", + "src": "https://images.unsplash.com/photo-1750609478707-d0996157da50?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxN3x8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A winding road through snowy mountains.", + "description": "A winding road through snowy mountains." + }, + { + "url": "https://unsplash.com/photos/a-plane-leaves-a-vapor-trail-in-the-sky-mMQHYztntrg", + "src": "https://images.unsplash.com/photo-1750609038027-2331b7e67c2a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxOHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A plane leaves a vapor trail in the sky.", + "description": "A plane leaves a vapor trail in the sky." + }, + { + "url": "https://unsplash.com/photos/man-enjoys-the-view-of-mountains-and-serene-lake-fwiHfyCqhGQ", + "src": "https://images.unsplash.com/photo-1749824823135-c2d69817c44c?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwxOXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Man enjoys the view of mountains and serene lake.", + "description": "Watching the sun rise over Loch Lomond" + }, + { + "url": "https://unsplash.com/photos/fO6RFaZLoTI", + "src": "https://images.unsplash.com/photo-1737912806708-8f8be9de6bb3?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyMHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Art installation at Canary Wharf" + }, + { + "url": "https://unsplash.com/photos/Oa86m_unh18", + "src": "https://images.unsplash.com/photo-1737912807709-10dc85d1962a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyMXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Art installation at Canary Wharf" + }, + { + "url": "https://unsplash.com/photos/WeHNF-eJyPs", + "src": "https://images.unsplash.com/photo-1737912806650-ee1d38f6cf5a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyMnx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Art installation at Canary Wharf" + }, + { + "url": "https://unsplash.com/photos/_UlwAmG83LY", + "src": "https://images.unsplash.com/photo-1736372717701-d6b4d69b66a0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyM3x8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "" + }, + { + "url": "https://unsplash.com/photos/qq2NtgaxRI8", + "src": "https://images.unsplash.com/photo-1736372715014-3648d32632ef?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyNHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "" + }, + { + "url": "https://unsplash.com/photos/c-An05-yWVs", + "src": "https://images.unsplash.com/photo-1736372715224-684158fda06b?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyNXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "" + }, + { + "url": "https://unsplash.com/photos/U2aZEDkDsiE", + "src": "https://images.unsplash.com/photo-1736372716914-ce634a109a2f?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyNnx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "" + }, + { + "url": "https://unsplash.com/photos/a-person-standing-in-front-of-a-glass-pyramid-EOUU862P4kk", + "src": "https://images.unsplash.com/photo-1736183587761-d5e43490fa5a?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyN3x8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A person standing in front of a glass pyramid", + "description": "Got lucky with the composition as we explored Paris in December" + }, + { + "url": "https://unsplash.com/photos/a-large-skeleton-of-a-fish-in-a-museum-H6X7NbvfMgw", + "src": "https://images.unsplash.com/photo-1733483145856-223aea7cfeb6?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyOHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A large skeleton of a fish in a museum", + "description": "Whale skeleton flowing through the Natural History Museum" + }, + { + "url": "https://unsplash.com/photos/a-group-of-people-standing-in-a-large-building-FAyQ04xd1Us", + "src": "https://images.unsplash.com/photo-1733483021074-858f11b83de2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwyOXx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A group of people standing in a large building", + "description": "Top view of whale skeleton in the Natural History Museum, London" + }, + { + "url": "https://unsplash.com/photos/a-large-building-with-a-huge-ceiling-and-a-clock-OcJpBdfeVOk", + "src": "https://images.unsplash.com/photo-1733482935085-606bf46b1dd2?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzMHx8fHx8fDJ8fDE3ODA2Nzk0MzF8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A large building with a huge ceiling and a clock", + "description": "Whale skeleton inside the Natural History Museum, London" + }, + { + "url": "https://unsplash.com/photos/a-person-standing-on-a-sidewalk-with-brown-shoes-TSy5ebfA0E8", + "src": "https://images.unsplash.com/photo-1732549952567-3a6367b8a396?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzMXx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "A person standing on a sidewalk with brown shoes", + "description": "My Doc Martin boots among the autumn leaves" + }, + { + "url": "https://unsplash.com/photos/l4t0ywc2J7g", + "src": "https://images.unsplash.com/photo-1732207685861-8de95f4ef435?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzMnx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Ammonite at the Natural History Museum in black and white" + }, + { + "url": "https://unsplash.com/photos/PHi7I23YyoM", + "src": "https://images.unsplash.com/photo-1732196126204-cdc1c92a12bc?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzM3x8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Spiral staircase at Tate Britain" + }, + { + "url": "https://unsplash.com/photos/a-black-and-white-photo-of-a-person-standing-in-a-cave-aOKetYdyDD4", + "src": "https://images.unsplash.com/photo-1711991593117-c0ef542a6e41?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzNHx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "a black and white photo of a person standing in a cave", + "description": "Reflection in Rydal cave, framing the trees" + }, + { + "url": "https://unsplash.com/photos/a-person-in-a-kayak-paddling-on-a-large-body-of-water-kFW9B87CD8k", + "src": "https://images.unsplash.com/photo-1711991587214-26253d3615ea?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzNXx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "a person in a kayak paddling on a large body of water", + "description": "A kayaker on Ullswater, Lake District" + }, + { + "url": "https://unsplash.com/photos/a-view-from-inside-a-car-of-a-mountainous-area-YCzgLfFnU24", + "src": "https://images.unsplash.com/photo-1711991573978-1b8567871583?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzNnx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "a view from inside a car of a mountainous area", + "description": "View of the Langdale pikes from a car" + }, + { + "url": "https://unsplash.com/photos/a-couple-of-sheep-standing-on-top-of-a-grass-covered-hillside-wJoXA5KoT-g", + "src": "https://images.unsplash.com/photo-1711992092885-dbf9a757a823?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzN3x8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "a couple of sheep standing on top of a grass covered hillside", + "description": "Two sheep in the Lake District" + }, + { + "url": "https://unsplash.com/photos/ClwHyMK2P3c", + "src": "https://images.unsplash.com/photo-1711551813475-03ffdbae0971?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzOHx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "Big brother is watching you" + }, + { + "url": "https://unsplash.com/photos/4uW5qu2-Cj4", + "src": "https://images.unsplash.com/photo-1711550871189-8e6602836fe8?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHwzOXx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "Unsplash photo", + "description": "City of London sky view" + }, + { + "url": "https://unsplash.com/photos/a-hospital-room-with-a-hospital-bed-and-a-chair-FIxkVr4Fzt4", + "src": "https://images.unsplash.com/photo-1678708314771-997ae20da1f0?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzAyNzd8MHwxfGFsbHw0MHx8fHx8fDJ8fDE3ODA2Nzk0MzJ8&ixlib=rb-4.1.0&q=80&w=400", + "alt": "a hospital room with a hospital bed and a chair", + "description": "Operating room in Alcatraz prison." + } +] \ No newline at end of file diff --git a/_pages/photos.md b/_pages/photos.md index 306d4bab..cc13b49e 100644 --- a/_pages/photos.md +++ b/_pages/photos.md @@ -129,12 +129,33 @@ gallerynature: title: "Swooping Kestrel" --- +- [Unsplash](#unsplash) +- [Film Photography](#film-photography) +- [Nature](#nature) +## Unsplash -TODO: I need to update this page. See more recent pictures on my [Unsplash profile](https://unsplash.com/@rlaker) but for now there are two galleries below: +My latest photos from [Unsplash](https://unsplash.com/@rlaker). -- [Film Photography](#film-photography) -- [Nature](#nature) + + +{% if site.data.unsplash.size > 12 %} +
+ +
+{% endif %} ## Film Photography From a2c097afd64f10d44ab6194d7e10741b790d0143 Mon Sep 17 00:00:00 2001 From: Ronan Laker Date: Fri, 5 Jun 2026 18:31:02 +0100 Subject: [PATCH 2/3] add unplash on blog post --- _posts/2026-06-30-showing-unsplash-on-blog.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 _posts/2026-06-30-showing-unsplash-on-blog.md diff --git a/_posts/2026-06-30-showing-unsplash-on-blog.md b/_posts/2026-06-30-showing-unsplash-on-blog.md new file mode 100644 index 00000000..b9e71422 --- /dev/null +++ b/_posts/2026-06-30-showing-unsplash-on-blog.md @@ -0,0 +1,100 @@ +--- +title: "Showing my Unsplash gallery on my blog" +layout: single +excerpt: "How I embedded my Unsplash photos on my Jekyll site without exposing an API key" +tags: [code,til] +--- + +I wanted to show my [Unsplash photos](https://unsplash.com/@rlaker) directly on my [photography page](/photos/) instead of just showing a text link. Since Unsplash doesn't have an embed widget, I needed to use their API. + +## The naive approach (and why I didn't use it) + +The simplest option is to call the Unsplash API from JavaScript on the client side: + +```javascript +fetch('https://api.unsplash.com/users/rlaker/photos', { + headers: { 'Authorization': 'Client-ID YOUR_ACCESS_KEY' } +}) +``` + +This works, but it has two problems: + +1. **Your API key is visible in the page source.** Unsplash's demo keys are read-only and rate-limited, so it's not catastrophic, but it's not great either. +2. **Rate limiting.** The demo tier allows 50 requests per hour. Each page view is one request that collects the metadata of all my images on unsplash (the photos themselves are served from Unsplash's CDN and don't count as requests). + +## The build-time approach + +Instead, with the help of Claude, I fetch the photos at build time using a GitHub Actions workflow. The workflow runs weekly (and can be triggered manually), calls the Unsplash API, and saves the results to `_data/unsplash.json`. Jekyll reads this file as `site.data.unsplash` during the build, so the photos are baked into the HTML meaning no API calls when someone views the page. + +### The GitHub Action + +```yaml +name: Fetch Unsplash photos + +on: + schedule: + - cron: "0 6 * * 1" # every Monday at 6am + workflow_dispatch: # manual trigger + +jobs: + fetch: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fetch photos from Unsplash + env: + UNSPLASH_ACCESS_KEY: {% raw %}${{ secrets.UNSPLASH_ACCESS_KEY }}{% endraw %} + run: | + # fetch all photos, paginating through results + # save to _data/unsplash.json + + - name: Commit if changed + run: | + git add _data/unsplash.json + git diff --cached --quiet && exit 0 + git commit -m "update Unsplash photo data" + git push +``` + +The API key lives in a GitHub secret and never touches the repo or the client. + +### The template + +The photos page loops over the data file using Liquid: + +```liquid +{% raw %}{% for photo in site.data.unsplash %} + + {{ photo.alt }} + +{% endfor %}{% endraw %} +``` + +I show the first 12 photos and hide the rest behind a "Load more" button that just removes `display:none` — no JavaScript fetch needed. + +### The data file + +Each entry in `unsplash.json` looks like: + +```json +{ + "url": "https://unsplash.com/photos/...", + "src": "https://images.unsplash.com/...?w=400", + "alt": "photo description", + "description": "longer description" +} +``` + +The `src` field uses Unsplash's `small` size (400px wide), which is plenty for a thumbnail grid and keeps the page fast. + +## Setup steps + +1. Create an app on [unsplash.com/developers](https://unsplash.com/developers) and grab the Access Key +2. Add it as a GitHub secret called `UNSPLASH_ACCESS_KEY` (repo Settings > Secrets and variables > Actions) +3. Run the workflow manually once to generate the initial `_data/unsplash.json` +4. After that, the workflow refreshes it weekly + +## The result + +My [photos page](/photos/) now shows my latest Unsplash uploads at the top, automatically refreshed every week, with no API key exposed and no client-side rate limiting to worry about. From 4e7432838c673ec3c3002b6c015974d45ac9aa2d Mon Sep 17 00:00:00 2001 From: Ronan Laker Date: Fri, 5 Jun 2026 18:35:03 +0100 Subject: [PATCH 3/3] rename date --- ...unsplash-on-blog.md => 2026-06-05-showing-unsplash-on-blog.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename _posts/{2026-06-30-showing-unsplash-on-blog.md => 2026-06-05-showing-unsplash-on-blog.md} (100%) diff --git a/_posts/2026-06-30-showing-unsplash-on-blog.md b/_posts/2026-06-05-showing-unsplash-on-blog.md similarity index 100% rename from _posts/2026-06-30-showing-unsplash-on-blog.md rename to _posts/2026-06-05-showing-unsplash-on-blog.md