chore: finish birds c76ff91f
Steve · 2026-04-25 07:46 3 file(s) · +245 −69
packages/client/scripts/parse-birds.ts +39 −4
11 11
const idx = (name: string) => headers.indexOf(name);
12 12
13 13
const seen = new Set<string>();
14 -
const birds: { commonName: string; scientificName: string; date: string; location: string; state: string }[] = [];
14 +
const birds: {
15 +
	commonName: string;
16 +
	scientificName: string;
17 +
	date: string;
18 +
	location: string;
19 +
	state: string;
20 +
	photo: string | null;
21 +
	summary: string | null;
22 +
	wikiUrl: string | null;
23 +
}[] = [];
15 24
16 25
for (const line of lines.slice(1)) {
17 -
	// Handle potential commas in quoted fields
18 26
	const cols: string[] = [];
19 27
	let inQuote = false;
20 28
	let cur = "";
33 41
34 42
	if (!commonName || seen.has(commonName)) continue;
35 43
	seen.add(commonName);
36 -
	birds.push({ commonName, scientificName, date, location, state });
44 +
	birds.push({ commonName, scientificName, date, location, state, photo: null, summary: null, wikiUrl: null });
37 45
}
38 46
39 47
birds.sort((a, b) => a.commonName.localeCompare(b.commonName));
40 48
49 +
console.log(`Fetching iNaturalist data for ${birds.length} species...`);
50 +
51 +
for (const bird of birds) {
52 +
	const query = encodeURIComponent(bird.commonName);
53 +
	const url = `https://api.inaturalist.org/v1/taxa?q=${query}&rank=species&per_page=1`;
54 +
	try {
55 +
		const res = await fetch(url);
56 +
		if (res.ok) {
57 +
			const data = await res.json() as any;
58 +
			const taxon = data.results?.[0];
59 +
			if (taxon) {
60 +
				bird.photo = taxon.default_photo?.medium_url ?? null;
61 +
				const raw: string | null = taxon.wikipedia_summary ?? null;
62 +
				bird.summary = raw ? (raw.length > 220 ? raw.slice(0, 220).replace(/\s\S*$/, "") + "…" : raw) : null;
63 +
				bird.wikiUrl = taxon.wikipedia_url ?? null;
64 +
			}
65 +
		}
66 +
	} catch (e) {
67 +
		console.warn(`  Failed to fetch ${bird.commonName}: ${e}`);
68 +
	}
69 +
	console.log(`  ${bird.photo ? "✓" : "✗"} ${bird.commonName}`);
70 +
	await new Promise(r => setTimeout(r, 100));
71 +
}
72 +
41 73
const ts = `export type BirdEntry = {
42 74
	commonName: string;
43 75
	scientificName: string;
44 76
	date: string;
45 77
	location: string;
46 78
	state: string;
79 +
	photo: string | null;
80 +
	summary: string | null;
81 +
	wikiUrl: string | null;
47 82
};
48 83
49 84
export const birds: BirdEntry[] = ${JSON.stringify(birds, null, "\t")};
50 85
`;
51 86
52 87
writeFileSync(outPath, ts);
53 -
console.log(`Wrote ${birds.length} species to ${outPath}`);
88 +
console.log(`\nWrote ${birds.length} species to ${outPath}`);
packages/client/src/data/birds.ts +175 −43
4 4
	date: string;
5 5
	location: string;
6 6
	state: string;
7 +
	photo: string | null;
8 +
	summary: string | null;
9 +
	wikiUrl: string | null;
7 10
};
8 11
9 12
export const birds: BirdEntry[] = [
12 15
		"scientificName": "Corvus brachyrhynchos",
13 16
		"date": "2025-06-01",
14 17
		"location": "Home",
15 -
		"state": "US-TN"
18 +
		"state": "US-TN",
19 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/4661023/medium.jpg",
20 +
		"summary": null,
21 +
		"wikiUrl": "http://en.wikipedia.org/wiki/American_crow"
16 22
	},
17 23
	{
18 24
		"commonName": "American Goldfinch",
19 25
		"scientificName": "Spinus tristis",
20 26
		"date": "2025-10-07",
21 27
		"location": "Home",
22 -
		"state": "US-TN"
28 +
		"state": "US-TN",
29 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/384965723/medium.jpg",
30 +
		"summary": null,
31 +
		"wikiUrl": "http://en.wikipedia.org/wiki/American_goldfinch"
23 32
	},
