also looking at this
LSU's own coaching surface, and what auditing the site turned up
#12Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
Binary file. Nothing to show line by line.
| @@ -1,82 +0,0 @@ | ||
| 1 | /** | |
| 2 | * Race photography, published by the organiser on lastsoulultra.com and shot | |
| 3 | * by rappid at the race. | |
| 4 | * | |
| 5 | * Only frames that have actually been looked at appear here, and each alt | |
| 6 | * describes what is in that specific frame. Alt text written from a filename | |
| 7 | * is worse than none: it tells a screen-reader user something confidently | |
| 8 | * wrong, and nobody sighted ever notices. | |
| 9 | * | |
| 10 | * Served from this origin. They used to be hotlinked from the organiser's | |
| 11 | * Squarespace CDN, which sent every visitor's IP to a third party the privacy | |
| 12 | * policy did not name, and made every photograph on the site depend on a host | |
| 13 | * we do not control and cannot cache. | |
| 14 | * | |
| 15 | * The four originals are 1080x1350, so the ladder stops at 1080 - Squarespace | |
| 16 | * happily answered a request for 1600w with the same 1080px file, which is how | |
| 17 | * the site came to believe it had widths it never had. Regenerate with | |
| 18 | * `bun run scripts/photos.ts` (macOS only, uses sips). | |
| 19 | */ | |
| 20 | ||
| 21 | export interface Shot { | |
| 22 | /** Slug on disk, not a URL: `<DIR>/<src>-<width>.jpg`. */ | |
| 23 | src: string | |
| 24 | alt: string | |
| 25 | /** Roughly how the frame is composed, so a slot can pick one that fits. */ | |
| 26 | orientation: 'portrait' | 'landscape' | |
| 27 | } | |
| 28 | ||
| 29 | /** Widths on disk. Nothing above 1080 exists, because nothing above 1080 was | |
| 30 | * ever shot. */ | |
| 31 | export const WIDTHS = [540, 810, 1080] as const | |
| 32 | ||
| 33 | const DIR = '/assets/images/race' | |
| 34 | ||
| 35 | export const shots = { | |
| 36 | /** | |
| 37 | * The lap board, and the best single image the race has: it states the | |
| 38 | * format in the brand's own red without a word of explanation. | |
| 39 | */ | |
| 40 | board: { | |
| 41 | src: 'board', | |
| 42 | alt: 'The race board lit red at dusk, reading 90 souls remaining, 7 laps, 46.9 total km', | |
| 43 | orientation: 'portrait', | |
| 44 | }, | |
| 45 | /** A runner caught by the flash against fog, out on the loop after dark. */ | |
| 46 | night: { | |
| 47 | src: 'night', | |
| 48 | alt: 'A runner in a pale jacket and rappid. cap lit by a flash against fog and darkness, carrying a soft flask', | |
| 49 | orientation: 'portrait', | |
| 50 | }, | |
| 51 | /** The gap between hours, which is the whole tactical problem of the format. */ | |
| 52 | refuel: { | |
| 53 | src: 'refuel', | |
| 54 | alt: 'A runner in a rappid. cap eating instant noodles from a cup, resting between laps', | |
| 55 | orientation: 'portrait', | |
| 56 | }, | |
| 57 | /** Daylight on the loop, wet road, somewhere in the middle of the race. */ | |
| 58 | loop: { | |
| 59 | src: 'loop', | |
| 60 | alt: 'A runner in a black jacket and rappid. cap running back down a wet country road at dawn', | |
| 61 | orientation: 'portrait', | |
| 62 | }, | |
| 63 | } satisfies Record<string, Shot> | |
| 64 | ||
| 65 | /** | |
| 66 | * The frame at the width a slot actually paints, snapped UP to a width that | |
| 67 | * exists on disk. Asking for something we do not have used to yield a working | |
| 68 | * URL from the CDN; now it would 404, so the snap is what keeps a template | |
| 69 | * free to name the width it wants. | |
| 70 | */ | |
| 71 | export function at(shot: Shot, width: number): string { | |
| 72 | const w = WIDTHS.find(candidate => candidate >= width) ?? WIDTHS[WIDTHS.length - 1] | |
| 73 | return `${DIR}/${shot.src}-${w}.jpg` | |
| 74 | } | |
| 75 | ||
| 76 | /** 1x and 2x, so a retina screen gets the sharp file and a phone does not. */ | |
| 77 | export function srcset(shot: Shot, width: number): string { | |
| 78 | return `${at(shot, width)} 1x, ${at(shot, width * 2)} 2x` | |
| 79 | } | |
| 80 | ||
| 81 | /** The strip on the home page, ordered the way a race night runs. */ | |
| 82 | export const recap: Shot[] = [shots.board, shots.loop, shots.refuel, shots.night] | |