summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/BaseHead.astro44
-rw-r--r--src/components/BlogFeed.astro76
-rw-r--r--src/components/FormattedDate.astro21
-rw-r--r--src/components/Me.astro43
-rw-r--r--src/consts.ts2
-rw-r--r--src/content/blog/2024/01/14/sapiens_on_religion.md77
-rw-r--r--src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md261
-rw-r--r--src/content/blog/2024/03/30/easter.md108
-rw-r--r--src/content/config.ts19
-rw-r--r--src/env.d.ts2
-rw-r--r--src/layouts/BlogPost.astro47
-rw-r--r--src/layouts/Page.astro17
-rw-r--r--src/pages/blog/[...slug].astro20
-rw-r--r--src/pages/blog/index.astro17
-rw-r--r--src/pages/error.astro14
-rw-r--r--src/pages/index.astro11
-rw-r--r--src/pages/rss.xml.js17
17 files changed, 796 insertions, 0 deletions
diff --git a/src/components/BaseHead.astro b/src/components/BaseHead.astro
new file mode 100644
index 0000000..e8e44ab
--- /dev/null
+++ b/src/components/BaseHead.astro
@@ -0,0 +1,44 @@
+---
+interface Props {
+ title: string;
+ description: string;
+ image?: string;
+}
+
+const canonicalURL = new URL(Astro.url.pathname, Astro.site);
+
+const { title, description, image = '/images/headshot.jpg' } = Astro.props;
+---
+
+<!-- Stylesheets -->
+<link rel="stylesheet" href="/css/reset.css" />
+<link rel="stylesheet" href="/css/base.css" />
+<link rel="stylesheet" href="/css/hcard.css" />
+
+<!-- Global Metadata -->
+<meta charset="utf-8" />
+<meta name="viewport" content="width=device-width,initial-scale=1" />
+<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+<meta name="generator" content={Astro.generator} />
+
+<!-- Canonical URL -->
+<link rel="canonical" href={canonicalURL} />
+
+<!-- Primary Meta Tags -->
+<title>{title}</title>
+<meta name="title" content={title} />
+<meta name="description" content={description} />
+
+<!-- Open Graph / Facebook -->
+<meta property="og:type" content="website" />
+<meta property="og:url" content={Astro.url} />
+<meta property="og:title" content={title} />
+<meta property="og:description" content={description} />
+<meta property="og:image" content={new URL(image, Astro.url)} />
+
+<!-- Twitter -->
+<meta property="twitter:card" content="summary_large_image" />
+<meta property="twitter:url" content={Astro.url} />
+<meta property="twitter:title" content={title} />
+<meta property="twitter:description" content={description} />
+<meta property="twitter:image" content={new URL(image, Astro.url)} />
diff --git a/src/components/BlogFeed.astro b/src/components/BlogFeed.astro
new file mode 100644
index 0000000..d785524
--- /dev/null
+++ b/src/components/BlogFeed.astro
@@ -0,0 +1,76 @@
+---
+import type { CollectionEntry } from 'astro:content';
+import { getCollection } from 'astro:content';
+
+export interface Props {
+ headingLevel?: 1 | 2 | 3 | 4 | 5 | 6,
+ hideAuthor?: boolean,
+};
+
+const { headingLevel = 2, hideAuthor = false } = Astro.props;
+
+const posts = (await getCollection('blog'));
+
+const distinctYears: number[] = posts
+ .map(post => post.data.pubDate.year)
+ .reduce<number[]>((acc, curr) => acc.includes(curr) ? acc : [...acc, curr], [])
+ .sort((a, b) => b - a);
+
+function matchesYear(year: number) {
+ return (post: CollectionEntry<'blog'>) => post.data.pubDate.year === year;
+}
+
+function sortByPubDateDescending(post1: CollectionEntry<'blog'>, post2: CollectionEntry<'blog'>) {
+ const year1 = post1.data.pubDate.year;
+ const year2 = post2.data.pubDate.year;
+ const month1 = post1.data.pubDate.month;
+ const month2 = post2.data.pubDate.month;
+ const day1 = post1.data.pubDate.day;
+ const day2 = post2.data.pubDate.day;
+
+ if (year1 !== year2) {
+ return year2 - year1;
+ } else if (month1 !== month2) {
+ return month2 - month1;
+ } else {
+ return day2 - day1;
+ }
+}
+
+const headingElem = `h${headingLevel}`;
+
+const canonicalUrl = new URL(Astro.url.pathname, Astro.site)
+---
+
+<section class="h-feed">
+ <Fragment set:html={`
+ <${headingElem} class="p-name">
+ My blog
+ </${headingElem}>
+ `} />
+
+ <aside>
+ <p class={hideAuthor ? 'hidden' : ''}>
+ This blog is written by <a class="p-author h-card" href="/">Joe Carstairs</a>
+ </p>
+
+ <p>
+ <a class="u-url" href={canonicalUrl}>Permalink</a>
+ </p>
+ </aside>
+
+ <ul>
+ { distinctYears.map(year => (
+ <li>
+ {year}
+ <ul>
+ { posts.filter(matchesYear(year)).sort(sortByPubDateDescending).map(post => (
+ <li class="h-entry">
+ <a class="u-url p-name" href={`/blog/${post.slug}`}>{post.data.title}</a>
+ </li>
+ )) }
+ </ul>
+ </li>
+ )) }
+ </ul>
+</section> \ No newline at end of file
diff --git a/src/components/FormattedDate.astro b/src/components/FormattedDate.astro
new file mode 100644
index 0000000..bb7a2c0
--- /dev/null
+++ b/src/components/FormattedDate.astro
@@ -0,0 +1,21 @@
+---
+interface Props {
+ className?: string;
+ date: Date | string;
+}
+
+let { className, date } = Astro.props;
+if (typeof(date) === 'string') {
+ date = new Date(date);
+}
+---
+
+<time datetime={date.toISOString()} class={className ?? ''}>
+ {
+ date.toLocaleDateString('en-GB', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ })
+ }
+</time>
diff --git a/src/components/Me.astro b/src/components/Me.astro
new file mode 100644
index 0000000..c9af587
--- /dev/null
+++ b/src/components/Me.astro
@@ -0,0 +1,43 @@
+---
+---
+
+<section class="h-card">
+ <img class="u-photo" src="/images/headshot.jpg" height="256" width="256" />
+
+ <div>
+ <h1>
+ <a class="p-name u-url u-uid" href="https://joeac.net" rel="me">
+ Joe Carstairs
+ </a>
+ </h1>
+
+ <p>
+ Hi! 👋 My name is <span class="p-given-name">Joe</span>
+ <span class="p-family-name">Carstairs</span>. I’m a
+ <span class="p-job-title">software developer</span> at
+ <a class="p-org" href="https://www.scottlogic.com">Scott Logic</a>, a
+ graduate of Philosophy and Mathematics at the University of Edinburgh,
+ a committed Christian and a pretty rubbish poet.
+ </p>
+
+ <p>
+ I’m also the <span class="p-job-title">secretary</span> of the
+ <a class="p-org" href="https://scotsleidassocie.org">Scots Language Society</a>.
+ <a href="https://github.com/joeacarstairs/lallans-wabsteid-astro">Help me maintain our website!</a>
+ </p>
+
+ <p>
+ Email me at
+ <a class="u-email" href="mailto:joeacarstairs@gmail.com">joeacarstairs@gmail.com</a>
+ with your thoughts on metaethics, Scots verse and eschatology.
+ </p>
+
+ <p>
+ Or get me on
+ <a href="https://www.facebook.com/joe.carstairs.5" rel="me">Facebook</a>,
+ <a href="https://mastodon.social/@joe_carstairs" rel="me">Mastodon</a>,
+ <a href="https://www.linkedin.com/in/joe-carstairs-0aa936277" rel="me">LinkedIn</a>,
+ or <a href="https://github.com/joeacarstairs" rel="me">GitHub</a>.
+ </p>
+ </div>
+</section> \ No newline at end of file
diff --git a/src/consts.ts b/src/consts.ts
new file mode 100644
index 0000000..461043d
--- /dev/null
+++ b/src/consts.ts
@@ -0,0 +1,2 @@
+export const SITE_TITLE = 'Joe Carstairs';
+export const SITE_DESCRIPTION = 'Joe’s personal website.'; \ No newline at end of file
diff --git a/src/content/blog/2024/01/14/sapiens_on_religion.md b/src/content/blog/2024/01/14/sapiens_on_religion.md
new file mode 100644
index 0000000..b484557
--- /dev/null
+++ b/src/content/blog/2024/01/14/sapiens_on_religion.md
@@ -0,0 +1,77 @@
+---
+title: Harari’s Sapiens on Religion
+description: >-
+ In which I discuss why I think Harari’s characterisation of religion
+ is inadequate because it’s too materialistic.
+pubDate:
+ year: 2024
+ month: 01
+ day: 14
+---
+
+I’ve been slowly re-reading Yuval Noah Harari’s 2014 classic,
+<a href="https://www.ynharari.com/book/sapiens-2">Sapiens</a>,
+which apart from being ridiculously over-scoped and hilariously
+under-evidenced, is proving delightfully entertaining.
+
+I’ve just finished chapter 12, covering the world history of all
+religion in thirty pages. Of course, at that level of brevity,
+there will be many deficiencies. But here’s some thoughts - not
+terribly well organised - which stand out to me.
+
+Hurari generally assumes a materialist metaphysic (a problem which
+blights the book more generally). Nothing exists except physical stuff.
+This gives him severe tunnel vision. As a consequence of this
+restricting metaphysic, he is forced to adopt limiting accounts of what
+the role of religion is in world history, and therefore what religion is.
+
+> The crucial historical role of religion has been to give superhuman
+> legitimacy to [all social orders and hierarchies].
+> Religion can thus be defined as <em>a system of human norms and
+> values that is founded on a belief in a superhuman order</em>.
+> <footer>p. 234</footer>
+
+It might seem a little unfair to criticise Harari for giving a
+materialist account of religion. <i>Sapiens</i> is, after all, a
+materialist world history.
+
+But this account is just one extreme example of how that project, to
+give a materialist account of world history, will inevitably lack the
+metaphysical resources to really understand the human story.
+
+On Harari’s view, any human enterprise which attempts to understand
+that which transcends direct human experience is at best an effort in
+imaginative story-telling. All scientific theory, theology, ethics and
+metaphysics either contorted out of all recognition into a pragmatic
+fiction or is cast to the flames.
+
+In particular, it’s a view which is incapable of taking seriously some
+of the most important questions human beings have grappled with in the
+course of their history. Those who know me won’t be surprised at which
+ones I’m going to pick out: who was the being which made their covenant
+with Abraham? How is that promise being fulfilled? And who the heck was
+Jesus of Nazareth?
+
+If Harari’s characterisation of religion is adequate - and the Abrahamic
+faiths come under that banner - then those questions are reduced to
+nothing more profound than Doctor Who fans arguing over ‘canon’. The
+question of who God is becomes a mere tool for the organisation of
+society, rather than a substantial and important question on a matter
+of fact.
+
+This is a shortcoming for its own sake: a materialist account of
+religion cannot adequately account for the phenomenon of religion
+itself.
+
+But it is also a shortcoming even by its own lights. Without giving
+serious consideration to the substantial matter of what Harari calls
+‘religion’ (which, to his mind, includes the Abrahamic faiths,
+Hinduism, paganism, animism, Buddhism, Shintoism, Confucianism,
+capitalism, communism and Nazism), even the material facts are
+inexplicable. Why would, as Harari is keen to point out, out, people
+fight and die over and over again for a fiction?
+
+The material facts themselves prove that ‘religion’ as he construes it
+is not window dressing to the real story of history. It cannot merely
+serve as a mechanism in the churning of material history. It is itself
+the centre of the story.
diff --git a/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md b/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md
new file mode 100644
index 0000000..8641a5d
--- /dev/null
+++ b/src/content/blog/2024/01/29/euhwc_toast_to_the_lasses_2024.md
@@ -0,0 +1,261 @@
+---
+title: EUHWC Toast to the Lassies 2024
+description: >-
+ At the <a href="https://www.euhwc.co.uk">EUHWC</a> Burns meet in
+ Ullapool last weekend, I had the last privilege of giving the Toast to
+ the Lassies. Particularly for the benefit of those who weren’t there,
+ here it is in full!
+pubDate:
+ year: 2024
+ month: 01
+ day: 29
+---
+
+Had Burns, instead of his sweet bonnie Jean,<br>
+his skills poetical for to mature<br>
+had any one of our club’s lassies seen<br>
+he would forever have remained obscure.<br>
+If he had nothing but this box of worms<br>
+Scotia would have been poorer, that I’m sure.<br>
+Now none of us can claim to be a Burns,<br>
+I’m no poetic master, still, I’ll have a punt,<br>
+though let’s be clear, I’ll do it on my terms.<br>
+I’ve everywhere avoided being blunt -<br>
+politeness matters more than any schema -<br>
+but it is hard when Isla’s such a cunt.<br>
+It was a challenge to produce a terza rima<br>
+I could recite withouten snoring;<br>
+you’ve been so stiff I thought youse had oedema.<br>
+The bother is this year is you’ll all been boring:<br>
+no drugs, no sex, no gossiping or lies,<br>
+no rock and roll, and hardly any whoring.<br>
+But hey well, rules is rules, I’ve had to try!<br>
+At least it can’t be worse than the reply.<br>
+
+I’ll start with Audrey, the club’s senior member,<br>
+for if there’s something that I say which disconcerts her,<br>
+it’s fine: the poor old girl, she won’t remember.<br>
+She likes to let us think she’s a hard worker<br>
+but we’re electing a third social sec…<br>
+it’s pretty clear she’s just another shirker.<br>
+This lady, half American, half Czech,<br>
+for study, moved to Scotland for to do<br>
+American history – really, what the heck?<br>
+The club is so much louder thanks to you:<br>
+impressive vocals for just five foot two.<br>
+
+That woman, Willow, reggles is bespeckled<br>
+with her sickle and her fishing tackle<br>
+shackled by the shins while she is heckled;<br>
+the way that Willow waddles maks me cackle<br>
+like a speckled jackal getting tickles,<br>
+worth a shekel in the tabernacle;<br>
+I chuckle muckle at her love of pickles<br>
+which she wiggles when she has the heart<br>
+while work for the Committee’s fickle trickles.<br>
+Her modus operandi: <em>you can’t rush art.</em><br>
+Her reimbursements programme’s going great;<br>
+any day now, she’ll maybe even start.<br>
+She cannot walk without Audrey, her mate:<br>
+I wonder when they’re going to consummate.<br>
+
+Although they make them pretty tough in Peebles,<br>
+the thought of actually going up a peak<br>
+fills Shona Lewis with the heeble-jeebles.<br>
+New car? We miss your beautiful antique!<br>
+How long before this one’s also up a creek?<br>
+
+Once there was a lass called Hannah Collier<br>
+whom even hell below regarded nasty,<br>
+deeply despised by all that dwells there.<br>
+Dating’s proceeding slowly for our lassie;<br>
+not far from giving up til she beguiles<br>
+a hot Italian in Southsider: classy!<br>
+At first, Michaelo seems to be all smiles<br>
+till it transpires he’s one of Dante’s demons…<br>
+I guess it’s back to posters of Harry Styles.<br>
+One day you’ll get a decent boy, keep dreamin;<br>
+somewhere there waits a handsome Mr Collier.<br>
+Hopefully when she meets him she’ll no be steamin.<br>
+Hannah, I’m not sure why you chose to maul your<br>
+poor skeleton at Subway (she’s still tetchy)<br>
+and then abandon what remains of all your<br>
+dignity at Ryvoan with a Frenchie!<br>
+I think he wishes that he never met ye.<br>
+
+And has a quiet Felicia e’er been seen?<br>
+The energy she has is frankly wild.<br>
+I’ve never seen a hillwalker so keen!<br>
+<i lang="de">Ssie ischt raschtlos und nie gelangweilt</i>.<br>
+She eats raw oats with soggy protein powder:<br>
+a camping pot has ne’er been worse defiled.<br>
+She uses what her Maker has endowed her<br>
+with: her recorder skills are off the charts;<br>
+youse think I’m joking, but I wouldn’t doubt her!<br>
+This lass of the land of the Rot-Gold-Schwarz<br>
+will soon depart, though long we might beseech ya<br>
+to stay. Of course, you’ll break all of our hearts,<br>
+but mine most of all. Any time, Felicia,<br>
+Creag Meagaidh calls, I know routes up the rear<br>
+dark and under-explored that I can teach you!<br>
+I won’t deny I think it’s rather queer<br>
+the things you do with chickpeas, but no matter.<br>
+You’re keen, you’re quick, you’re cool, that much is clear.<br>
+In fact, I think you’d make a damn good faffer:<br>
+swoop down on distilleries like the Luftwaffe.<br>
+
+And now we come to our girl Emily Topness!<br>
+You’re keen for social sec. You’d suit the role<br>
+because… I’m not quite sure, it’s embdy’s guess.<br>
+We met your sister, and she was just as dull.<br>
+No, please drone on about Icelandic soil!<br>
+Poor Joe here down the front’s bored out his skull.<br>
+And since I mentioned Joe – I hate to spoil<br>
+it for you – but you’ve got the inferior Joe,<br>
+by Jove, no joke, it’s Jock here’s got the style!<br>
+Nah, write the boy a sonnet, get in the flow,<br>
+Whatever you produce’ll beat by thrice<br>
+your Masters thesis. What’d you got to show<br>
+for months of hunting for the butterflies?<br>
+‘There weren’t any.’ Oh, and have some sense,<br>
+cos I’ve heard rumours – I assume they’re lies –<br>
+you’ve called yourself the ‘poet in residence.’<br>
+You know you can’t compete, drop the pretence.<br>
+
+Tereza was our gear sec for last year.<br>
+She helped herself to stuff: that’s factual.<br>
+Now when she asks to loan a bit club gear<br>
+we have to ask her to provide collateral.<br>
+She picked up tin whistle pretty sharp!<br>
+Which is to say, she’s not a natural.<br>
+She’s nowhere happier than under tarp<br>
+gazing up at the moon and stars alone<br>
+somewhere distant and remote like Glen Tarff.<br>
+Now what to say about Lucy Ma-the-soooon....<br>
+she likes… to faff… mm hmmm… well, moving on!<br>
+
+And now we come to Emilie the French.<br>
+She seems to be nice on the trips we see her<br>
+but my distrust of frogs will ne’er be quenched.<br>
+Claims she’s a ‘pharmacist’? So she’s a dealer.<br>
+Need some pills in a pinch? You call, she’s there<br>
+at your door in her rally-approved four-wheeler.<br>
+One question we have is, why are you here?<br>
+Most folk are in uni, you’ve no refutin<br>
+you were kicked out after second year!<br>
+Now the Engineering grad, Sophia Newton.<br>
+Your namesake, Isaac, was a man convicted,<br>
+constructed calculus; but no computin,<br>
+not even Isaac’s, could’ve e’er predicted<br>
+you’d drop the Eng for creative writing!<br>
+now that’s what I would call a self-inflicted<br>
+inflection point! It must be quite enlightening,<br>
+but that doesn’t excuse when you give us an earful.<br>
+The blood boils in our veins, the rage heightening,<br>
+and you’re an American, that makes me fearful.<br>
+What’s your secret? You have us knackered!<br>
+What are you on to always be so cheerful?<br>
+Now we approach the topic of Merzbacher.<br>
+Wait, she’s not here? Abandoned ship?<br>
+She says she’s informatics: so she’s a hacker?<br>
+She has strong views, she lets her anger rip.<br>
+Poor George got an earful, full of future advice,<br>
+but why hasn’t she been on another club trip?<br>
+We’re cruel to focus on this list of vice;<br>
+the fact remains: she’s headstrong and nice.<br>
+
+On Skye, a lady gave her poles to Sasha,<br>
+which was really nice - I mean just the best -<br>
+but Sasha really didn’t have to flash her.<br>
+Quick history lesson: way back, RBS<br>
+led the banking system to self-destruct<br>
+and left taxpayers to pick up the mess.<br>
+Since then, the name’s so irredeemably fucked<br>
+they’ve had to ditch the brand once and for all.<br>
+There’s one lassie who I need not instruct<br>
+What, these days, the Royal Bank is called<br>
+cos NatWest’s nasty history of scandal<br>
+didn’t stop Booth from working there at all.<br>
+Nothing motivates her more than to trample<br>
+upon the working class. They set her free.<br>
+She sank the pound quicker than the Belgrano,<br>
+because ‘there is no such thing as society,’<br>
+that’s how it is, is it? All right, I see.<br>
+
+Now, coming all the way from Glenmore Lodge,<br>
+it’s Ellie’s turn! We have done what we can,<br>
+although I’m scared what she’ll put in my squash.<br>
+She wasn’t into Benji, but listen man,<br>
+you’re lucky that you dodged her drunken benders.<br>
+You’ll wake up in a tent in Kyrgystan,<br>
+as for how you got there, no-one remembers,<br>
+and if you’d known you’d be sleeping next to Ellie,<br>
+you would’ve brought some fucking ear defenders.<br>
+She’ll wrap you in bubblewrap, from your ears to your belly,<br>
+cotton clothes for none, and no complaining,<br>
+applying safety to the max, spare socks in your wellies.<br>
+She’s always at her Mountain Leader training,<br>
+practicing her night nav in the locale,<br>
+pursuing QMDs - unless it’s raining.<br>
+But some water should not scare our gal!<br>
+She’s had much experience with the wet as of late:<br>
+after all, she got on well with our navy pal.<br>
+What was the age of that particular first mate?<br>
+Older than your ex - always part of the plan?<br>
+Ah, of course! He was a spry twenty-eight!<br>
+Youth’s for the losers, let’s get you a real man,<br>
+mature and rugged, but kind and astute?<br>
+Just make sure he’s not as old as your gran.<br>
+One request we all have is you ditch the uke:<br>
+never have strings been pluckèd quite so shitely;<br>
+we would all much rather be hit by a nuke.<br>
+And please shut up about your nice society.<br>
+We are all glad you had a fun summer,<br>
+but bringing it up throws us right back to sobriety.<br>
+To lose you of course would be a bummer:<br>
+that is, for your carefully groomed newcomers.<br>
+
+Now time for the main woman, El Presidente!<br>
+To here, it’s been like getting stones to bleed,<br>
+but in Isla Burslem’s case we’ve material aplenty!<br>
+As Holy Scripture says, ‘let those who lead<br>
+well be worthy of double honour,’ so<br>
+your bit is double length – it’s quite the screed!<br>
+I’ll start off with her brilliant boyfriend – oh!<br>
+Not boyfriend! Friend? To me this rather smacks<br>
+of low commitment, but what do I know?<br>
+So far, he’s disappointing, but on track.<br>
+What’s he up to Isla: seven minutes? neat!<br>
+Despite that, he is never holding back<br>
+your blossoming romance with Dr Peat.<br>
+Don’t deny it, that launch was pretty hard!<br>
+It’s fifth base next: that’s photos of his feet.<br>
+It’s fair to say her reputation’s marred.<br>
+We all regret that we did once anoint<br>
+her President: her premiership’s ill-starred.<br>
+Hey - you’re meant to be in charge of this joint!<br>
+You’re seldom seen cos of the mountaineering<br>
+meets that you’re always on. You’d made your point<br>
+before you chose to go off disappearing<br>
+to <em>New Zealand</em>… we get the message! Plus<br>
+we’ve had enough of all your domineering:<br>
+maybe it’s time we put you on a bus!<br>
+Nah, I’m just joking. All I’ve said’s refutable.<br>
+But the boys, we mean this next bit, all of us,<br>
+so stop me Isla if this isn’t suitable<br>
+but honestly we think your mum is beautiful.<br>
+
+Alas, I have to bring an end to this rhyme.<br>
+I know it wasn’t much, in our defence,<br>
+the fact you used ChatGPT’s a crime.<br>
+I hope I’ve not caused over much offence<br>
+don’t worry, that is it, I’ve said my bit,<br>
+so I’ll turn from the ladies to the gents.<br>
+Yeah, don’t look away now, we wrote this shit!<br>
+I see you looking at your laces, Chris!<br>
+Wit without real goodwill is not legit,<br>
+so boys, don’t send sincerity to piss!<br>
+Why did God say he’d take our hearts of stone<br>
+and give us hearts of flesh? For this, for this!<br>
+Here is flesh of our flesh, bone of our bone;<br>
+love, and love nothing more but God alone.<br>
diff --git a/src/content/blog/2024/03/30/easter.md b/src/content/blog/2024/03/30/easter.md
new file mode 100644
index 0000000..01d4c5b
--- /dev/null
+++ b/src/content/blog/2024/03/30/easter.md
@@ -0,0 +1,108 @@
+---
+title: Why Easter is the best week of the year
+description: >-
+ Based on a talk given to my colleagues at
+ <a href="https://www.scottlogic.co.uk">Scott Logic</a> for Maundy
+ Thursday, 2024.
+pubDate:
+ year: 2024
+ month: 03
+ day: 30
+---
+
+As you might have noticed, it is Easter this week! So I'd like to take five or
+five minutes of your time to share why I – and about two billion other humans
+going about the place just now – think Easter is the best week of the year. And
+it's got something to do with a special Christian ritual called Communion.
+
+Communion, at its heart, is about as simple a ritual as you can get. You get
+together with a bunch of other people. You share some bread, and you share some
+wine.
+
+And it’s because of this ritual that so many people regard Easter as the best
+week of the year. I want to explain to you why that is, and more than that, I
+want to convince you that Easter is the best week of the year for you, too!
+
+If you’ve passed by _The Hub_ at the top of Johnstone Terrace here in
+Edinburgh recently, you might have notice the banner which is draped over
+the railings just now – reading, ‘RITUALS THAT UNITE US.’
+
+Now, that might seem like an odd idea. But wouldn’t that be great, if we
+actually had a ritual which could unite us? Because the world could surely do
+with a bit more unity right now. The world seems so divided, and sometimes it
+seems like there’s no hope for real unity.
+
+We can see that in our politics. We’re divided about foreign policy, about
+taxation policy, about trade policy, about environmental policy.
+
+And the conflicts that we have in this country seem pretty trivial when we
+remember the conflicts that are playing out in other parts of the world right
+now. In Israel and Gaza. In Sudan. In Russia and Ukraine.
+
+And there’s plenty of conflict happening on the small scale, too. Often it’s the
+smallest-scale conflicts which hurt us the most deeply. Your landlord pushes you
+around. That friend you trusted like no-one else in the world lets you down. The
+partner or spouse you loved like no-one else in the world – you end up fighting.
+
+It’s possible that you’re going to be reading this right now with a heavy heart
+because of a broken relationship in their life. And doesn’t that hurt more than
+anything else we know?
+
+When the world is groaning so heavily under the weight of conflict, and some
+banner on _The Hub_ tells us a ritual can unite us, that seems so out of
+proportion to the scale of the problem, doesn’t it? What can a ritual do? A bit
+of old superstition? An excuse to divide people, maybe – what can a ritual do
+to unite us?
+
+Well, two thousand years ago, a man had a meal with his friends. Together, they
+shared a meal of bread and wine – which, in that time and place, was the most
+ordinary meal imaginable.
+
+And yet, in that most ordinary event imaginable, something was happening which
+was totally unimaginable. As this man, Jesus, shared the elements of this meal,
+he made some extraordinary statements about what he was doing: ‘take, eat, this
+is my body’ – ‘drink this, all of you; this is my blood of the new covenant.’
+(The word ‘covenant’ means a promise.)
+
+He told them he wasn’t just giving them bread and wine, he was giving his body
+and his blood, and a promise.
+
+Before Jesus ate another meal, he was flogged and nailed to a cross. His blood
+was spilt and his body broken, even to death.
+
+And yet, that wasn’t the end of the Easter story. Because three days later,
+mourners turned up at Jesus’ tomb to pay their respects, and found the tomb
+empty, the stone rolled away. Then they became the first of crowds of
+incredulous eyewitnesses to see Jesus, the same Jesus who was killed on a cross,
+alive.
+
+Some magic trick, right? But this matters a hell of a lot more than just some
+magic trick. Because Jesus became the first person in history to prove that you
+really can both have your cake and eat it. He gave his life, and lived! As a
+result, we can have his life and our own. We can join with Jesus through the
+ritual of Communion which he established, and thereby, through Jesus’ body, join
+together with everyone else who takes part in that ritual, as one body. Then we
+can start living our brand-new, full-fat, original-recipe life overflowing with
+generosity where we too can both give our life to others and enjoy it ourselves.
+Indeed, Jesus taught us and showed us that it’s precisely by giving our lives to
+others that we get to truly live ourselves.
+
+This is why, in spite of all the division which persists in the world today, two
+billion people regard this week as the best week of the year. Two billion
+people, from every nation on Earth, speaking thousands of languages, of every
+age and culture and gender and race, who defy the divisions of this world to
+insist on joining together as one body in Jesus.
+
+That includes Edinburgh’s thriving and diverse Christian community, many of whom
+will be taking part in the ritual of Communion at some point this week. And if
+you want to hear more about how Jesus gave his life for us and why that matters
+for all of us, I’m sure every church in Edinburgh will have their doors open at
+some point this week and would be delighted to have you. My own church,
+Bruntsfield Evangelical, will be having a service tomorrow, Good Friday at
+twelve noon, and also at eleven o’ clock on Sunday – I’d especially recommend this
+one if you’re new to church or haven’t been in a while. You’d be very welcome to
+join me there!
+
+Because Jesus’ new covenant, his promise to all of us, is that in an apparently
+hopelessly divided world, there exists real hope for unity. And that’s why
+Easter is the best week of the year.
diff --git a/src/content/config.ts b/src/content/config.ts
new file mode 100644
index 0000000..58dbc00
--- /dev/null
+++ b/src/content/config.ts
@@ -0,0 +1,19 @@
+import { defineCollection, z } from 'astro:content';
+
+const dateSchema = z.object({
+ year: z.number(),
+ month: z.number(),
+ day: z.number(),
+});
+
+const blog = defineCollection({
+ type: 'content',
+ schema: z.object({
+ title: z.string(),
+ description: z.string(),
+ pubDate: dateSchema,
+ updatedDate: z.optional(dateSchema),
+ }),
+});
+
+export const collections = { blog };
diff --git a/src/env.d.ts b/src/env.d.ts
new file mode 100644
index 0000000..acef35f
--- /dev/null
+++ b/src/env.d.ts
@@ -0,0 +1,2 @@
+/// <reference path="../.astro/types.d.ts" />
+/// <reference types="astro/client" />
diff --git a/src/layouts/BlogPost.astro b/src/layouts/BlogPost.astro
new file mode 100644
index 0000000..25f76e6
--- /dev/null
+++ b/src/layouts/BlogPost.astro
@@ -0,0 +1,47 @@
+---
+import type { CollectionEntry } from 'astro:content';
+import BaseHead from '../components/BaseHead.astro';
+import FormattedDate from '../components/FormattedDate.astro';
+
+type Props = CollectionEntry<'blog'>['data'];
+
+const { title, description, pubDate, updatedDate } = Astro.props;
+
+const canonicalUrl = new URL(Astro.url.pathname, Astro.site);
+
+const pubDateStr = `${pubDate.year}-${pubDate.month}-${pubDate.day}`;
+const updatedDateStr = updatedDate ?
+ `${updatedDate.year}-${updatedDate.month}-${updatedDate.day}`
+ : pubDateStr;
+---
+
+<html lang="en">
+ <head>
+ <BaseHead title={`${title} | joeac’s blog`} description={description} />
+ <link rel="stylesheet" href="/css/blog.css">
+ </head>
+
+ <body>
+ <article class="h-entry">
+ <aside>
+ <p>This is a blog post by <a class="p-author h-card" href="/">Joe Carstairs</a></p>
+ { updatedDate
+ ? (
+ <p>Updated: <FormattedDate date={updatedDateStr} className="dt-updated"/></p>
+ <p>Originally published: <FormattedDate date={pubDateStr} className="dt-published"/></p>
+ ) : <p>Published: <FormattedDate date={pubDateStr} className="dt-published"/></p>
+ }
+ <p>Go back to his <a href="/blog">blog</a> if you like.</p>
+ <p><a class="u-url uid" href={canonicalUrl}>Permalink</a></p>
+ </aside>
+
+ <h1 class="h-name">{title}</h1>
+
+ <p class="p-summary" set:html={description} />
+
+ <div class="e-content">
+ <slot />
+ </div>
+ </article>
+ </body>
+</html>
diff --git a/src/layouts/Page.astro b/src/layouts/Page.astro
new file mode 100644
index 0000000..4e5f3bb
--- /dev/null
+++ b/src/layouts/Page.astro
@@ -0,0 +1,17 @@
+---
+import BaseHead from "../components/BaseHead.astro";
+
+const { title, description } = Astro.props;
+---
+
+<!DOCTYPE html>
+<html lang="en-GB">
+ <head>
+ <BaseHead title={title} description={description} />
+ </head>
+
+ <body>
+ <slot />
+ </body>
+</html>
+
diff --git a/src/pages/blog/[...slug].astro b/src/pages/blog/[...slug].astro
new file mode 100644
index 0000000..800c534
--- /dev/null
+++ b/src/pages/blog/[...slug].astro
@@ -0,0 +1,20 @@
+---
+import { type CollectionEntry, getCollection } from 'astro:content';
+import BlogPost from '../../layouts/BlogPost.astro';
+
+export async function getStaticPaths() {
+ const posts = await getCollection('blog');
+ return posts.map((post) => ({
+ params: { slug: post.slug },
+ props: post,
+ }));
+}
+type Props = CollectionEntry<'blog'>;
+
+const post = Astro.props;
+const { Content } = await post.render();
+---
+
+<BlogPost {...post.data}>
+ <Content />
+</BlogPost> \ No newline at end of file
diff --git a/src/pages/blog/index.astro b/src/pages/blog/index.astro
new file mode 100644
index 0000000..0c617bd
--- /dev/null
+++ b/src/pages/blog/index.astro
@@ -0,0 +1,17 @@
+---
+import BaseHead from '../../components/BaseHead.astro';
+import { SITE_TITLE, SITE_DESCRIPTION } from '../../consts';
+import BlogFeed from '../../components/BlogFeed.astro';
+---
+
+<!doctype html>
+<html lang="en">
+ <head>
+ <BaseHead title={SITE_TITLE} description={SITE_DESCRIPTION} />
+ </head>
+ <body>
+ <main>
+ <BlogFeed headingLevel={1} />
+ </main>
+ </body>
+</html>
diff --git a/src/pages/error.astro b/src/pages/error.astro
new file mode 100644
index 0000000..fc84122
--- /dev/null
+++ b/src/pages/error.astro
@@ -0,0 +1,14 @@
+---
+import Page from '../layouts/Page.astro';
+---
+
+<Page title="Got lost?" description="Error page for Joe's personal website">
+ <main>
+ <h1>Got lost?</h1>
+
+ <p>
+ If you’re on this page, something’s probably gone wrong. Try going to
+ my <a href="/">homepage</a> instead.
+ </p>
+ </main>
+</Page>
diff --git a/src/pages/index.astro b/src/pages/index.astro
new file mode 100644
index 0000000..74cf934
--- /dev/null
+++ b/src/pages/index.astro
@@ -0,0 +1,11 @@
+---
+import BlogFeed from '../components/BlogFeed.astro';
+import Me from '../components/Me.astro';
+import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
+import Page from '../layouts/Page.astro';
+---
+
+<Page title={SITE_TITLE} description={SITE_DESCRIPTION}>
+ <Me />
+ <BlogFeed hideAuthor />
+</Page> \ No newline at end of file
diff --git a/src/pages/rss.xml.js b/src/pages/rss.xml.js
new file mode 100644
index 0000000..79da596
--- /dev/null
+++ b/src/pages/rss.xml.js
@@ -0,0 +1,17 @@
+import rss from '@astrojs/rss';
+import { getCollection } from 'astro:content';
+import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
+
+export async function GET(context) {
+ const posts = await getCollection('blog');
+ return rss({
+ title: SITE_TITLE,
+ description: SITE_DESCRIPTION,
+ site: context.site,
+ items: posts.map((post) => ({
+ ...post.data,
+ link: `/blog/${post.slug}/`,
+ pubDate: new Date(post.data.pubDate.year, post.data.pubDate.month - 1, post.data.pubDate.day),
+ })),
+ });
+}