24 33
	{
25 34
		"commonName": "American Robin",
26 35
		"scientificName": "Turdus migratorius",
27 36
		"date": "2025-06-01",
28 37
		"location": "Home",
29 -
		"state": "US-TN"
38 +
		"state": "US-TN",
39 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/34859026/medium.jpg",
40 +
		"summary": null,
41 +
		"wikiUrl": "http://en.wikipedia.org/wiki/American_robin"
30 42
	},
31 43
	{
32 44
		"commonName": "Bald Eagle",
33 45
		"scientificName": "Haliaeetus leucocephalus",
34 46
		"date": "2026-03-08",
35 47
		"location": "Home",
36 -
		"state": "US-TN"
48 +
		"state": "US-TN",
49 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/349074095/medium.jpeg",
50 +
		"summary": null,
51 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Bald_eagle"
37 52
	},
38 53
	{
39 54
		"commonName": "Barred Owl",
40 55
		"scientificName": "Strix varia",
41 56
		"date": "2025-11-05",
42 57
		"location": "302",
43 -
		"state": "US-TN"
58 +
		"state": "US-TN",
59 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/359094470/medium.jpg",
60 +
		"summary": null,
61 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Barred_owl"
44 62
	},
45 63
	{
46 64
		"commonName": "Belted Kingfisher",
47 65
		"scientificName": "Megaceryle alcyon",
48 66
		"date": "2025-10-28",
49 67
		"location": "Reflection",
50 -
		"state": "US-TN"
68 +
		"state": "US-TN",
69 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/244248986/medium.jpg",
70 +
		"summary": null,
71 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Belted_kingfisher"
51 72
	},
52 73
	{
53 74
		"commonName": "Black Vulture",
54 75
		"scientificName": "Coragyps atratus",
55 76
		"date": "2025-11-24",
56 77
		"location": "Grandview",
57 -
		"state": "US-VA"
78 +
		"state": "US-VA",
79 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/14689662/medium.jpg",
80 +
		"summary": null,
81 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Black_vulture"
58 82
	},
59 83
	{
60 84
		"commonName": "Blue Jay",
61 85
		"scientificName": "Cyanocitta cristata",
62 86
		"date": "2019-02-17",
63 87
		"location": "Rittman",
64 -
		"state": "US-OH"
88 +
		"state": "US-OH",
89 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/530721418/medium.jpg",
90 +
		"summary": null,
91 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Blue_jay"
65 92
	},
66 93
	{
67 94
		"commonName": "Blue-gray Gnatcatcher",
68 95
		"scientificName": "Polioptila caerulea",
69 96
		"date": "2026-04-04",
70 97
		"location": "Reflection",
71 -
		"state": "US-TN"
98 +
		"state": "US-TN",
99 +
		"photo": "https://static.inaturalist.org/photos/269328954/medium.jpeg",
100 +
		"summary": null,
101 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Blue-gray_gnatcatcher"
72 102
	},
73 103
	{
74 104
		"commonName": "Brown Thrasher",
75 105
		"scientificName": "Toxostoma rufum",
76 106
		"date": "2025-06-01",
77 107
		"location": "Home",
78 -
		"state": "US-TN"
108 +
		"state": "US-TN",
109 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/186561540/medium.jpeg",
110 +
		"summary": null,
111 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Brown_thrasher"
79 112
	},
80 113
	{
81 114
		"commonName": "Brown-headed Cowbird",
82 115
		"scientificName": "Molothrus ater",
83 116
		"date": "2025-06-15",
84 117
		"location": "Home",
85 -
		"state": "US-TN"
118 +
		"state": "US-TN",
119 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/471179764/medium.jpg",
120 +
		"summary": null,
121 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Brown-headed_cowbird"
86 122
	},
87 123
	{
88 124
		"commonName": "Brown-headed Nuthatch",
89 125
		"scientificName": "Sitta pusilla",
90 126
		"date": "2025-12-01",
91 127
		"location": "Home",
92 -
		"state": "US-TN"
128 +
		"state": "US-TN",
129 +
		"photo": "https://static.inaturalist.org/photos/254151643/medium.jpg",
130 +
		"summary": null,
131 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Brown-headed_nuthatch"
93 132
	},
