{"version":"https://jsonfeed.org/version/1.1","title":"Ethan Hawksley's Blog","description":"A blog by Ethan Hawksley, a Computer Science student in the UK. Articles on systems programming, cybersecurity, and whatever else grabs my attention.","home_page_url":"https://hawksley.dev/","feed_url":"https://hawksley.dev/feed.json","authors":[{"name":"Ethan Hawksley","url":"https://hawksley.dev/"}],"language":"en","favicon":"https://hawksley.dev/icon-48x48.png","icon":"https://hawksley.dev/icon-512x512.png","items":[{"id":"https://hawksley.dev/blog/hack-club-horizons","url":"https://hawksley.dev/blog/hack-club-horizons","title":"My Experience with Hack Club Horizons Europa","summary":"I had a great time at a 3-day hackathon","content_text":"I had a great time at a 3-day hackathon","content_html":"<p>I&#39;ve been participating in Hack Club for about two years so far. I&#39;ve written posts about when I <a href=\"https://hawksley.dev/blog/hack-club-shipwrecked\">went to Shipwrecked</a> and when I <a href=\"https://hawksley.dev/blog/hack-club-campfire\">won Campfire</a>.</p>\n<p>It started in late February. Whilst I was working on building this website, I checked the Hack Club Slack and discovered a new announcement.</p>\n<blockquote>\n<p>You notice something big coming from the horizon... a new series of hackathons coming to a region near you!!</p>\n</blockquote>\n<p>This was the announcement for <a href=\"https://horizons.hackclub.com\">Hack Club Horizons</a>, a series of hackathons taking place around the world. I checked the region list and found that one such hackathon was called Horizons Europa. It was happening in Berlin, which the UK has reasonably priced flights to. Since it took place 24th-26th July, I had plenty of time to prepare.</p>\n<p>I signed up to the official Horizons dashboard and checked what I needed to do to qualify. It was simple - all I had to do was spend 35 hours programming on projects of my choice. Hack Club used a platform called <a href=\"https://hackatime.hackclub.com/\">Hackatime</a> to track time coding. My prior experience with Hack Club meant it was already set up.</p>\n<p>Despite also preparing for exams, there was enough free time where I could polish this site. It didn&#39;t take long for me to complete the 35 hours required. I acquired a ticket from the official dashboard and, soon enough, the event arrived.</p>\n<h2>24th July - Day One</h2>\n<p>On the first day of the event, I woke up early at 4:15 and travelled from home to Heathrow Airport. My flight was at 8:50, but it didn&#39;t hurt to be cautious. Security was a breeze to get through. I arrived at the duty-free shops with two hours to kill.</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/heathrow-airport.jpg\" alt=\"Seating area and duty-free shops in Heathrow Airport.\"></p>\n<p>In the run-up to the event, I had also been planning a free book about the XZ Utils backdoor called <em>The Second Maintainer</em>. As of the time of writing, it&#39;s in the proofreading stage. When I was at the airport, I found a table and continued working on it.</p>\n<p>After about 45 minutes, a message on Slack caught my attention. It turned out two other Horizons Europa attendees were taking the same flight as me (I won&#39;t be naming any attendees out of respect to their privacy). I saved my work and set off to find them.</p>\n<p>When we met up with each other, we got along immediately. With all the stuff we had in common, there was no shortage of things to talk about. Sadly, none of us had seats next to each other on the plane. When it came time to board, we drew the conversation to a close. The flight only took 90 minutes and went very peacefully.</p>\n<p>The plane landed, and we all made our way through security - the queues were long but not unbearably so. We met up with another attendee from the UK who had caught another flight. As it turned out, she went to the same school I completed my GCSE&#39;s at! Whilst travelling via German public transport to the event accommodation, we caught up on how everything had been going. I also chatted with the organisers of the event, who were surprisingly sociable.</p>\n<p>After disembarking and going on a brief walk, we arrived at the <a href=\"https://www.wyndhamhotels.com/wyndham-garden/berlin-germany/wyndham-garden-berlin-mitte/overview\">Wyndham Garden Berlin Mitte hotel</a>. Originally built in the 1920s, it was starting to show its age. Nonetheless, it was a comfortable place. We waited in the lobby as the other attendees arrived in droves. Half an hour passed, and enough people had finally arrived.</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/motionlab-berlin.jpg\" alt=\"Interior of MotionLab.Berlin makerspace.\"></p>\n<p>With enough people, we set off to the nearby makerspace called <a href=\"https://motionlab.berlin\">MotionLab.Berlin</a>. It was nearby and had plenty of space for us all. The standout feature was the double-decker bus, converted into a meeting room and office space. We all split into teams, and I formed a team of four.</p>\n<p>The premise of this hackathon revolved around household objects: each team is given a random item and has to build a project out of it. Our item was a plastic piece of broccoli. At first, we brainstormed potential ideas on a whiteboard. One of our team members brought some hardware, and the NFC tag reader caught our eye. With that, we formed a plan.</p>\n<p>The game focused on growing your broccoli faster than your opponent could. Every couple of seconds, either a raincloud or the sun would appear at the top of the screen. Both players had a corresponding NFC tag for each event, and the first to scan their tag boosted their broccoli&#39;s growth. The game ended once all 12 ingame-months had passed, and each player&#39;s score was saved to their mobile app. We started the implementation on the first day, finished it on the second, and polished it on the third.</p>\n<p>It quickly turned dark outside, so everybody headed back to the hotel. The spreadsheet used for room allocation had broken and took the organisers until midnight to sort out everyone&#39;s room numbers. On the flip side, I enjoyed the free ice cream they handed out. After a small room mix-up, my room was finally arranged and I got some much-needed rest.</p>\n<h2>25th July - Day Two</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/east-side-gallery.jpg\" alt=\"Group of Hack Club Horizons attendees walking past the East Side Gallery, Berlin.\"></p>\n<p>Day 2 opened with us exploring the city of Berlin. Several people split off from the main group, but I decided to stay with the majority. We visited the <a href=\"https://www.eastsidemall.de/\">East Side Mall</a>, where we bought snacks from Aldi. My personal favourites were vanilla waffles. A single pack contained over 40 waffles, and they each tasted amazing. I also found a nearby bookstore and got myself a nice fridge magnet of a penguin with balloons. Next, we walked alongside the <a href=\"https://en.wikipedia.org/wiki/East_Side_Gallery\">East Side Gallery</a> - the longest stretch of the Berlin Wall still standing. It had plenty of beautiful art made after the Berlin Wall fell.</p>\n<p>Having finished sightseeing for the day, we headed back to the venue to continue work on our projects. I completed a backend for the game using Python and Flask, whereas other members built the MicroPython NFC reader code, the React website, and the Flutter app. We had a functional, albeit clunky, game.</p>\n<p>During the afternoon, the organisers announced there would be lightning talks running. What separates a lightning talk from an ordinary talk is you only have two minutes to explain your topic. Some organisers had talks, but they opened it up to the attendees to join in too. I thought I might as well do one all about Search Engine Optimisation for personal websites. It is an area I have plenty of knowledge on, given the research I&#39;ve completed whilst making this site. Over the course of an hour, I created a slideshow and then presented it to the 80 people there. It was received well, and I was approached for some feedback on someone&#39;s personal website afterwards.</p>\n<p>Each attendee was given some very nice Korean takeaway for dinner, which went down a treat. We then headed back to the hotel for the night. My team stayed up late as we polished the game. I tried to complete an online check-in for my flight back home, but the system gave an error. After an hour with customer support, the conclusion was to just check-in at the airport kiosk. I called it for the night and went to get some rest.</p>\n<h2>26th July - Day Three</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/cakes-breakfast.jpg\" alt=\"An assortment of cakes for breakfast\"></p>\n<p>On the final day, I ensured to pack my bags early. We all travelled from the hotel to the venue, although on an empty stomach. When we arrived, we were soon met with an assortment of cakes from a local bakery for breakfast. Although unconventional, they were high-quality and tasted divine.</p>\n<p>We didn&#39;t have long until our projects needed presentation, so my team scrambled to apply the finishing touches. Our game was finally ready just in time!</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/broccoli-hack-club-horizons-europa.jpg\" alt=\"A person playing the broccoli game at Hack Club Horizons Europa\"></p>\n<p>All teams had their projects running on their laptops during the presentation, and you walked around to see them all. Since our project involved hardware, of course we encountered a couple of technical difficulties. They were relatively minor and easy to fix. Most importantly, the other attendees all seemed to really enjoy the game.</p>\n<p>Everyone else&#39;s projects were great too. Some key highlights were: a camera that used motion tracking so you could play Doom using a paper keyboard, a talking hamster therapist you could call on your phone, and a cooperative puzzle game where all players are computer mice. Everybody managed to create really unique projects out of their random items.</p>\n<p>Once everyone finished voting for their favourites, the scores were tallied. Although our team didn&#39;t win, I still greatly enjoyed the process of making the project. The organisers presented the closing ceremony and marked the end of Horizons Europa.</p>\n<p>I still had a couple of hours until my flight was due to set off. Me and two others asked a German attendee what their favourite restaurant was in all of Berlin. They gave us the address of a sushi place roughly 40 minutes away from the venue, and we set off to go find it. We accidentally held up a full bus whilst figuring out how to buy a ticket, but aside from that it was smooth sailing.</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-horizons/sushi-jasmin-restaurant.jpg\" alt=\"Sushi from Jasmin Restaurant\"></p>\n<p>We arrived at the location - <a href=\"https://jasminrestaurant.de/\">Jasmin</a>, a Vietnamese restaurant. The <em>Celebration</em> sushi mix caught our eyes, so we ordered it and a handful of starters. When they came, we easily saw why the German guy recommended it so highly. It&#39;s hard to put into words how nice it was. If you&#39;re ever in Berlin, you need to try it. Out of curiosity, I checked the kid&#39;s menu too and found dishes such as the <em>I&#39;m not hungry</em> and the <em>I want to go to Mc Donald&#39;s</em>. The place has a great sense of humour.</p>\n<p>The journey back to the airport was slow but easy. We meandered through various train networks and even boarded a double-decker train! I chatted with the help desk at the airport, and it turned out they just wanted to stow my suitcase. The overhead lockers were overbooked and they needed some extra space. When passing through security, a guard asked about the talking hamster plush we were carrying. We turned it on, and its chattering attracted attention from plenty of passers-by. The guard found it very funny and let us continue.</p>\n<p>In the duty-free shops, I bought a fridge magnet of <a href=\"https://en.wikipedia.org/wiki/Ampelm%C3%A4nnchen\">&quot;Ampelmann&quot;</a> - the little pedestrians on German traffic lights. The queue for leaving the EU was far longer than expected, and I almost missed the plane! Thankfully, I made it with a few minutes to spare. There was some nice conversation with the passenger next to me during the flight, but I was relieved to make it back home to my own bed.</p>\n<h2>Overall</h2>\n<p>Horizons Europa was really fun! I&#39;m endeavouring to stay in touch with the people I met there. I also enjoyed learning more about Flask, and I feel like a much more confident speaker after the lightning talk. It took a few days to wear off the sleep defecit, but it was all worth it.</p>\n<p>You can certainly count on me doing more hackathons in the future.</p>\n","date_published":"2026-07-30T00:00:00.000Z","tags":["hack-club","hackathon"]},{"id":"https://hawksley.dev/blog/json-ld-explained-for-personal-websites","url":"https://hawksley.dev/blog/json-ld-explained-for-personal-websites","title":"JSON-LD Explained for Personal Websites","summary":"Everything you need to know about JSON-LD and how to implement it.","content_text":"Everything you need to know about JSON-LD and how to implement it.","content_html":"<p>JSON-LD, also known as JSON Linked Data, is a format for adding structured data to webpages. It can aid web crawlers in understanding the semantic structure of your site, qualifying you for richer link previews, and even potentially improving your search ranking.</p>\n<p>It&#39;s been 4 months since my first post where I described building this site, and Wakatime estimates I&#39;ve spent ~100 hours coding now, not including time spent researching and testing. Since then, this site has been receiving plenty of polish, including the addition of JSON-LD on each page.</p>\n<h2>JSON-LD Fundamentals</h2>\n<p>To add JSON-LD to a page, add the following somewhere in your <code>&lt;head&gt;</code> section:</p>\n<pre><code class=\"language-json\">&lt;script type=&quot;application/ld+json&quot;&gt;\n  {\n    &quot;@context&quot;: &quot;https://schema.org&quot;,\n    &quot;@graph&quot;: [\n      {\n        &quot;@type&quot;: &quot;WebSite&quot;,\n        &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;,\n        &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n        &quot;name&quot;: &quot;Ethan Hawksley&quot;\n      },\n      // Insert more nodes here.\n    ]\n  }\n&lt;/script&gt;\n</code></pre>\n<p>Let’s break down what each part does.</p>\n<pre><code class=\"language-json\">&lt;script type=&quot;application/ld+json&quot;&gt;\n</code></pre>\n<p>This declares a new script with MIME type <code>application/ld+json</code>. Since it has this type specified, the browser&#39;s JS engine won&#39;t run it. Specialised crawlers like Googlebot look out for these elements and parse the contents.</p>\n<pre><code class=\"language-json\">{\n  &quot;@context&quot;: &quot;https://schema.org&quot;\n}\n</code></pre>\n<p>Here, a JSON object is initialised and the property <code>@context</code> is set to <code>https://schema.org</code>. In JSON-LD, the structure of data is determined by assigning the appropriate context. Web crawlers are standardised on <a href=\"https://schema.org\">Schema.org</a>, which defines all the valid key-value pairs for the JSON.</p>\n<p>Now that we&#39;ve defined the schema our JSON-LD is following, we can describe our webpage!</p>\n<pre><code class=\"language-json\">{\n  &quot;@graph&quot;: [\n    {\n      &quot;@type&quot;: &quot;WebSite&quot;,\n      &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;,\n      &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n      &quot;name&quot;: &quot;Ethan Hawksley&quot;\n    }\n    // Insert more nodes here.\n  ]\n}\n</code></pre>\n<p>A JSON-LD document can be thought of as a labelled, directed graph, stored under <code>@graph</code>. The graph contains multiple nodes, connected to each other with directed arcs.</p>\n<p>Nodes have:</p>\n<ul>\n<li><code>@type</code> - Describes what the node is, e.g. <code>WebSite</code> or <code>SoftwareApplication</code></li>\n<li><code>@id</code> - A unique identifier for the node, typically a URL with a unique hash value at the end</li>\n<li>Properties - Key/Value pairs that describe the attributes of the node</li>\n</ul>\n<p>In the example above, the type is <code>WebSite</code>, the ID is <code>https://hawksley.dev#website</code>, and it has two properties, <code>url</code> and <code>name</code>.</p>\n<p>Web crawlers can merge the properties of a node across multiple pages, as long as they share an ID. However, scrapers that only read one page - such as LLMs - will not merge the properties. When JSON-LD is reused across pages, striking this balance is important to keep in mind. It is best practice for the ID to be a URL followed by a hash, such as <code>#website</code>, that uniquely identifies the node.</p>\n<p>Although the Schema.org context defines many types of nodes, this guide will only be covering nodes that have noticeable SEO impact. If you&#39;re interested in more, look up the semantic web - it&#39;s a fun rabbit hole.</p>\n<p>Let&#39;s move on to which nodes each page on our site should include. For each type, I&#39;ve included the JSON-LD from this site, so you can copy-paste and edit it to fit your own.</p>\n<h2>WebSite</h2>\n<p>You&#39;ve seen an extract of <code>WebSite</code> earlier! Now here&#39;s the full version:</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;WebSite&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n  &quot;name&quot;: &quot;Ethan Hawksley&quot;,\n  &quot;alternateName&quot;: [&quot;hawksley.dev&quot;, &quot;Hawksley&quot;],\n  &quot;description&quot;: &quot;The personal site and blog of Ethan Hawksley, a Computer Science student in the UK with a focus on systems programming and cybersecurity.&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;publisher&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;image&quot;: {\n    &quot;@type&quot;: &quot;ImageObject&quot;,\n    &quot;url&quot;: &quot;https://hawksley.dev/hawksley-dev-logo.png&quot;,\n    &quot;name&quot;: &quot;Logo of hawksley.dev&quot;,\n    &quot;caption&quot;: &quot;The official EH monogram logo for hawksley.dev&quot;,\n    &quot;width&quot;: 1024,\n    &quot;height&quot;: 1024\n  }\n}\n</code></pre>\n<p><code>WebSite</code> explains the metadata about the site. It gives crawlers hints on how to display your site.</p>\n<p><img src=\"https://hawksley.dev/blog/json-ld-explained-for-personal-websites/ethan-hawksley-sitename.png\" alt=\"Google search results showing hawksley.dev, with the site&#39;s name &quot;Ethan Hawksley&quot; highlighted\"></p>\n<p>Here, you can see that Google has interpreted the name field as representative of the domain and is labelling the result appropriately.</p>\n<p>Although <code>WebSite</code> applies to every page, you don&#39;t need to include the full version of it on every page. The root page of the domain should be fully detailed, but it is perfectly acceptable for other pages to have a slimmed-down version:</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;WebSite&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n  &quot;name&quot;: &quot;Ethan Hawksley&quot;\n}\n</code></pre>\n<p>This gives sufficient context to single-page crawlers so they correctly name the site, but they don&#39;t need the full details.</p>\n<h2>WebPage</h2>\n<p><code>WebPage</code> describes the current page, but it&#39;s important to distinguish it from other types like <code>BlogPosting</code> (covered later). <code>WebPage</code> represents the physical page itself, the HTML. It contains the content of the page.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;WebPage&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev/blog/hack-club-campfire#webpage&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev/blog/hack-club-campfire&quot;,\n  &quot;isPartOf&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;\n  },\n  &quot;name&quot;: &quot;Winning the Hack Club Campfire Hackathon&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;mainEntity&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/blog/hack-club-campfire#blogposting&quot;\n  },\n  &quot;breadcrumb&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/blog/hack-club-campfire#breadcrumb&quot;\n  }\n}\n</code></pre>\n<p>There are more specific subtypes of <code>WebPage</code>. In this post, I&#39;ll cover <code>ProfilePage</code> and <code>CollectionPage</code>. You can find less common ones at the bottom of <a href=\"https://schema.org/WebPage\">Schema.org&#39;s definition for WebPage</a>.</p>\n<h2>Person</h2>\n<p>Another node that every page on a personal website should have is <code>Person</code>. It describes who you are, which Google uses as part of their content quality metric. Increasingly, LLM crawlers are also using it to decide who to cite in their answers.</p>\n<p>Unlike <code>WebSite</code>, it is important enough context that you should include it on all of your site&#39;s pages.</p>\n<details>\n  <summary>Warning - Quite Long!</summary><pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;Person&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n  &quot;name&quot;: &quot;Ethan Hawksley&quot;,\n  &quot;givenName&quot;: &quot;Ethan&quot;,\n  &quot;familyName&quot;: &quot;Hawksley&quot;,\n  &quot;description&quot;: &quot;Long Description&quot;,\n  &quot;disambiguatingDescription&quot;: &quot;Shorter Description&quot;,\n  &quot;jobTitle&quot;: &quot;Computer Science Student&quot;,\n  &quot;knowsLanguage&quot;: &quot;en&quot;,\n  &quot;knowsAbout&quot;: [\n    // Keywords\n  ],\n  &quot;nationality&quot;: {\n    &quot;@type&quot;: &quot;Country&quot;,\n    &quot;name&quot;: &quot;United Kingdom&quot;\n  },\n  &quot;homeLocation&quot;: {\n    &quot;@type&quot;: &quot;Place&quot;,\n    &quot;address&quot;: {\n      &quot;@type&quot;: &quot;PostalAddress&quot;,\n      &quot;addressLocality&quot;: &quot;Warwick&quot;,\n      &quot;addressRegion&quot;: &quot;Warwickshire&quot;,\n      &quot;addressCountry&quot;: &quot;GB&quot;\n    }\n  },\n  &quot;affiliation&quot;: {\n    &quot;@type&quot;: &quot;CollegeOrUniversity&quot;,\n    &quot;url&quot;: &quot;https://warwick.ac.uk&quot;,\n    &quot;name&quot;: &quot;University of Warwick&quot;,\n    &quot;sameAs&quot;: [\n      &quot;https://www.wikidata.org/wiki/Q865528&quot;,\n      &quot;https://en.wikipedia.org/wiki/University_of_Warwick&quot;\n    ]\n  },\n  &quot;alumniOf&quot;: [\n    {\n      &quot;@type&quot;: &quot;HighSchool&quot;,\n      &quot;url&quot;: &quot;https://www.alcestergs.co.uk&quot;,\n      &quot;name&quot;: &quot;Alcester Grammar School&quot;,\n      &quot;sameAs&quot;: [\n        &quot;https://www.wikidata.org/wiki/Q4713005&quot;,\n        &quot;https://en.wikipedia.org/wiki/Alcester_Grammar_School&quot;\n      ]\n    },\n    {\n      &quot;@type&quot;: &quot;HighSchool&quot;,\n      &quot;url&quot;: &quot;https://www.brookeweston.org&quot;,\n      &quot;name&quot;: &quot;Brooke Weston Academy&quot;,\n      &quot;sameAs&quot;: [\n        &quot;https://www.wikidata.org/wiki/Q4974495&quot;,\n        &quot;https://en.wikipedia.org/wiki/Brooke_Weston_Academy&quot;\n      ]\n    }\n  ],\n  &quot;image&quot;: {\n    &quot;@type&quot;: &quot;ImageObject&quot;,\n    &quot;url&quot;: &quot;https://hawksley.dev/ethan-hawksley.jpg&quot;,\n    &quot;name&quot;: &quot;Ethan Hawksley&quot;,\n    &quot;caption&quot;: &quot;Headshot of Ethan Hawksley, Computer Science student&quot;,\n    &quot;width&quot;: 1536,\n    &quot;height&quot;: 1536\n  },\n  &quot;sameAs&quot;: [\n    &quot;https://github.com/ethanhawksley&quot;,\n    &quot;https://www.linkedin.com/in/ethanhawksley&quot;,\n    &quot;https://lobste.rs/~ethanhawksley&quot;,\n    &quot;https://news.ycombinator.com/user?id=ethanhawksley&quot;\n    // etc. etc.\n  ]\n}\n</code></pre>\n</details><p>Phew! There&#39;s plenty of properties for <code>Person</code>. I find that it helps to be more descriptive, rather than less, when it comes to filling it out. Let’s look at the most important properties:</p>\n<ul>\n<li><code>url</code> - Points to your root page, anchoring the node.</li>\n<li><code>name</code>, <code>givenName</code>, <code>familyName</code> - Clearly describes your name.</li>\n<li><code>image</code> - Preferably a photo of you, or a logo you are affiliated with. Connects you to a canonical image of you.</li>\n<li><code>sameAs</code> - Immensely useful for disambiguation, especially if you have a common name. It cleanly informs crawlers what your other profiles are, letting them build a knowledge graph representation of you across multiple pages. At the time of writing, <a href=\"https://www.google.com/search?kgmid=/g/11m62cgdtf\">/g/11m62cgdtf</a> is my Google knowledge graph ID.</li>\n</ul>\n<p>The other properties of <code>Person</code> are useful for adding more detail, but aren&#39;t strictly necessary. You can trim them if you wish with only minor impact.</p>\n<h2>ProfilePage</h2>\n<p>A <code>ProfilePage</code>, as you may expect, describes a page on the site about a person. For instance, I use this node on my home page, as that&#39;s where I talk about myself. On your site, putting it on an about page could be more appropriate.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;ProfilePage&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev#webpage&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev&quot;,\n  &quot;isPartOf&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;\n  },\n  &quot;name&quot;: &quot;About Ethan Hawksley&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;dateCreated&quot;: &quot;2024-09-10T00:00:00.000Z&quot;,\n  &quot;dateModified&quot;: &quot;2026-05-17T00:00:00.000Z&quot;,\n  &quot;mainEntity&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  }\n}\n</code></pre>\n<p>It&#39;s important to use <code>isPartOf</code> to link it to your broader <code>WebSite</code> node, to create a relationship between the two nodes. The same applies for <code>mainEntity</code>, it lets crawlers know who the page is about. Including <code>dateCreated</code> and <code>dateModified</code> is a good freshness signal for crawlers, but if your site doesn&#39;t have them readily available, don&#39;t worry too much about it.</p>\n<h2>SoftwareApplication</h2>\n<p>If you are showcasing any software on your page, it&#39;s a good idea to include a <code>SoftwareApplication</code> node to describe the metadata about it.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;SoftwareApplication&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev#app-yt-play&quot;,\n  &quot;url&quot;: &quot;https://crates.io/crates/yt-play&quot;,\n  &quot;name&quot;: &quot;yt-play&quot;,\n  &quot;description&quot;: &quot;A CLI utility written in Rust that synchronises YouTube playlists to local directories.&quot;,\n  &quot;applicationCategory&quot;: &quot;MultimediaApplication&quot;,\n  &quot;creator&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;sameAs&quot;: [&quot;https://github.com/ethanhawksley/yt-play&quot;]\n}\n</code></pre>\n<p>If you want to be more specific than <code>SoftwareApplication</code>, other valid types for this node are <code>MobileApplication</code>, <code>WebApplication</code>, and <code>VideoGame</code>.</p>\n<p>The <code>url</code> property should be a link to where the project is deployed, e.g. crates.io. <code>sameAs</code> is for any other pages associated with the project, like its source code repository.</p>\n<p>There are lots of valid values for <code>applicationCategory</code>, you can find a list on <a href=\"https://developers.google.com/search/docs/appearance/structured-data/software-app\">Google&#39;s definition for SoftwareApplication</a>.</p>\n<h2>BreadcrumbList</h2>\n<p><code>BreadcrumbList</code> is widely useful and should be included on all pages aside from the root page. It is used to describe the categorisation of a page, which isn&#39;t necessarily the actual path to the page.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;BreadcrumbList&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev/blog/hack-club-shipwrecked#breadcrumb&quot;,\n  &quot;itemListElement&quot;: [\n    {\n      &quot;@type&quot;: &quot;ListItem&quot;,\n      &quot;item&quot;: &quot;https://hawksley.dev&quot;,\n      &quot;position&quot;: 1,\n      &quot;name&quot;: &quot;Home&quot;\n    },\n    {\n      &quot;@type&quot;: &quot;ListItem&quot;,\n      &quot;item&quot;: &quot;https://hawksley.dev/blog&quot;,\n      &quot;position&quot;: 2,\n      &quot;name&quot;: &quot;Blog&quot;\n    },\n    {\n      &quot;@type&quot;: &quot;ListItem&quot;,\n      &quot;item&quot;: &quot;https://hawksley.dev/blog/hack-club-shipwrecked&quot;,\n      &quot;position&quot;: 3,\n      &quot;name&quot;: &quot;The Shipwrecked Hackathon by Hack Club&quot;\n    }\n  ]\n}\n</code></pre>\n<p><code>BreadcrumbList</code> describes the path of a page. By including one, you can control how search engines represent the path of a specific page.</p>\n<p><img src=\"https://hawksley.dev/blog/json-ld-explained-for-personal-websites/hawksley-dev-breadcrumbs.png\" alt=\"Google search results for The Shipwrecked Hackathon by Hack Club, with the breadcrumbs highlighted\"></p>\n<p>Here, the search result for my blog post contains the path <code>https://hawksley.dev › Blog</code>. If your site already uses short paths, this node is a minor gain and can be omitted. However, if your paths are longer, <code>BreadcrumbList</code> is useful for shortening them.</p>\n<h2>CollectionPage</h2>\n<p>The <code>CollectionPage</code> node is a subtype of <code>WebPage</code>, usable for pages that primarily contain lists. For example, my /elsewhere/ page lists all my other profiles, and /blog/ lists all my blog posts.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;CollectionPage&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev/elsewhere#webpage&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev/elsewhere&quot;,\n  &quot;isPartOf&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;\n  },\n  &quot;name&quot;: &quot;Elsewhere&quot;,\n  &quot;description&quot;: &quot;Online profiles of Ethan Hawksley, a Computer Science student in the UK. Links to his development, social media, blogging, and security accounts.&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;about&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;breadcrumb&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/elsewhere#breadcrumb&quot;\n  }\n}\n</code></pre>\n<p>You&#39;ve met most of these properties already, so they are largely self-explanatory. Make sure you link <code>breadcrumb</code> to the correct <code>BreadcrumbList</code>! It needs to be the one on the current page for it to make any sense.</p>\n<h2>Blog</h2>\n<p>You should add the <code>Blog</code> node to your blog&#39;s index or home page. It acts as a stepping stone between your <code>WebSite</code> and the individual blog posts you publish.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;Blog&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev/blog#blog&quot;,\n  &quot;isPartOf&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#website&quot;\n  },\n  &quot;mainEntityOfPage&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/blog#webpage&quot;\n  },\n  &quot;name&quot;: &quot;Ethan Hawksley&#39;s Blog&quot;,\n  &quot;description&quot;: &quot;A blog by Ethan Hawksley, a Computer Science student in the UK. Articles on systems programming, cybersecurity, and whatever else grabs my attention.&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;dateModified&quot;: &quot;2026-05-17T00:00:00.000Z&quot;,\n  &quot;publisher&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;license&quot;: &quot;https://creativecommons.org/licenses/by/4.0/&quot;\n}\n</code></pre>\n<p><code>dateModified</code> is a good freshness signal, but if you don&#39;t have it handy, don&#39;t worry. Including <code>license</code> lets crawlers know under what circumstances they can use your prose.</p>\n<p><img src=\"https://hawksley.dev/blog/json-ld-explained-for-personal-websites/jsonld-publisher-organization-person.png\" alt=\"Definition of publisher property, where available types include Organization and Person\"></p>\n<p>If you&#39;ve done prior research into JSON-LD, you may be surprised that the <code>publisher</code> property is set to be a <code>Person</code>, not an <code>Organization</code>. Although it used to require one, Google&#39;s documentation has since been relaxed and <code>Person</code> is entirely valid too, and arguably more accurate for a personal website.</p>\n<h2>BlogPosting</h2>\n<p>The last node we&#39;ll be covering is <code>BlogPosting</code>. It should be included on all published blog posts, providing added information to crawlers so they can more accurately represent them, including both more accurate placement and richer details in search results.</p>\n<pre><code class=\"language-json\">{\n  &quot;@type&quot;: &quot;BlogPosting&quot;,\n  &quot;@id&quot;: &quot;https://hawksley.dev/blog/need-for-post-quantum-cryptography#blogposting&quot;,\n  &quot;url&quot;: &quot;https://hawksley.dev/blog/need-for-post-quantum-cryptography&quot;,\n  &quot;mainEntityOfPage&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/blog/need-for-post-quantum-cryptography#webpage&quot;\n  },\n  &quot;isPartOf&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev/blog#blog&quot;\n  },\n  &quot;headline&quot;: &quot;The Need for Post-Quantum Cryptography&quot;,\n  &quot;description&quot;: &quot;Quantum computers are closer to breaking RSA and ECC than we thought. Learn what post-quantum cryptography is and how to start migrating.&quot;,\n  &quot;articleSection&quot;: &quot;cybersecurity&quot;,\n  &quot;keywords&quot;: &quot;cybersecurity, quantum&quot;,\n  &quot;inLanguage&quot;: &quot;en&quot;,\n  &quot;datePublished&quot;: &quot;2026-04-13T00:00:00.000Z&quot;,\n  &quot;dateModified&quot;: &quot;2026-04-17T00:00:00.000Z&quot;,\n  &quot;author&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;publisher&quot;: {\n    &quot;@id&quot;: &quot;https://hawksley.dev#person&quot;\n  },\n  &quot;image&quot;: {\n    &quot;@type&quot;: &quot;ImageObject&quot;,\n    &quot;url&quot;: &quot;https://hawksley.dev/og/blog/need-for-post-quantum-cryptography.jpg&quot;,\n    &quot;width&quot;: 1200,\n    &quot;height&quot;: 630\n  },\n  &quot;license&quot;: &quot;https://creativecommons.org/licenses/by/4.0/&quot;\n}\n</code></pre>\n<p>Since this is a personal site, it is alright for <code>author</code> and <code>publisher</code> to both point towards the same <code>Person</code> node. The <code>image</code> property should mirror the OG image that the post already uses for link previews.</p>\n<h2>Conclusion</h2>\n<p>Congrats, that&#39;s all the JSON-LD a personal site needs! I&#39;ve structured this post to make it as easy as possible for you to copy-paste and implement into your own personal site. Even if you run a static site without a build step, you can still benefit from adding at a minimum <code>WebSite</code>, <code>ProfilePage</code>, and <code>Person</code> to the root page of your site.</p>\n<p>If you have any questions, you can get in touch via email, and I&#39;ll do my best to help.</p>\n","date_published":"2026-06-21T00:00:00.000Z","tags":["seo","web"]},{"id":"https://hawksley.dev/blog/supply-chain-attacks","url":"https://hawksley.dev/blog/supply-chain-attacks","title":"Mitigating Ongoing Registry Supply Chain Attacks","summary":"They are only becoming more frequent and more damaging.","content_text":"They are only becoming more frequent and more damaging.","content_html":"<p>Supply chain attacks are a growing threat to software registries.</p>\n<p>One of the first major typosquatting attacks on npm took place in 2017 when the user &quot;hacktask&quot; <a href=\"https://blog.npmjs.org/post/163723642530/crossenv-malware-on-the-npm-registry\">published a series of packages</a> with similar names to popular libraries (e.g. crossenv vs cross-env), misleading people into installing them.</p>\n<p>Then the first time an official package was compromised was in 2018, when the account of an <code>eslint-scope</code> maintainer <a href=\"https://eslint.org/blog/2018/07/postmortem-for-malicious-package-publishes/\">was hijacked</a> and used to publish malicious packages. These packages would exfiltrate the contents of developers&#39; <code>.npmrc</code> files, often including their keys used for publishing new npm packages. This marked a turning point when attackers began harvesting credentials at scale, to fuel further attacks.</p>\n<p>The recent attack - named <a href=\"https://www.akamai.com/blog/security-research/mini-shai-hulud-worm-returns-goes-public\">Mini Shai-Hulud</a> as a homage to the Dune sandworms - is a symptom of how broken the security of these registries is. It scans your system for API keys, creates public GitHub repositories containing said API keys, and abuses credentials to upload itself to other libraries, spreading its influence.</p>\n<p>Software developers are large targets for malicious actors due to their unique position as both users and distributors. They can easily be compromised by a single library and spread the infection rapidly to all downstream users. Paired with keys to expensive cloud billing accounts and browser token-stealers, it&#39;s no wonder developers face these attacks so often.</p>\n<p>Not all registries are at equal risk, npm is the worst offender by far. A recent report shows it contained <a href=\"https://www.reversinglabs.com/blog/sscs-report-2026-takeaways\">90% of all detected malware</a> across a collection of registries. PyPI and crates.io both contain some malware, but their scale is dwarfed.</p>\n<h2>Mitigations</h2>\n<p>Despite the dangers associated with supply chain attacks, they can be mitigated. The easiest method is by setting a minimum-age on new packages. Most malicious packages from hijacked libraries last no longer than a few hours, so by setting an age-requirement of a week, the vast majority of threats can be foiled.</p>\n<p>For npm, set the contents of <code>~/.npmrc</code> to:</p>\n<pre><code class=\"language-ini\">min-release-age=7\n</code></pre>\n<p>For pnpm, run the command:</p>\n<pre><code class=\"language-shell\">pnpm config set minimumReleaseAge 10080 --global\n</code></pre>\n<p>For Bun, set the contents of <code>~/.bunfig.toml</code> to:</p>\n<pre><code class=\"language-toml\">[install]\nminimumReleaseAge = 604800\n</code></pre>\n<p>For Yarn, run the command:</p>\n<pre><code class=\"language-shell\">yarn config set --home npmMinimalAgeGate 10080\n</code></pre>\n<p>For uv, set the contents of <code>~/.config/uv/uv.toml</code> to:</p>\n<pre><code class=\"language-toml\">exclude-newer = &quot;7 days&quot;\n</code></pre>\n<p>For pip, set the contents of <code>~/.config/pip/pip.conf</code> to:</p>\n<pre><code class=\"language-ini\">[install]\nuploaded-prior-to = P7D\n</code></pre>\n<p>Cargo has a minimum age feature as an RFC, but as of the time of writing, it is not implemented in the client yet. Go has no such plans, nor do Maven or Gradle.</p>\n<p>This mitigation is not without drawbacks. It&#39;s worth considering that delaying such installations by a week also delays any critical security patches, unless otherwise overridden. It&#39;s worth weighing up whether it is worth the trade-off.</p>\n<h2>Other Considerations</h2>\n<p>Aside from a minimum-age, there are a few other key changes worth implementing.</p>\n<p>Firstly, ensure you have 2FA set up for all accounts that support it. It greatly limits the potential blast radius if you become compromised. I personally recommend <a href=\"https://ente.com/auth/\">Ente Auth</a> - it&#39;s free, cross-platform, and supports optional E2EE cloud backups.</p>\n<p>Secondly, if you are not using <a href=\"https://jonathan.bergknoff.com/journal/always-pin-your-versions/\">pinned versions</a> or <a href=\"https://dev.to/wilsonwangdev/lock-files-and-package-manager-migration-a-practical-risk-analysis-2ejn\">lock-files</a>, you should be. They ensure that installed software versions will only change when explicitly updated. This gives you greater control over how and when you want to update, limiting your exposure to supply chain attacks.</p>\n<p>Lastly, if your language supports <a href=\"https://docs.npmjs.com/cli/v11/using-npm/scripts\">post-install scripts</a>, it&#39;s worth disabling them unless necessary. Especially in the JS ecosystem, post-install scripts are often abused to execute payloads immediately before you have time to react. In most circumstances, the feature is just an unnecessary attack surface.</p>\n<p>Even with these mitigations, no project is immune. However, with enough common sense and precautions, you should be able to keep reasonably safe.</p>\n","date_published":"2026-05-17T00:00:00.000Z","tags":["cybersecurity","packages"]},{"id":"https://hawksley.dev/blog/need-for-post-quantum-cryptography","url":"https://hawksley.dev/blog/need-for-post-quantum-cryptography","title":"The Need for Post-Quantum Cryptography","summary":"Quantum computers are becoming increasingly realistic. We need to be ready.","content_text":"Quantum computers are becoming increasingly realistic. We need to be ready.","content_html":"<p>The threat quantum computers pose to encryption is growing faster than many expected. Historically, the internet has relied on algorithms like <a href=\"https://brilliant.org/wiki/rsa-encryption/\">RSA encryption</a> and <a href=\"https://blog.cloudflare.com/a-relatively-easy-to-understand-primer-on-elliptic-curve-cryptography/\">Elliptic Curve Cryptography (ECC)</a> to exchange encryption keys and verify identities across the web. These algorithms are in danger of being entirely broken by the new capabilities of quantum computers, leaving our communications insecure. Modern breakthroughs in research are bringing us closer to this becoming a reality than we expected.</p>\n<p>Quantum computers can be thought of as computers with fundamentally unique capabilities. Instead of using bits as memory, they use <a href=\"https://www.ibm.com/think/topics/qubit\">qubits</a>. Quantum computers can run specialised &quot;quantum algorithms&quot;, which under certain circumstances can be much faster than conventional algorithms[^1]. RSA and ECC seemed secure when we designed them, but as we research more about quantum computers, we realise that they are not secure at all. To resolve this, we have now designed post-quantum algorithms capable of securing data even after quantum computers are accessible. NIST <a href=\"https://csrc.nist.gov/pubs/fips/203/final\">finalised its post-quantum standards</a> in 2024, introducing new post-quantum algorithms such as ML-KEM to replace the old vulnerable ones.</p>\n<p>Shor&#39;s algorithm - the algorithm responsible for factorising the integers used in RSA encryption - has <a href=\"https://arxiv.org/html/2603.28627v1\">been improved</a> and requires far fewer qubits than before. Google has also made quantum algorithms used for breaking ECC <a href=\"https://arxiv.org/abs/2603.28846\">significantly more efficient</a>. Major tech companies like Google, Microsoft, and IBM are competing to build bigger and faster quantum computers, bringing us closer to these quantum algorithms becoming possible. With these developments, the advent of quantum computing is closer than we had once expected. In response, Google has announced a <a href=\"https://blog.google/innovation-and-ai/technology/safety-security/cryptography-migration-timeline/\">2029 timeline</a> for using post-quantum cryptography, and <a href=\"https://blog.cloudflare.com/post-quantum-roadmap/\">Cloudflare has followed suit</a> with their global network. It won&#39;t be long until other major providers follow.</p>\n<p><img src=\"https://hawksley.dev/blog/need-for-post-quantum-cryptography/cisa-post-quantum.png\" alt=\"Cloudflare Radar showing cisa.gov not supporting post-quantum cryptography\"></p>\n<p>In the course of researching this, I discovered that America&#39;s Cybersecurity and Infrastructure Security Agency surprisingly still doesn&#39;t support post-quantum cryptography. If you want to check some websites yourself, <a href=\"https://radar.cloudflare.com/post-quantum\">Cloudflare offers a free tool</a> where you can enter any URL and check.</p>\n<h2>The Risks</h2>\n<p>Adversaries aren&#39;t waiting, even though experts in the field predict that quantum computers capable of breaking RSA are <a href=\"https://globalriskinstitute.org/publication/quantum-threat-timeline-report-2025b/\">likely to be built in 10-15 years</a>. They are already harvesting huge quantities of our encrypted communications - known as <a href=\"https://www.federalreserve.gov/econres/feds/files/2025093pap.pdf\">Harvest Now, Decrypt Later</a> (HNDL) attacks - knowing they will be able to decrypt them later.</p>\n<p>Adversaries aren&#39;t selective, they are collecting all the data they can get their hands on. Once quantum computers arrive, they&#39;ll decide what&#39;s worth decrypting. The question is simply whether your data will still matter in ten years.</p>\n<p>On a personal level, private messages, account records, <a href=\"https://pmc.ncbi.nlm.nih.gov/articles/PMC12540643/\">medical records</a>, and biometrics are in danger of being exposed. Some sensitive data, like passport pages and credit card details, will likely have expired by then, so there isn&#39;t much risk for those.</p>\n<p>For companies, trade secrets, source code, internal logs, and customer data pose great risk. Exposure of client information could lead to <a href=\"https://gowlingwlg.com/en/insights-resources/articles/2025/data-protection-in-a-quantum-enabled-future\">legal fines</a>, and trade secrets risk losing your competitive advantage.</p>\n<p>With governments, however, the stakes are highest. Foreign intelligence, real identities of agents in the field, military plans, and diplomatic communications are severe threats to <a href=\"https://www.ncsc.gov.uk/blog-post/setting-direction-uk-migration-to-pqc\">national security</a>.</p>\n<p>No matter the scale you operate at, the transition needs to happen before quantum hardware catches up. That window is narrowing.</p>\n<p>[^1]: If you&#39;re interested in more details about how quantum computing works on a fundamental level, I highly recommend the <a href=\"https://quantum.country\">quantum.country</a> essay, which goes into far greater depth.</p>\n","date_published":"2026-04-13T00:00:00.000Z","tags":["cybersecurity","quantum"]},{"id":"https://hawksley.dev/blog/hack-club-campfire","url":"https://hawksley.dev/blog/hack-club-campfire","title":"Winning the Hack Club Campfire Hackathon","summary":"A high-intensity 12-hour collaborative sprint.","content_text":"A high-intensity 12-hour collaborative sprint.","content_html":"<p>As leader of ComSoc at my school, I guide the Year 12 and 13 students and keep on the lookout for any new opportunities we can take part in.</p>\n<p>A few months ago, we practised for the <a href=\"https://olympiad.org.uk/\">British Informatics Olympiad</a>, a challenging set of three questions that required us to design accurate algorithms to solve. Over a short period, I had helped transform a group of enthusiastic Year 12 students into confident programmers capable of breaking down and solving complex problems.</p>\n<p>Whilst looking for new events, I found <a href=\"https://campfire.hackclub.com/\">Campfire</a>, a team-based hackathon by Hack Club, where over the span of 12 hours, you design, create, and present a game, and the best teams receive prizes! Having previously competed in <a href=\"https://daydream.hackclub.com/\">Daydream</a> - a very similar game jam - I knew that this would be a lot of fun. After getting the members of ComSoc to split into teams and sign up, we were off to the races.</p>\n<h2>Preparing for Campfire</h2>\n<p>Nowadays, there&#39;s plenty of choice for how to produce and release a game. The event was restricted to only game engines that could be exported to run on the web. After some thought, I chose to use <a href=\"https://godotengine.org/\">Godot</a> as its beginner-friendly nature makes it the perfect candidate for teaching.</p>\n<p>The members of my society needed some teaching, so after struggling through the pain that is Git credentials on Windows, I had them all set up and installed with the necessary software. After I ran a session on creating a simple platformer to bring them up to speed, I knew they were ready.</p>\n<h2>The Big Day</h2>\n<p>On the car journey over, I learned that the surprise theme was &quot;Beneath the Surface&quot;, which started to give me plenty of ideas.</p>\n<p>Once we all arrived at the location, I met up with my teammates. It took a little brainstorming, but we came up with the idea for our game. Inspired by the incremental games you often see in ads or see others playing, our game was to be based around a cat that goes fishing deeper and deeper into the ocean. Any caught fish could be sold to buy upgrades, allowing for even deeper levels to be reached. We named it &quot;Cat-Fishing Frenzy&quot;.</p>\n<h2>Developing the Game</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-campfire/cat-fishing-frenzy.png\" alt=\"Cat-Fishing Frenzy gameplay\"></p>\n<p>We began the event by first creating the assets we would need. I used <a href=\"https://www.aseprite.org/\">Aseprite</a> to design the pixel art for many of the fish that would swim in the sea.</p>\n<p>Next, we laid the foundations. The main scene was created and populated with the cat, rod, and boat. After adding the fishhook, Ryley discovered a neat way to create the fishing line by just moving a point on a Godot Line2D.</p>\n<pre><code class=\"language-gdscript\">func _process(_delta: float) -&gt; void:\n\tset_point_position(1, to_local(node.global_position))\n</code></pre>\n<p>Ryley and I worked on fish spawning, whilst Sam implemented a currency and shop system. With each of us working on largely different areas of the codebase, Git merge conflicts were easy to solve and very infrequent.</p>\n<p>After not too long, we had created the skeleton of a fully functional game! With plenty of time to spare, we introduced a couple of smaller mechanics, and the game received many balancing tweaks. After some polishing, the time was up, just in time for the pizza that the organisers ordered.</p>\n<p>We presented the game to the other competitors, and then came a voting round. After a suspenseful wait, the results were in, with <a href=\"https://refractfoundation.org/events/campfire\">our game taking first place!</a></p>\n<h2>Retrospective</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-campfire/group-photo.webp\" alt=\"Hack Club Campfire participants\"></p>\n<p>This hackathon was plenty of fun to attend, and winning it was the icing on top. Lots of nice prizes were dished out, but I really enjoyed getting to know the people I was working with better. Programming under strict time constraints was excellent practice for my efficiency and teamwork skills, and I look forward to further opportunities in the future.</p>\n<p>If you&#39;re curious, you can play <a href=\"https://ethanhawksley.itch.io/cat-fishing-frenzy\">Cat-Fishing Frenzy</a> here on itch.io. You&#39;ll need the arrow keys to move the hook, so no mobile compatibility at the time of writing.</p>\n","date_published":"2026-03-03T00:00:00.000Z","tags":["hack-club","hackathon"]},{"id":"https://hawksley.dev/blog/hack-club-shipwrecked","url":"https://hawksley.dev/blog/hack-club-shipwrecked","title":"The Shipwrecked Hackathon by Hack Club","summary":"Shipwrecked was really fun, actually.","content_text":"Shipwrecked was really fun, actually.","content_html":"<p>Last April, I was minding my own business when I received a curious email in my inbox.</p>\n<blockquote>\n<ol>\n<li>Hack Club is renting an island.</li>\n<li>We are hosting a hackathon.</li>\n<li>Anyone who ships 4 projects gets to come. Flight stipends available!</li>\n<li>Dates are August 8th - 11th. Boston, USA.</li>\n</ol>\n</blockquote>\n<p>I&#39;d signed up for <a href=\"https://hackclub.com\">Hack Club</a> mailing lists in the past since they run a variety of coding activities. However, this email particularly stood out to me. I&#39;ve always wanted to visit America, and it seemed like they would be offering me a free flight!</p>\n<p>Following the link led me to a sign-up page where I entered my details to register my interest. I spread the word about this event to the other coders I knew, and soon it reached the 5,000-signup threshold to be given the green light.</p>\n<p>Hack Club is a non-profit organisation that has received some <a href=\"https://eu.burlingtonfreepress.com/story/news/2021/11/26/hack-club-shelburne-vt-grant-elon-musk-teens-coding/8552542002/\">generous donations</a> in the past, but they still can&#39;t afford to give away such a great trip for free. To earn my place at the event, I needed to pass The Bay.</p>\n<h2>The Bay</h2>\n<p>The Bay was an online platform designed by Hack Club specifically for this event. You tracked your time spent coding projects with their WakaTime server <a href=\"https://hackatime.hackclub.com/\">Hackatime</a> and submitted any completed projects for review by the Shipwrecked moderators.</p>\n<p>Approved projects granted you a number of &quot;shells&quot; relative to how many hours were spent creating the project. These shells could be spent on many different items from their shop, from Flipper Zeros to 3D printers. However, the grand prize was a ticket for the Shipwrecked hackathon in Boston. This cost the small sum of 60 hours total. Travel stipends were also available, at a conversion rate of 1 hour coding → $10 grant towards a flight.</p>\n<p>After checking Skyscanner to see return flights would charge an entire £400 (~$550), I checked the clauses for Shipwrecked and saw that if I programmed for 100 hours total, they would fully cover my flight regardless of how expensive.</p>\n<p>Now I had the 100-hour goal in mind, it was time to start programming.</p>\n<h2>Some Notable Projects</h2>\n<h3>Gravify</h3>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/gravify-extension.jpg\" alt=\"Gravify extension in action\"></p>\n<p>I had found an interesting library called <a href=\"https://github.com/bubkoo/html-to-image\">html-to-image</a> that could convert DOM elements into images I could manipulate with JavaScript. While that library was sitting in the back of my mind, I stumbled across the <a href=\"https://brm.io/matter-js/\">Matter.js</a> physics simulation library. The library supported custom PNGs as input to the physics simulation, so I decided to combine these two libraries and recreate something very similar to <a href=\"https://mrdoob.com/projects/chromeexperiments/google-gravity/\">Google Gravity</a>.</p>\n<p>Going through the <a href=\"https://developer.mozilla.org/en-US/\">MDN Web Docs</a> to learn how to build a browser extension was far smoother than I had expected. I was comfortable writing JavaScript, and not much boilerplate was required to hook into the Browser APIs. After not too long, <a href=\"https://github.com/ethanhawksley/gravify-extension\">Gravify</a> was created.</p>\n<h3>Turing Machine Simulator</h3>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/turing-machine-simulator.webp\" alt=\"Turing Machine Simulator\"></p>\n<p>After visits to Bletchley Park and diving into more theoretical computer science, I knew I wanted to build a Turing machine.</p>\n<p>Turing machines are a model for computing first devised by Alan Turing in 1936. They consist of an infinite roll of tape marked out into squares, a &quot;head&quot; that points to a single square at a time, and a set of states and instructions. Each cycle, the Turing machine reads the value of the tape underneath itself, compares it to the corresponding instruction, writes a new symbol onto the tape, and finally either moves left or right along the tape.</p>\n<p>Despite their limited nature, they can provably carry out any calculation and are often used for proving new theorems in computer science. I did some research and couldn&#39;t find a simulator online that I was content with using, so I built my own.</p>\n<p>I was still studying a React course at the time, so I wasn&#39;t fully confident building UI with JSX components. This led to a lot of repetitive code.</p>\n<pre><code class=\"language-js\">const rightOption = document.createElement(&#39;option&#39;);\nrightOption.value = &#39;Right&#39;;\nrightOption.textContent = &#39;Right&#39;;\n// ...\n</code></pre>\n<p>Looking back at it, it is quite brittle and challenging to debug, but I can thank my understanding of JavaScript fundamentals for framework-less code like this.</p>\n<h2>Reaching America</h2>\n<p>As soon as I got the official go-ahead from the organisers, I booked a flight ready to set off for Boston.</p>\n<p>Coming alongside me were two friends who also qualified for the event. They had put in the hours to earn tickets, and we all came over together.</p>\n<p>The flight took a long eight hours, but after not too long we finally reached America.</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/boston-pier.jpg\" alt=\"EDIC Pier\"></p>\n<p>Once we arrived at the airport, we set off to EDIC Pier where we laid our picnic blankets and bought some local food. I got to know the other contestants who were there. Many were American, but surprising numbers were from all across the globe, with one even as far as Vietnam!</p>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/ferry.jpg\" alt=\"Boston Harbour\"></p>\n<p>It didn&#39;t take long until we began to set off towards Cathleen Stone Island. The ride was short but a great opportunity to chat with the other people there and take some photos. By the time we finally reached the island, we had already decided on the structure of our teams.</p>\n<p>Everyone scrambled to claim their beds, but as one of my projects had gone viral, I was given first pick and stayed in the executive suite with the other viral programmers. It was here that I met Daniel, another participant who managed to go viral, and formed a team with him and Oliver.</p>\n<p>By the end of the day, we were all invited down to the pavilion to learn about the first project we would be assigned. This hackathon had three projects that we would make in total, each themed around a certain idea. The first project was to design a method of communication that wouldn&#39;t involve letters or numbers.</p>\n<h2>SymbolNotes</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/symbolnotes.png\" alt=\"SymbolNotes UI\"></p>\n<p>In my team, we decided to create an online peer-to-peer messaging app. Since I had prior experience with the PeerJS library, I suggested that we use that.</p>\n<p>As per hackathon tradition, the code we made was hacky, and the graphics were rough, but we managed to create a working solution. We released the source code for <a href=\"https://github.com/Thesupernile/SymbolNotes\">SymbolNotes</a> onto GitHub so others could examine and learn from it. We made it together over the course of a morning and submitted it onto The Bay.</p>\n<h2>Exploring The Island</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/map.jpg\" alt=\"Map of Cathleen Stone Island\"></p>\n<p>With our project complete, we spent the rest of the day exploring the island as a team. The executive suite and pavilion were both very close to the docks, but there was the entire rest of the island still to investigate.</p>\n<p>Alongside us were some Americans who knew the area much better than we did and told plenty of stories about what it was like living in America. As somebody who&#39;d only set foot outside Europe once before, it was very refreshing to hear such different perspectives.</p>\n<p>Before long, however, it was back to the next challenge.</p>\n<h2>Connect Infinity</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/connect-infinity.png\" alt=\"Connect Infinity gameplay\"></p>\n<p>Our next challenge was to design a creative game that could be played online. We decided to put a spin on the classic game Connect 4 and make it real-time. To prevent it from ending extremely quickly, the game only ends once the entire board is full. Every 4-in-a-row gives a point, most points win.</p>\n<p>Now the team had got used to PeerJS, we decided to use it once again here as it seemed perfectly suited for the task. As a challenge, we designed the code so that lobbies of unlimited size were supported, instead of just groups of two.</p>\n<p>This took more time than the previous project due to its larger scope, but we worked as a team much more efficiently. After a few tireless hours, <a href=\"https://github.com/ethanhawksley/connect-infinity\">Connect Infinity</a> was created just in time to present.</p>\n<p>We took laptops down to the pavilion and set them up running the game by the shore. The other participants all tried out the game, and it was surprisingly well received! We managed to win a limited-edition Shipwrecked shirt, which I still have and wear to this day.</p>\n<h2>Perlin Colours</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/perlin-colours.png\" alt=\"Perlin Colours\"></p>\n<p>Our final task was to create a project that heavily featured a variety of colours. One of our team had the idea to create a Perlin noise generator that could create plenty of colourful gradients. We decided to name it <a href=\"https://github.com/Thesupernile/PerlinColours\">Perlin Colours</a>.</p>\n<p>After some research on the formulas involved and a fair bit of trial and error, we ended up with a functioning noise generator implemented with HTML Canvas. I had dabbled with Canvas before, but this was the first time I had implemented a proper renderer using it.</p>\n<p>We ended up sleeping on the problem, but once we woke up, we were fully refreshed and fixed the final bugs with the implementation. It was very satisfying to have a proper end product.</p>\n<p>The project was presented, and it went down a treat! The prizes were limited-edition laptop stickers that I have proudly stuck onto my laptop ever since.</p>\n<h2>Returning Home</h2>\n<p><img src=\"https://hawksley.dev/blog/hack-club-shipwrecked/boston-airport.jpg\" alt=\"Boston Logan International restaurant\"></p>\n<p>Alas, all good things must come to an end. It was the final day, so I gathered all my belongings and packed them away into a suitcase. My team attended the closing ceremony and said our goodbyes to the people there. There were so many nice people I met, and I&#39;ll cherish the memories made forever.</p>\n<p>I caught an Uber alongside some others also needing a flight back and returned to the airport. It was there our team finally disbanded. One amusing thing at the airport was the Hack Club official plushies were just over the weight limit and were tripping all the security alarms. I got through just fine, but it caused a right mess!</p>\n<p>It was then I also got to see my very first American passport. Having only seen European ones, it was a right culture shock with pages full of landmarks and a giant eagle on the photo page.</p>\n<p>The actual flight was thankfully uneventful. I watched the in-flight entertainment and ate the food, but slowly drifted off to sleep until we landed.</p>\n<h2>Reflections</h2>\n<p>Participating in Shipwrecked is an experience I&#39;ll remember forever, and I thank Hack Club and the organisers for helping set up such a wonderful event. I have still maintained contact with several of the other people there and arrange to meet up with them at future hackathons.</p>\n<p>It was also very enjoyable working in teams. Having spent so much of my time programming solo, it was nice bouncing ideas back and forth to come to informed conclusions.</p>\n<p>I&#39;ve done many other things with Hack Club since, but they are perhaps a story for another blog post...</p>\n<p>One thing you can take for granted is that I&#39;ll absolutely be doing more hackathons in the future!</p>\n","date_published":"2026-02-11T00:00:00.000Z","tags":["hack-club","hackathon"]},{"id":"https://hawksley.dev/blog/building-astro-blog","url":"https://hawksley.dev/blog/building-astro-blog","title":"Building an Astro Blog","summary":"My developer experience trying out the Astro framework and ditching my old site.","content_text":"My developer experience trying out the Astro framework and ditching my old site.","content_html":"<p>I&#39;ve owned the domain name hawksley.dev for a while now, but I&#39;ve never done much with it aside from sending email. Over the weekend, I thought I might as well make good use of it and decided to create a blog.</p>\n<p>In the beginning, this site had a humble home page with some links to GitHub projects. A blog requires much more infrastructure for me to use it effectively. For one, it&#39;d be great if I could just write my posts in Markdown and have them formatted by my project automatically. Having a look at the options available, the first that stood out was <a href=\"https://jekyllrb.com/\">GitHub&#39;s Jekyll</a>. It looked nice and had great integration with GitHub Pages, which I&#39;m hosting with at the time of writing. However, it just felt too rigid. I needed something modern that I felt I could get my hands dirty with.</p>\n<p>Enter Astro.</p>\n<h2>Why Astro</h2>\n<p>In the grand scheme of things, <a href=\"https://astro.build/\">the Astro framework</a> is pretty new at just 5 years old. That hasn&#39;t prevented it from gaining popularity rapidly. It holds performance as a key design principle, anything that can be static will render statically. By default, it ships absolutely no JS to the browser, which felt perfect for my use case. I have no need for advertising or heavy tracking scripts weighing down my site. All I need is a place to write.</p>\n<p>Learning how to work with Astro was completely painless. I created a new GitHub repository and followed along with their very high-quality documentation to create a blog of my own. At the very end of it, I’d created a nice neat blog that loaded instantly and was easy to write for.</p>\n<p>I wasn&#39;t satisfied by using the tutorial&#39;s blog for my site, though, as it felt too cookie-cutter, and so I started again, now with confidence in the framework.</p>\n<h2>The Design Decisions</h2>\n<p>There were some definite design decisions I knew I wanted from the get-go. First-class light mode and dark mode support were a must. Plenty of blogs offered just one or the other, and after a bit of digging, it didn&#39;t seem technically hard to implement at all.</p>\n<p>Another key principle was mobile-first design. Many of my previous projects started with the key assumption that the reader will be on a laptop or desktop, but that often isn&#39;t the case. Mobile devices account for approximately two-thirds of global internet traffic, with the remaining third coming from desktops. I wanted this site to stand the test of time until I inevitably rebuild it again, so it&#39;s only natural I designed it with mobile devices in mind.</p>\n<p>My final decision I knew I wanted was to have excellent performance scores, one of the very reasons that I chose Astro for the job. Components are almost a certainty in modern web design to ensure consistency across pages, but I needed to write components used at compile time, rather than at run time.</p>\n<h2>Actually Programming the Site</h2>\n<p>After creating an experimental fork of my main repository and wiping the directory, I initialised a new blank Astro project with <code>pnpm create astro@latest</code> and scaffolded the directory structure I&#39;d need.</p>\n<pre><code class=\"language-bash\">public // Assets such as robots.txt and favicons.\nsrc\n  assets // Any assets displayed in the actual site.\n  components // Reusable components representing sections of UI.\n  content // Markdown files representing the blog.\n  layouts // The base layout each page uses.\n  pages // The actual pages rendered, combining all parts together.\n</code></pre>\n<p>Development went very smoothly over two days and 15 full hours, with only some minor hiccups along the way.</p>\n<p>Google&#39;s PageSpeed Insights flagged my planned accent colour International Orange <code>#FF4F00</code> as having insufficient contrast in light mode, so I had to split into separate accents for light and dark mode. By using global CSS variables that change depending on the theme, I implemented this change painlessly.</p>\n<p>I also encountered some <abbr title=\"Content Layout Shift\">CLS</abbr> issues surrounding my choice of font. I personally love the look of the <a href=\"https://rsms.me/inter/\">Inter font</a> and included it in my project[^1]. However, although 300 KiB may not sound like a lot, on a slow connection it’s a noticeable delay. After some research I discovered the <a href=\"https://github.com/fonttools/fonttools\">fontTools library</a> and managed to shrink the Inter font to 70 KiB by subsetting the font to only the character sets I need.</p>\n<h2>The Conclusion</h2>\n<p>Astro has been a pleasure to work with, and I can easily see myself using it in future projects now I&#39;ve adjusted to it.</p>\n<p><img src=\"https://hawksley.dev/blog/building-astro-blog/pagespeed-insights.webp\" alt=\"Perfect Google PageSpeed score\"></p>\n<p>A perfect score has got to be one of the most satisfying feelings after doing a site redesign.</p>\n<p>[^1]: I&#39;m now using IBM Plex Sans and IBM Plex Mono, but they are subsetted to 62KiB and 9KiB respectively, so the advice still holds.</p>\n","date_published":"2026-02-04T00:00:00.000Z","tags":["astro","web"]}]}