# Photos — Spec

This file defines the requirements for the Photos section of andyhiggs.uk.
All implementation work should conform to this spec.
Scope: Everything in this spec applies only to /photos and its assets. The rest of andyhiggs.uk must remain untouched.


# Overview

A photo stories section at andyhiggs.uk/photos that migrates six existing stories from the legacy photos.andyhiggs.uk site. The section presents photography in a clean, white gallery aesthetic — distinct in feel from the main andyhiggs.uk site — while remaining visually connected via a shared (but adapted) header.


# User Experience

Landing page (/photos/):

Story page (/photos/<slug>/):

Navigation:


# Features

# Landing Page

# Story Pages

# Header (Photos-specific)


# Content Model

Data lives in _data/photoStories.js as an exported array. Each story object:

{
slug: String, // URL segment, e.g. "boulder"
title: String, // Display title, e.g. "Boulder"
description: String, // Story-level intro text (shown in hero overlay and on cards)
date: String, // ISO date, e.g. "2019-12-29"
cover: String, // Path to cover image, e.g. "/img/photos/boulder/cover.webp"
sections: [
{
title: String | null, // Section heading, e.g. "Tibet" (null for untitled)
description: String | null, // Paragraph of text for this section (null if none)
textOnly: Boolean, // true for text-only sections (no photos); omitted (falsy) on photo sections
rows: [
// Each row is an array of photos shown side-by-side
[{ src: String, w: Number, h: Number }, ...]
]
}
]
}

A section with textOnly: true (empty rows array) renders as a text-only block (e.g. "About this collection" in A Decade of Travels).

# The Six Stories (in order)

Migrated from photos.andyhiggs.uk. Sections, titles, descriptions, and photo sets match the originals exactly:

  1. Boulder (boulder) — 18 photos, no named sections
  2. In the middle of the Pacific (on-hawaii) — 50 photos, 6 sections: O'ahu · surf, moco loco, sharks · queens bath · Nā Pali Coast, Air & Sea · Hanelei bay · beachside in waikiki
  3. A Decade of Travels (a-decade-of-travels) — 54 photos, 11 photo sections + 1 text-only section ("About this collection"), each with a paragraph of narrative text
  4. A week with Buffer (a-week-with-buffer) — 32 photos, 8 sections
  5. Wild New Zealand (new-zealand) — 42 photos, 6 sections
  6. North of Limpopo (north-of-limpopo) — 31 photos, 7 sections

Images are stored as .webp files under /img/photos/<slug>/, numbered 001.webp, 002.webp, etc., plus a cover.webp.


# Design & Style

# Aesthetic

# Colour Palette (Photos section only)

Role Value
Background #fff (white)
Text #111 or #222 (near-black)
Secondary text / meta #666 or #888 (mid-grey)
Borders / lines #ddd or #e0e0e0 (light grey)
Header top border #111 (thin black line)
Hover accents #444 (dark grey)

# Cards (landing page)

# Story hero

# Photo layout (story pages)

# Row group size rules

Group size Treatment CSS class Notes
1 photo Centred at natural dimensions; never stretched beyond its pixel width; whitespace fills the sides; shrinks smoothly when viewport is narrower than the image photo-row--single flex: 0 1 auto, width/height: auto, object-fit: unset
2–4 photos Justified row at natural aspect ratio — all photos the same height, widths proportional to aspect ratio (default) Portrait photos appear tall; landscape photos appear wide; no cropping
5+ photos Same justified row treatment — photos are smaller thumbnails but still uncropped (default) Exposure sometimes grouped many photos into one row as a contact-sheet effect

Determining row groupings: Extract from the original .webarchive using the Python plist parser to read computed width/height styles on .photo relative clearfix elements. Photos with the same rendered height belong to the same visual row.


# Technical Requirements


# Photo File Numbering — Exposure Export Pattern

When photos are exported from Exposure.co stories, the files are numbered in a round-robin pattern across all sections, not section-by-section. Understanding this is essential for correctly assigning files to sections when building the data.

# A Decade of Travels — observed pattern (54 files, 11 sections)

File dimensions (actual):

Numbering structure:

Files Description
001, 003 Tibet grid photos 1–2
005, 007 Delhi grid photos 1–2
009 Chitwan grid photo 1
011, 013 Hong Kong grid photos 1–2
015 Daschi grid photo 1
017, 019 Cameron Highlands grid photos 1–2
021, 022 Space Houses grid photos 1–2
023 Agra grid photo 1
024, 025 Phewa Tal grid photos 1–2
026 Tokyo grid photo 1
027, 028 Sydney grid photos 1–2
029–037 Grid photo 3 for each section in order (Tibet→Sydney)
038–045 Grid photo 4 for each section in order (Tibet→Sydney)
046–053 Remaining grid photos filling sections short of 4 (in section order)
054 Final grid photo (Tokyo)
002, 004, 006, 008, 010, 012, 014, 016, 018, 020 Wide/panoramic singles, one per section (no Sydney wide)

Wide single → section mapping (derived from visual content identification):

File Section
002 Chitwan
004 Daschi
006 Agra
008 (2000×772) Tokyo
010 Tibet
012 Hong Kong
014 Delhi
016 Space Houses
018 Phewa Tal
020 Cameron Highlands

Key insight: The even-numbered files 002–020 are NOT placed in the section that corresponds to their numerical position. They must be identified by visual content and assigned accordingly. The wide singles are interleaved with the grid photos in the export numbering, which is what caused the confusion in initial implementations.

For other Exposure stories, check actual file dimensions first (using the Python WebP header parser or similar) to identify which files are wide singles vs standard grid photos. Then assign wides to sections by visual content identification. The round-robin grid photo pattern should hold across stories.


# Out of Scope