94 133
	{
95 134
		"commonName": "Carolina Chickadee",
96 135
		"scientificName": "Poecile carolinensis",
97 136
		"date": "2019-02-17",
98 137
		"location": "Rittman",
99 -
		"state": "US-OH"
138 +
		"state": "US-OH",
139 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/3053969/medium.jpg",
140 +
		"summary": null,
141 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Carolina_chickadee"
100 142
	},
101 143
	{
102 144
		"commonName": "Carolina Wren",
103 145
		"scientificName": "Thryothorus ludovicianus",
104 146
		"date": "2025-09-19",
105 147
		"location": "Home",
106 -
		"state": "US-TN"
148 +
		"state": "US-TN",
149 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/461889083/medium.jpg",
150 +
		"summary": null,
151 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Carolina_wren"
107 152
	},
108 153
	{
109 154
		"commonName": "Cedar Waxwing",
110 155
		"scientificName": "Bombycilla cedrorum",
111 156
		"date": "2026-01-02",
112 157
		"location": "Reflection",
113 -
		"state": "US-TN"
158 +
		"state": "US-TN",
159 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/450146514/medium.jpeg",
160 +
		"summary": null,
161 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Cedar_waxwing"
114 162
	},
115 163
	{
116 164
		"commonName": "Chimney Swift",
117 165
		"scientificName": "Chaetura pelagica",
118 166
		"date": "2025-09-22",
119 167
		"location": "Yellow house",
120 -
		"state": "US-TN"
168 +
		"state": "US-TN",
169 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/383206018/medium.jpeg",
170 +
		"summary": null,
171 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Chimney_swift"
121 172
	},
122 173
	{
123 174
		"commonName": "Chipping Sparrow",
124 175
		"scientificName": "Spizella passerina",
125 176
		"date": "2026-03-14",
126 177
		"location": "Home",
127 -
		"state": "US-TN"
178 +
		"state": "US-TN",
179 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/273251789/medium.jpg",
180 +
		"summary": null,
181 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Chipping_sparrow"
128 182
	},
129 183
	{
130 184
		"commonName": "Common Loon",
131 185
		"scientificName": "Gavia immer",
132 186
		"date": "2025-11-24",
133 187
		"location": "Grandview",
134 -
		"state": "US-VA"
188 +
		"state": "US-VA",
189 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/2960972/medium.jpg",
190 +
		"summary": null,
191 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Common_loon"
135 192
	},
136 193
	{
137 194
		"commonName": "Cooper's Hawk",
138 195
		"scientificName": "Astur cooperii",
139 196
		"date": "2025-08-27",
140 197
		"location": "Home",
141 -
		"state": "US-TN"
198 +
		"state": "US-TN",
199 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/170370008/medium.jpg",
200 +
		"summary": null,
201 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Cooper's_hawk"
142 202
	},
143 203
	{
144 204
		"commonName": "Dark-eyed Junco",
145 205
		"scientificName": "Junco hyemalis",
146 206
		"date": "2019-02-17",
147 207
		"location": "Rittman",
148 -
		"state": "US-OH"
208 +
		"state": "US-OH",
209 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/111714701/medium.jpeg",
210 +
		"summary": null,
211 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Dark-eyed_junco"
149 212
	},
150 213
	{
151 214
		"commonName": "Double-crested Cormorant",
152 215
		"scientificName": "Nannopterum auritum",
153 216
		"date": "2025-11-24",
154 217
		"location": "Grandview",
155 -
		"state": "US-VA"
218 +
		"state": "US-VA",
219 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/182713821/medium.jpg",
220 +
		"summary": null,
221 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Double-crested_cormorant"
156 222
	},
157 223
	{
158 224
		"commonName": "Downy Woodpecker",
159 225
		"scientificName": "Dryobates pubescens",
160 226
		"date": "2019-02-17",
161 227
		"location": "Rittman",
162 -
		"state": "US-OH"
228 +
		"state": "US-OH",
229 +
		"photo": "https://static.inaturalist.org/photos/25544008/medium.jpeg",
230 +
		"summary": null,
231 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Downy_woodpecker"
163 232
	},
164 233
	{
165 234
		"commonName": "Eastern Bluebird",
166 235
		"scientificName": "Sialia sialis",
167 236
		"date": "2025-04-14",
168 237
		"location": "Home",
169 -
		"state": "US-TN"
238 +
		"state": "US-TN",
239 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/356151567/medium.jpeg",
240 +
		"summary": null,
241 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Eastern_bluebird"
170 242
	},
171 243
	{
172 244
		"commonName": "Eastern Towhee",
173 245
		"scientificName": "Pipilo erythrophthalmus",
174 246
		"date": "2025-06-01",
175 247
		"location": "Home",
176 -
		"state": "US-TN"
248 +
		"state": "US-TN",
249 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/45584021/medium.jpg",
250 +
		"summary": null,
251 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Eastern_towhee"
177 252
	},
178 253
	{
179 254
		"commonName": "Gray Catbird",
180 255
		"scientificName": "Dumetella carolinensis",
181 256
		"date": "2025-10-23",
182 257
		"location": "Home",
183 -
		"state": "US-TN"
258 +
		"state": "US-TN",
259 +
		"photo": "https://static.inaturalist.org/photos/16329727/medium.jpg",
260 +
		"summary": null,
261 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Gray_catbird"
184 262
	},
185 263
	{
186 264
		"commonName": "Hairy Woodpecker",
187 265
		"scientificName": "Leuconotopicus villosus",
188 266
		"date": "2026-01-02",
189 267
		"location": "Reflection",
190 -
		"state": "US-TN"
268 +
		"state": "US-TN",
269 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/1275/medium.jpg",
270 +
		"summary": null,
271 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Hairy_woodpecker"
191 272
	},
192 273
	{
193 274
		"commonName": "House Finch",
194 275
		"scientificName": "Haemorhous mexicanus",
195 276
		"date": "2019-02-17",
196 277
		"location": "Rittman",
197 -
		"state": "US-OH"
278 +
		"state": "US-OH",
279 +
		"photo": "https://static.inaturalist.org/photos/178968933/medium.jpg",
280 +
		"summary": null,
281 +
		"wikiUrl": "http://en.wikipedia.org/wiki/House_finch"
198 282
	},
199 283
	{
200 284
		"commonName": "House Sparrow",
201 285
		"scientificName": "Passer domesticus",
202 286
		"date": "2019-02-17",
203 287
		"location": "Rittman",
204 -
		"state": "US-OH"
288 +
		"state": "US-OH",
289 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/4608133/medium.jpg",
290 +
		"summary": null,
291 +
		"wikiUrl": "http://en.wikipedia.org/wiki/House_sparrow"
205 292
	},
206 293
	{
207 294
		"commonName": "Indigo Bunting",
208 295
		"scientificName": "Passerina cyanea",
209 296
		"date": "2025-05-28",
210 297
		"location": "Home",
211 -
		"state": "US-TN"
298 +
		"state": "US-TN",
299 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/168808461/medium.jpeg",
300 +
		"summary": null,
301 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Indigo_bunting"
212 302
	},
213 303
	{
214 304
		"commonName": "Mourning Dove",
215 305
		"scientificName": "Zenaida macroura",
216 306
		"date": "2019-02-17",
217 307
		"location": "Rittman",
218 -
		"state": "US-OH"
308 +
		"state": "US-OH",
309 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/14015355/medium.jpg",
310 +
		"summary": null,
311 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Mourning_dove"
219 312
	},
220 313
	{
221 314
		"commonName": "Northern Cardinal",
222 315
		"scientificName": "Cardinalis cardinalis",
223 316
		"date": "2019-02-17",
224 317
		"location": "Rittman",
225 -
		"state": "US-OH"
318 +
		"state": "US-OH",
319 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/189434971/medium.jpg",
320 +
		"summary": null,
321 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Northern_cardinal"
226 322
	},
227 323
	{
228 324
		"commonName": "Northern Mockingbird",
229 325
		"scientificName": "Mimus polyglottos",
230 326
		"date": "2025-06-01",
231 327
		"location": "Home",
232 -
		"state": "US-TN"
328 +
		"state": "US-TN",
329 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/142697277/medium.jpg",
330 +
		"summary": null,
331 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Northern_mockingbird"
233 332
	},
234 333
	{
235 334
		"commonName": "Palm Warbler",
236 335
		"scientificName": "Setophaga palmarum",
237 336
		"date": "2026-04-04",
238 337
		"location": "Reflection",
239 -
		"state": "US-TN"
338 +
		"state": "US-TN",
339 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/496238324/medium.jpeg",
340 +
		"summary": null,
341 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Palm_warbler"
240 342
	},
241 343
	{
242 344
		"commonName": "Pileated Woodpecker",
243 345
		"scientificName": "Dryocopus pileatus",
244 346
		"date": "2026-04-24",
245 347
		"location": "Home",
246 -
		"state": "US-TN"
348 +
		"state": "US-TN",
349 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/168915535/medium.jpg",
350 +
		"summary": null,
351 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Pileated_woodpecker"
247 352
	},
248 353
	{
249 354
		"commonName": "Red-bellied Woodpecker",
250 355
		"scientificName": "Melanerpes carolinus",
251 356
		"date": "2025-06-01",
252 357
		"location": "Home",
253 -
		"state": "US-TN"
358 +
		"state": "US-TN",
359 +
		"photo": "https://static.inaturalist.org/photos/383227697/medium.jpeg",
360 +
		"summary": null,
361 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Red-bellied_woodpecker"
254 362
	},
255 363
	{
256 364
		"commonName": "Red-headed Woodpecker",
257 365
		"scientificName": "Melanerpes erythrocephalus",
258 366
		"date": "2019-02-17",
259 367
		"location": "Rittman",
260 -
		"state": "US-OH"
368 +
		"state": "US-OH",
369 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/232699/medium.jpg",
370 +
		"summary": null,
371 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Red-headed_woodpecker"
261 372
	},
262 373
	{
263 374
		"commonName": "Song Sparrow",
264 375
		"scientificName": "Melospiza melodia",
265 376
		"date": "2025-06-01",
266 377
		"location": "Home",
267 -
		"state": "US-TN"
378 +
		"state": "US-TN",
379 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/104749271/medium.jpg",
380 +
		"summary": null,
381 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Song_sparrow"
268 382
	},
269 383
	{
270 384
		"commonName": "Summer Tanager",
271 385
		"scientificName": "Piranga rubra",
272 386
		"date": "2025-06-01",
273 387
		"location": "Home",
274 -
		"state": "US-TN"
388 +
		"state": "US-TN",
389 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/21089917/medium.jpg",
390 +
		"summary": null,
391 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Summer_tanager"
275 392
	},
276 393
	{
277 394
		"commonName": "Tufted Titmouse",
278 395
		"scientificName": "Baeolophus bicolor",
279 396
		"date": "2025-04-24",
280 397
		"location": "Home",
281 -
		"state": "US-TN"
398 +
		"state": "US-TN",
399 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/178382206/medium.jpeg",
400 +
		"summary": null,
401 +
		"wikiUrl": "https://en.wikipedia.org/wiki/Tufted_titmouse"
282 402
	},
283 403
	{
284 404
		"commonName": "White-breasted Nuthatch",
285 405
		"scientificName": "Sitta carolinensis",
286 406
		"date": "2019-02-17",
287 407
		"location": "Rittman",
288 -
		"state": "US-OH"
408 +
		"state": "US-OH",
409 +
		"photo": "https://static.inaturalist.org/photos/25544020/medium.jpeg",
410 +
		"summary": null,
411 +
		"wikiUrl": "http://en.wikipedia.org/wiki/White-breasted_nuthatch"
289 412
	},
290 413
	{
291 414
		"commonName": "White-throated Sparrow",
292 415
		"scientificName": "Zonotrichia albicollis",
293 416
		"date": "2025-11-19",
294 417
		"location": "Coolidge Park",
295 -
		"state": "US-TN"
418 +
		"state": "US-TN",
419 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/23268661/medium.jpg",
420 +
		"summary": null,
421 +
		"wikiUrl": "https://en.wikipedia.org/wiki/White-throated_sparrow"
296 422
	},
297 423
	{
298 424
		"commonName": "Yellow-bellied Sapsucker",
299 425
		"scientificName": "Sphyrapicus varius",
300 426
		"date": "2025-10-23",
301 427
		"location": "Home",
302 -
		"state": "US-TN"
428 +
		"state": "US-TN",
429 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/294094189/medium.jpeg",
430 +
		"summary": null,
431 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Yellow-bellied_sapsucker"
303 432
	},
304 433
	{
305 434
		"commonName": "Yellow-rumped Warbler",
306 435
		"scientificName": "Setophaga coronata",
307 436
		"date": "2026-01-02",
308 437
		"location": "Reflection",
309 -
		"state": "US-TN"
438 +
		"state": "US-TN",
439 +
		"photo": "https://inaturalist-open-data.s3.amazonaws.com/photos/128439522/medium.jpg",
440 +
		"summary": null,
441 +
		"wikiUrl": "http://en.wikipedia.org/wiki/Yellow-rumped_warbler"
310 442
	}
311 443
];
packages/client/src/pages/birds.astro +31 −22
14 14
			<h1 class="title">Birds</h1>
15 15
			<p class="mt-2 text-sm text-gray-400">{birds.length} species observed</p>
16 16
		</div>
17 -
		<div class="w-full">
18 -
			<table class="w-full text-sm border-collapse">
19 -
				<thead>
20 -
					<tr class="border-b border-gray-700 text-left text-gray-400">
21 -
						<th class="pb-2 pr-6 font-normal">#</th>
22 -
						<th class="pb-2 pr-6 font-normal">Common Name</th>
23 -
						<th class="pb-2 pr-6 font-normal italic">Scientific Name</th>
24 -
25 -
					</tr>
26 -
				</thead>
27 -
				<tbody>
28 -
					{birds.map((bird, i) => (
29 -
						<tr class="border-b border-gray-800 hover:bg-gray-900/40 transition-colors">
30 -
							<td class="py-2 pr-6 text-gray-500 tabular-nums">{i + 1}</td>
31 -
							<td class="py-2 pr-6 font-medium">
32 -
								<a href={`https://www.allaboutbirds.org/guide/${bird.commonName.replace(/ /g, "_")}`} target="_blank" rel="noopener noreferrer" class="hover:underline">{bird.commonName}</a>
33 -
							</td>
34 -
							<td class="py-2 pr-6 text-gray-400 italic">{bird.scientificName}</td>
35 -
						</tr>
36 -
					))}
37 -
				</tbody>
38 -
			</table>
17 +
		<div class="flex w-full flex-col">
18 +
			{birds.map((bird) => (
19 +
				<a
20 +
					href={`https://www.allaboutbirds.org/guide/${bird.commonName.replace(/ /g, "_")}`}
21 +
					target="_blank"
22 +
					rel="noopener noreferrer"
23 +
					class="flex items-center gap-4 border-b border-[#333] py-3 no-underline transition-opacity hover:opacity-70"
24 +
				>
25 +
					{bird.photo ? (
26 +
						<img
27 +
							src={bird.photo}
28 +
							alt={bird.commonName}
29 +
							class="h-16 w-16 flex-shrink-0 rounded object-cover"
30 +
							loading="lazy"
31 +
						/>
32 +
					) : (
33 +
						<div class="flex h-16 w-16 flex-shrink-0 items-center justify-center rounded bg-[#1a1a1a]">
34 +
							<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 opacity-30" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round">
35 +
								<path d="M16 7h.01M3.055 11H5a2 2 0 0 1 2 2v1a2 2 0 0 0 2 2a2 2 0 0 1 2 2v2.945M8 3.935V5.5A2.5 2.5 0 0 0 10.5 8h.5a2 2 0 0 1 2 2a2 2 0 0 0 4 0a2 2 0 0 1 2-2h1.064M15 20.488V18a2 2 0 0 1 2-2h3.064M21 12a9 9 0 1 1-18 0a9 9 0 0 1 18 0"/>
36 +
							</svg>
37 +
						</div>
38 +
					)}
39 +
					<div class="flex flex-col gap-0.5">
40 +
						<span class="text-base">{bird.commonName}</span>
41 +
						<span class="text-xs italic opacity-50">{bird.scientificName}</span>
42 +
						{bird.summary && (
43 +
							<span class="mt-0.5 text-xs opacity-50 line-clamp-2">{bird.summary}</span>
44 +
						)}
45 +
					</div>
46 +
				</a>
47 +
			))}
39 48
		</div>
40 49
	</div>
41 50
</PageLayout>