Shellsharks Blogroll - BlogFlock 2026-07-06T23:56:27.975Z BlogFlock Adepts of 0xCC, destructured, Aaron Parecki, Trail of Bits Blog, fLaMEd, gynvael.coldwind//vx.log (pl), Westenberg, James' Coffee Blog, joelchrono, Evan Boehs, Kev Quirk, cool-as-heck, Posts feed, Sophie Koonin, cmdr-nova@internet:~$, <span>Songs</span> on the Security of Networks, Johnny.Decimal, Werd I/O, Robb Knight, Molly White, Hey, it's Jason!, Terence Eden’s Blog 📝 2026-07-06 21:45: Two of the eggs are starting to hatch and we can hear multiple chicks cheeping... - Kev Quirk https://kevquirk.com/2026-07-06-2145 2026-07-06T20:45:00.000Z <p>Two of the eggs are starting to hatch and we can hear multiple chicks cheeping away. Exciting! 🐣</p> <p><img loading="lazy" src="https://kevquirk.com/content/images/2026-07-06-2145/1000010211.webp" alt="1000010211" /></p> <div class="email-hidden"> <hr /> <p>Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️</p> <p>You can <a href="mailto:19gy@qrk.one?subject=%F0%9F%93%9D%202026-07-06%2021%3A45">reply to this post by email</a>, or <a href="https://kevquirk.com/2026-07-06-2145#comments">leave a comment</a>.</p> </div> Return to the gym, 3DS theming, Mexico lost - W27 - Joel's Log Files https://joelchrono.xyz/blog/w27 2026-07-06T17:30:00.000Z <p>Mexico was knocked out of the World Cup by England. You have no idea how much hope I had for this squad, the quality and resiliance Mexico had shown. Unfortunately we were unable to take advantage of the locality and it only took a couple of mistakes for England to put us in a very bad situation, two goals in just a couple minutes, and a third penalty due to one of the only errors our goalkeeper ever did. Quiñones helped us stay afloat, and a killer penalty kick from Jimenez too, but the game devolved into a battle of trying to get the ball in the area, when it was all filled by the brittish. The weirdest decision for me was taking out the creativity of Mora and the power of Quiñones, and relying on the same play.</p> <p>Even with that, Mexico fought hard, so many attempts, many of them would have gone in, but the goalkeeper did his job very well. And now we lose, but it’s not the same as always for me. I believe in the new generation. Mora has a long way to go, I would have wanted to see more of la Hormiga, the inheritor of Chicharito’s 14. Rafa Marquez is the manager now, I’m hopeful that the Kaiser will do great for the future.</p> <p>Oh right, week notes. Let’s see.</p> <ul> <li> <p>💪 I have returned to the gym! It was a long hiatus full of laziness and overeating, I went up two kilos since the last time I checked, so I decided to go back and try again to take better care of myself. I continue to be annoyed by the need to go at all, it just kinda feels like a trend for me and a bit of a timewaste. I really need to get that bycicle commute going, it has the potential to really change things for the bettter for me.</p> </li> <li> <p>🎮 The <em>Nintendo 2DS XL</em> arrived and it was a bit dissapointing, if I’m honest. While the design is fun, the plastic feels much cheaper and the structural integrity feels lesser than the New 3DS XL. I packaged it up and requested to return it, I only turned it on once and didn’t even try any games on it.</p> </li> <li> <p>🍿 Invited my friend to another movie! This time we arrived earlier and went window shopping for a bit, and also played some card games! We watched <em>Supergirl</em>, and it was honestly kind of weird. All in all, the point was to spend some time together and it was alright.</p> </li> <li>🚲 This weekend’s bike ride was a very functional one! I went a bit later than my usual morning because I wanted to visit a couple of places. I actually carried a backpack this time too! <ul> <li>I headed to a delivery spot to return the <em>Nintendo 2DS XL</em>, I picked up a couple packages as well, then</li> <li>I went to the local game shop to look for any 3DS games, I had no carts and I wanted at least one! I chose the <em>Ocarina of Time</em> remake.</li> <li>I was supposed to go to the bike shop to buy fenders and a rack, but it was late already so it didn’t happen.</li> </ul> </li> <li> <p>🍜 Went to a Japanese restaurant and had some ramen, this was a night out with a few friends, it was really nice! We pretty much only go here when these specific friends are in town, so I always enjoy it.</p> </li> <li> <p>🎨 Spent a couple hours figuring out how to create themes for the Nintendo 3DS, and ended up making two! Of course they are based on some epic locations from <em>Chrono Trigger</em>, I will write a blogpost detailing the process and have already uploaded them to <a href="https://themeplaza.art">Theme Plaza</a>, waiting for approval!</p> </li> <li>📱 I rarely use mainstream social media, but during the World Cup I keep visiting it to see the memes and the euphoria, and now the sadness of losing—and memes asking Haaland to avenge us. Until next time, México.</li> </ul> <p><img src="/assets/img/blogs/2026-07-06-week.webp" alt="Collage of the Week" /></p> <h2 id="gaming">Gaming</h2> <ul> <li> <p><strong>Fire Emblem Awakening</strong> continues to be at the top for my gaming time, the story has picked up quite a bit, but the mechanics alone, and the challenge of each chapter have been formidable. I am currently on chapter 13, and some revelations have been made that caught my eye. I also love my party quite a bit now! I’ve managed to create a pretty decent team but I’m also trying to level up some weaker characters and also level up the support between characters, I love the conversations that happen between them. Looking forward to what this game has next!</p> </li> <li> <p><strong>Fire Emblem The Sacred Stones</strong> was played for a second. I remembered that I have a lot of handhelds, and my Miyoo Mini Flip had this one loaded up, I wanted to try a battle but didn’t even get far enough for that yet. I opened the game, saw the opening sequence, and got busy with something else.</p> </li> <li> <p><strong>Super Smash Bros Ultimate</strong> saw some action! and the best player of my friend group has started to struggle with the younglings, we are getting old, RIP.</p> </li> <li> <p><strong>Worms W.M.D</strong> - We did one battle of this as well, it was a fun time! We kinda forgot how to play Worms but picked things up in the end.</p> </li> </ul> <h2 id="watching">Watching</h2> <ul> <li> <p><strong>Avatar The Last Airbender (Live Action Season 2)</strong> - We are in the Earth Kingdom and they are actually pulling off a couple neat moments in great ways! Like, ways that could have worked on the original series rather well. It still has some very awkward things and bad pacing, but I’m invested a bit.</p> </li> <li> <p><strong>Supergirl (2026)</strong> - Went to the movies with my friend and family to see this one. It was good, but it wasn’t great. It really has a lot to say about society and stuff, but it felt mixed, and the music selection wasn’t that great. Too convoluted, and nobody in my group resonated with it that much. I really really wanted to like it, and I do, it just didn’t quite stick the landing for me.</p> </li> <li> <p><strong>Superman (2025)</strong> - Back at home, I thought I’d give a shot to the first movie in James Gunn’s DC universe. I enjoyed this one! It really questioned the nature of Superman and how it affects the politics of the world. Some moments felt over the top and had that characteristic style James Gunn has. I appreciate that the universe already exists and it doesn’t feel the need to hold our hand. However, the plot was predictable and with almost no surprises. Mr. Terrific was truly terrific though, great guy!</p> </li> <li> <p><strong>Yes Day</strong> - I watched this movie ages ago and only recognized Electra, watching it again, I also recognized Jenna Ortega. What a weird family movie. Entertaining enough but there’s better ways to spend your time.</p> </li> </ul> <h2 id="reading">Reading</h2> <p><strong>Shikimori’s Not Just Cute</strong> is now complete! What a journey, a tiny bit rushed in the ending but still very nice! All in all this manga was a cozy read that had some great moments and hilarious situations. It was not life-changing or anything, but I’m glad it tied things nicely. I actually grew to love all the characters beyond the main couple quite a bit, which is something that got a bit overwhelming with <em>Komi Can’t Communicate</em>. The cast here was smaller but the roles were well defined and they all had a time to shine if only a bit. I will actually miss some of them! I ended up a little melancholic about it due to Mexico’s loss, and I am running out of romance manga to read too, which is a bit sad. Leave recommendations.</p> <p>I didn’t read any of <em>The Expanse</em> at all because of the gaming I did.</p> <h2 id="around-the-web">Around the Web</h2> <h3 id="blog-posts">Blog posts</h3> <ul> <li><a href="https://gabz.blog/posts/a-new-nintendo-3ds-xl">A New Nintendo 3DS XL</a> - Gabz also has a 3DS! Who would have thought I’d inspire him to return to it? I am happy to have caused that in him and a few other people too, very cool.</li> <li><a href="https://bell.bz/im-playing-the-zelda-games-in-timeline-order/">I’m playing the Zelda games in timeline order</a> - Oh look! Andy is playing Zelda, that’s cool. I should try playing through a whole franchise sometime, I guess I’m kind of close with <em>Resident Evil</em>…</li> <li><a href="https://neilzone.co.uk/2026/07/being-excited-still-about-technology-in-2026/">Being excited still about technology in 2026</a> - Neil continues to remind us that technology still has some good things going on. It’s not all doom and gloom!</li> <li><a href="https://pawelgrzybek.com/my-blog-is-not-on-the-at-protocol-standard-site-is-not-for-me">My blog is not on the AT Protocol, standard.site is not for me</a> - This. AT Protocol, to me, is not where it’s at, I added <a href="https://microformats.org/">microformats</a> and that feels much simple and universal to me, even then it feels a bit much, but it’s setup and forgotten and it’s okay!</li> </ul> <h3 id="youtube">YouTube</h3> <ul> <li><a href="https://youtu.be/dvAczAth9ms">How To Make Your Own Custom 3DS Themes / Kame-Editor</a> - This is the tutorial I followed to make my theme, although I had to figure out some things it didn’t explain especially for the image placement on static images (the tutorial is only about scrolling backgrounds).</li> <li><a href="https://youtu.be/qeKJqnlF3UQ">The Reusable Water Bottle Trend is DEAD</a> - Making good purchasing decisions hasn’t been my forte this year, but at least I know I won’t fall for energetic drinks any time soon.</li> <li><a href="https://youtu.be/QiEtHwCTPb0">Why Mexico v. England is already historic</a> - This article from the <em>Search Party</em> channel shared a lot of awesome details on the history of the Azteca Stadium! I recommend giving it a watch as it had a lot of details I didn’t know about at all.</li> <li><a href="https://youtu.be/bue-lSUJr-g">For Real this Time!</a> - Someone seems to be working on a port of <em>Twilight Princess</em> for the Nintendo 3DS. I hope more GameCube games get ported to the handheld some day.</li> <li><a href="https://youtu.be/QLkc_b1L9Ow">Dear Sony: You messed up</a> - It seems like physical discs for Play Station will stop being a thing. This is disaster, but kind of expected. I hope things change some day.</li> </ul> <p>This is day 90 of <a href="https://100daystooffload.com">#100DaysToOffload</a></p> <p> <a href="mailto:me@joelchrono.xyz?subject=Return to the gym, 3DS theming, Mexico lost - W27">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/116874409962412108">Reply on Fediverse</a> </p> Governance can prevent AI from being used to undermine democracy. But only if it has teeth. - Werd I/O 6a4baf9b1f0a8b0001fa5853 2026-07-06T13:37:31.000Z <p>Link: <a href="https://www.techpolicy.press/in-geneva-the-world-can-anchor-ai-governance-in-free-expression/?ref=werd.io"><em>In Geneva, the World Can Anchor AI Governance in Free Expression, by Isabelle Anzabi in Tech Policy Press</em></a></p><p>I like that this is happening, will appreciate the recommendations that arise from it, and know with complete certainty that no major AI vendor will adhere to them unless they are forced:</p><blockquote>&#x201C;The United Nations is convening its <a href="https://www.un.org/global-dialogue-ai-governance/en?ref=werd.io">Global Dialogue on Artificial Intelligence Governance</a> in Geneva July 6-7, and the stakes are high: what rights will anchor the future of how the world governs AI? Generative AI systems mediate, filter, and generate the information we encounter every day. They are, at their core, a technology of expression and access to information. How we govern them will have downstream effects on what people can say, seek, and know. Geneva is where we can get that right.&#x201D;</blockquote><p>A previous UNESCO meeting recommended governance that &#x201C;bars AI systems from being used for social scoring or mass&#x202F;surveillance, and&#x202F; requires member states to ensure that AI actors respect rights in the AI lifecycle&#x201D;. As we all know, that has <em>definitely</em> happened. Both AI vendors and the two countries that predominantly house them, the United States and China, are famously against surveillance and social scoring, and in favor of maintaining rights. Certainly none of them have, for example, used AI with <a href="https://www.npr.org/2026/01/23/nx-s1-5684185/doge-data-social-security-privacy?ref=werd.io">the most sensitive personal information government has access to</a>, or worked with the private sector to <a href="https://theconversation.com/us-government-ramps-up-mass-surveillance-with-help-of-ai-tech-data-brokers-and-your-apps-and-devices-277440?ref=werd.io">create a surveillance apparatus</a> for policing <a href="https://www.404media.co/elite-the-palantir-app-ice-uses-to-find-neighborhoods-to-raid/?ref=werd.io">that includes the broad detainment of immigrants</a>. Neither of them is famous for social scoring &#x2014; in China by government or in the United States <a href="https://hai.stanford.edu/news/how-flawed-data-aggravates-inequality-credit?ref=werd.io">through private enterprise</a>. Job done!</p><p>Sarcasm aside, it&#x2019;s important to have these meetings, and it&#x2019;s important to continue to add pressure to protect rights and prevent abuses of this technology. But we should be clear-eyed about the fact that any recommendations almost certainly won&#x2019;t actually be followed unless major nations that represent real dollars to these vendors enshrine them in law and hold the line when countries like the US apply pressure to undermine them.</p><p>It can&#x2019;t just come down to <em>individual</em> governments. The linked article argues that we should also protect freedom of expression, in particular from government (although I&#x2019;d argue we should also worry about the influence of wealthy private entities). Because these are black box systems, it&#x2019;s vital that we prevent governments (or anyone with power) from opaquely tweaking their answers and outputs to benefit their agendas. They should not be allowed to be opaque systems; full transparency and auditability should be requirements. The danger is that, in the wrong hands, AI can be used to cement centralized, undemocratic power. To prevent this, ideally, organizations like the UN should apply real sanctions to nations that don&#x2019;t obey transparency rules and tweak AI systems in service of undemocratic goals &#x2014; but the UN&#x2019;s history of doing this effectively is not strong.</p><p>Part of the problem is the &#x201C;AI is the information technology of the future&#x201D; framing used here. If you believe that AI is the future of information systems, you are also more likely to believe that your nation will miss out if you don&#x2019;t embrace it completely. (That&#x2019;s been the marketing: if you don&#x2019;t jump into AI, you will be left behind.) But the reality is, of course, far more nuanced. AI will absolutely change industries like software engineering, and has already made an impact there, although it&#x2019;s far from an existential transformation. I&#x2019;m less receptive to the idea that it will transform entire nations. AI vendors need global markets more than global markets need AI. Governments should understand the power they have, and use it.</p> I'm just so bored of AI - Terence Eden’s Blog https://shkspr.mobi/blog/?p=71384 2026-07-06T11:34:18.000Z <p>I'm just so <em>bored</em> of talking about AI. It's like listening to vapers tell me how delicious their flavoured poison is.</p> <p>Did you ever meet someone at university who'd just tried drugs for the first time? Listening to a stoner ramble on about their mystic crystal revelations is amusing for the first five minutes, but quickly gets tiresome. Wow! You got your little computer friend to automate calling your mum? Great job, mate! Can we talk about something interesting now?</p> <p>Just as bad are the people telling me how bad smoking is for my health. Yes, I know! That's why I don't. Yes, I know that hanging out with friends while they smoke is bad for me - but loneliness is probably worse. In fact, you screaming about how every puff on a fag reduces my life expectancy by 37 seconds makes me want to inhale a whole cigar so I can die early enough to exit this tedious conversation.</p> <p>If you want to enhance your consciousness / rot your brain with AI - go ahead. Just stop wanging on about it like you discovered the secret to eternal life.</p> <p>And to the rest of you, stop pissing on people's chips. Haranguing and scolding have never convinced anyone of everything.</p> <p>I can't fucking wait for this bubble to burst so we can go back to chatting shit about literally <em>anything</em> else.</p> <img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=71384&HTTP_REFERER=Atom" alt width="1" height="1" loading="eager"> Built for Exactly One - Kev Quirk https://kevquirk.com/built-for-exactly-one 2026-07-06T11:00:00.000Z <div class="link card"><h2>Built for Exactly One</h2><p class="post-author">by Amit Gawande</p><p>Amit talks about what motivated him to build his custom blogging platform, Jot. It's an interesting read that resonated with me as it aligns with why I created Pure Blog.</p><p><a class="button" target="_blank" href="https://www.amitgawande.com/blog/2026/built-for-exactly-one/">Read post ➡</a></p></div> <hr> <blockquote> <p>A month ago, this website moved to a custom engine that I built myself, one I call Jot. Why did I create it? Because I got tired of almost. Almost the right editor. Almost the right publishing flow. Almost the right feature set.</p> <p><cite>-- Amit Gawande</cite></p> </blockquote> <p>Preach!</p> <p>This is <em>exactly</em> why I started building <a href="https://pureblog.org">Pure Blog</a>, but the difference here is that I decided to publish it for everyone to use. Before doing so, I considered many of the same questions that Amit talks about in his post - I was concerned that the project would morph into a product for <em>everyone</em>, not just me.</p> <p>Ironically, it's been exactly 5 months since I <a href="https://kevquirk.com/introducing-pure-blog">introduced Pure Blog</a> and since then I've done a shit tonne of work to it. But that wasn't driven by the people who use it. It was driven, almost exclusively, by me. Lots of people have contributed to Pure Blog, but there's hasn't been a single feature I've added that I won't get use from.</p> <p>Actually, that's a lie. The <em>only</em> feature I've added that I wouldn't have if I'd kept Pure Blog private is <a href="https://docs.pureblog.org/changelog/#v2.2.0-%E2%80%94-27-march-2026">translations</a>. But I think that's fine, as it's the community who contribute those translations.</p> <p>Anyway, I digress. I'm happy to see other bloggers forging their own path - I'd love to get a look at Jot to see what it does differently to Pure Blog, and if there's anything I could <del>copy</del> improve upon. Maybe one day Amit will release the source code for us to look at, but if he doesn't, I don't blame him.</p> <p>As for my use of Pure Blog - it's <em>by far</em> the best thing I ever did from a blogging perspective. <em>Everything</em> is just how I want it, and in a place that makes sense to me. If others get use from it too (<a href="https://roll.pureblog.org/">and they do</a>) then all the better. But I'll keep developing Pure Blog in a way that makes sense to me.</p> <p>Congrats, Amit. Welcome to the club.</p> <div class="email-hidden"> <hr /> <p>Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️</p> <p>You can <a href="mailto:19gy@qrk.one?subject=Built%20for%20Exactly%20One">reply to this post by email</a>, or <a href="https://kevquirk.com/built-for-exactly-one#comments">leave a comment</a>.</p> </div> IndieWebCamp Nürnberg 2026 - James' Coffee Blog https://jamesg.blog/2026/07/05/indiewebcamp-nurnberg-2026 2026-07-05T00:00:00.000Z <p><figure><picture><img alt="All of the participants of IndieWebCamp Nürnberg, lined up in three rows, one row behind the other. I am in the middle of the back row, wearing my green and grey microformats t-shirt. " loading="lazy" src="https://editor.jamesg.blog/content/images/2026/07/1000px-iwc-nbg-2026-10.jpg" style=" max-width: 130%;"/></picture><div class="alt"><label><input aria-label="Toggle image alt text on screen" type="checkbox"/>ALT</label><div class="content">All of the participants of IndieWebCamp Nürnberg, lined up in three rows, one row behind the other. I am in the middle of the back row, wearing my green and grey microformats t-shirt. </div></div></figure></p><p>Last weekend I attended <a href="https://indieweb.org/2026/Nuremberg">IndieWebCamp in Nürnberg, Germany</a>, hosted at the wonderful <a href="https://tollwerk.de/">Tollwerk</a> offices. The two-day event was part of the larger Nürnberg Digital Festival happening across the city. The IndieWebCamp was held primarily in English. Over 23 people attended, many from Germany and some from further afield (including myself!).</p><p>On the first day, we organised discussion sessions in the BarCamp style, in which new participants are encouraged to propose sessions first, followed by regular participants, and then we all agree on where and when during the day to hold each session. On the second day, we made web pages and shared them!</p><p>Indeed, being a two-day event, <a href="https://indieweb.org/IndieWebCamp" rel="noreferrer">IndieWebCamps</a> have room to balance both discussing topics that interest participants as well as creating things on our websites. We also welcome remote participants, and so, in addition to the ~23 attendees we had, there were several people joining sessions from Zoom (and even helping to take notes, which was and is greatly appreciated!).</p><p>I joined the event both as a participant and as something of a co-organiser, helping to ensure Zoom rooms were set up, helping to open Create Day, archiving notes on the wiki, and more. <a href="https://tantek.com/">Tantek</a> and <a href="https://jkphl.is/">Joschi</a> both led organising, with the <a href="https://tollwerk.de/">Tollwerk</a> team also helping in so many ways.</p><p>Throughout the day, <a href="https://www.maxhaesslein.de/" rel="noreferrer">Max</a> took pictures using a camera connected to a thermal printer. All the pictures were monochrome, which created a terrific effect. I have included some of these pictures throughout the post. Thank you Max for taking such great pictures!</p><h2 id="day-one:-discussions">Day One: Discussions</h2><p>I am always surprised and delighted at the variety of discussion topics that come up at IndieWebCamp events. I think the range of discussion topics is in large part because new participants are encouraged to share ideas first. This ensures that we don’t keep talking about the same topics between events.</p><p>This time, we discussed everything from how to welcome more new people to the indie web, calendar subscriptions on the web, beautiful web design, and whether or not the way we will consume websites will permanently change. <a href="https://indieweb.org/2026/Nuremberg/Schedule">Notes for all of these sessions are archived on the IndieWeb wiki</a> in case you would like to take a look at what we discussed.</p><p>Whenever I join a discussion, I like to try and take notes. This started at IndieWebCamp Brighton in 2024 when I realised how much I love being the note-taker. Every discussion needs at least one note-taker, and I’m happy to help (although the more people who take notes, the more we are able to capture, and the more likely the notes are to reflect all that was discussed). I think I helped with note-taking in every session I joined, which was fun.</p><p>Two sessions that stood out to me were “<a href="https://indieweb.org/2026/Nuremberg/blogchat" rel="noreferrer">Archiving live sessions / co-creating</a>” and “<a href="https://indieweb.org/2026/Nuremberg/thenewweb" rel="noreferrer">Will the way we consume websites permanently change?</a>”. The first session, led by <a href="https://thedriftinghealer.com" rel="noreferrer">Una</a>, was about how to create discussion on a website after a live session (i.e. a group event). It was fascinating to think about how the role artefacts (i.e. recording of sessions, summaries of sessions) play in building community. Our discussion led to digital gardens and knowledge graphs, which gave us plenty of points of inspiration.</p><p>The discussion about whether the way we consume websites will permanently change was a fascinating one. Facilitated by <a href="https://noracudazzo.com/">Nora</a>, we discussed the transformations currently happening on the web as a result of AI, the long-term viability of browsers, and more. This session encouraged the optimist in me. As Casey Newton said in the movie Tomorrowland “I get things are bad, but what are we doing to fix it?”</p><p>Starting with an assessment of where we are – a solid foundation from which to start our discussion – we spoke about the 700,000+ people who run sites on Neocities, the long-lived role of information design, the role of the web in government services, media diets, and more. We were then left with many questions, among them:</p><ul><li>How to convince artists to join the web?</li><li>"How do I talk to my friends on there [the web]?”</li><li>Maybe lurkers have a healthier relationship with consumption?</li></ul><p>The notes from the session include a note that “James doesn't have the words to convince someone without a pre-existing interest to start a website.” This reflected my realisation that while I have so many words to say on the web, I don’t think I have successfully encouraged someone to create a website in direct conversation who hasn’t already said something that would indicate an interest in the web.</p><p>Now that I write this, maybe this website has encouraged others to start websites, in which case maybe I do have the words! But I think the note speaks to a discomfort I have: I wish more people had websites, but communicating their value is tricky. Maybe the best thing I can do to advocate for the web is keep doing what I’m doing – writing, talking about websites passionately, making cool things on the web – and be there for anyone and everyone who sees potential in this medium as I do.</p><h2 id="day-two:-creating">Day Two: Creating</h2><p>The second day of an IndieWebCamp event is <a href="https://indieweb.org/Create_Day">Create Day</a>, in which participants are invited to work on their personal websites for the day. Toward the end of the day, everyone – both in-person and remote attendees – are invited to demo what they built on their website if they would like to share what they made.</p><p>At the beginning of Create Day, I stood up to share what the day is all about. Looking back, it amazes me the ease with which I was able to stand up in front of a room and introduce what Create Day is all about. I am bewildered by how anxious I feel at times in social situations and yet how I can stand up in front of a room and talk to over a dozen people with few notes. My confidence has certainly come a long way over the last few years; there are still many places for me to grow, too.</p><figure><picture><img alt="Me standing up in front of the room presenting what Create Day is all about." loading="lazy" src="https://editor.jamesg.blog/content/images/2026/07/IMG_4686-Large.jpeg" style=" max-width: 130%;"/></picture><div class="alt"><label><input aria-label="Toggle image alt text on screen" type="checkbox"/>ALT</label><div class="content">Me standing up in front of the room presenting what Create Day is all about.</div></div></figure><p>The <a href="https://indieweb.org/what_to_make_at_IndieWebCamp">“what to make at IndieWebCamp” page on the wiki</a>, authored by Tantek, was a good starting point for me to present what to do on Create Day. As someone who has attended an IndieWeb event before and tried to take on a big project, the points “start easy” and “prioritise joy” were particularly resonant. These points were mentioned by several participants as being their guide to the day as time passed, which was a delight to hear.</p><p>I had a few ideas in mind of what I wanted to do on Create Day. I made a new <a href="https://jamesg.blog/bookshelf">bookshelf</a> page as my first project. The last version of the page had not been updated in several years. I decided that rather than try to list all of the books I have on my shelves, I wanted the new page to encapsulate my current interests and include a few select recommendations across different genres. I am happy with the page I made.</p><figure><picture><img alt="Me, with long hair, smiling, sitting at a desk with my right hand on my laptop and looking directly into the camera." loading="lazy" src="https://editor.jamesg.blog/content/images/2026/07/IMG_4676-Large.jpeg" style=" max-width: 130%;"/></picture><div class="alt"><label><input aria-label="Toggle image alt text on screen" type="checkbox"/>ALT</label><div class="content">Me, with long hair, smiling, sitting at a desk with my right hand on my laptop and looking directly into the camera.</div></div></figure><p>I also made an <a href="https://jamesg.blog/indiewebcamp">IndieWebCamp category</a> on my blog and added posts explicitly about or written at IndieWebCamp events. This blog post will go in that category.</p><p>Having started with easier projects, I had finished in the first hour or so. By coincidence, something happened that would give me enough to work on for the rest of the day, and beyond. Tantek mentioned he had updated his “daylists” page, which lists the names of Spotify daylists that have been generated for him based on his music tastes. I didn’t see the changes in Artemis, my web reader, which led me to ask why this was the case.</p><p>This led to my working on two changes. First, <a href="https://jamesg.blog/2026/07/04/processing-feed-items-without-urls">Artemis can now process feed items that don’t have URLs</a>, which was at the root of the problem. Second, because Tantek updates his daylists page every few months and backdates the posts that are added to when the daylist appeared in his Spotify, I wouldn’t see many of the items because Artemis shows posts ordered by creation date. This led me to work on a (still-work-in-progress) feature that would display new items in a feed with a tag like “post title [2 days prev.]” to indicate that a post has been newly discovered but was published with a date equal to N days ago. This turned out to be a complex feature and so I am still working on the logic, but I made good progress during the event.</p><p>After several hours of making – and a wonderful salad lunch prepared by the Tollwerk team – we all came together for demos. It was delightful to see what everyone had made: new designs for a portfolio website, an interactive web page that lets you create a resonance signature based on your humming, improvements to photo grids on websites, and more. In total, we had ~17 demos, including demos presented by remote participants.</p><h2 id="heat-safety">Heat safety</h2><p>A few days before the event, myself, another event organiser, and the lead organiser for the IndieWebCamp Nürnberg event had a discussion about the extreme heat that was forecast for the event. This led to our creating a section on the event wiki page called “Heat Safety” which summarised the weather forecast (temperatures expected to range from 21C to 38C), the accommodations made by the venue, and general advice we had (bring a hat, suncream, sunglasses, etc.).</p><p>While the office where we had the event did not have air conditioning, the staff reported the temperature was relatively cool prior to the event. When the event happened, even with ~23 people in the office – which was incredibly spacious – the room remained at a manageable temperature. Thank you to Tollwerk for helping to ensure this was the case!</p><p>While this particular event was manageable in terms of temperature, I did note that we may want to re-think where we organise events in summer. The heatwave that happened could not have been predicted when the event was first scheduled months ago, but with an increased prevalence of heatwaves likely in summer, heat should definitely be something that event organisers, both in the IndieWeb community and in general, consider when scheduling events.</p><p>Admittedly, I was concerned about travelling somewhere that was 10C warmer than Scotland which, at the time, was experiencing the heatwave. I was already warm at home; going somewhere hotter felt daunting. I coped well in the heat in Nürnberg, avoiding the sunlight as much as I could at peak times, and taking other precautions to stay safe, but I did want to record how, even for someone who loves going to events, heat was a real concern.</p><p>On this note, a few years ago, I attended a Beyond Tellerrand event in Berlin in September. It was so warm the organiser moved the event to be over a month later the next year, to help avoid the same heat scenario for future events. This was the first thing that came to mind when I was voicing my concerns about organising events in summer; the IndieWeb community is far from alone in needing to consider accommodations for heat safety.</p><h2 id="reflections">Reflections</h2><p>IndieWebCamp events are among the highlights of my year. It is a delight to see people who make websites in person and to chat about all things that are on our minds. Because there were enough session ideas to have two event tracks, we all had to make choices about which sessions we attended. Thankfully, because notes are taken and sessions are recorded, we can all review the sessions we couldn’t attend afterward. I am planning to look through some of the notes in the coming days to both see what was discussed and see if any new ideas come to mind for things I can do on my website.</p><p>I am delighted by all the progress I made on both my website and Artemis, too. In particular, the logic <a href="https://jamesg.blog/2026/07/04/processing-feed-items-without-urls" rel="noreferrer">I wrote to process web feed entries without URLs</a> helped make Artemis significantly more robust, and allowed me to discover that a project I worked on a while ago that generated feeds whose entries did not have URLs was actually still working; it was just that Artemis couldn’t handle the feed because unique URLs were not generated.</p><p>The IndieWeb community lists all upcoming IndieWebCamp events on the <a href="https://events.indieweb.org/">IndieWeb Events</a> web page. We also have both in-person and online Homebrew Website Club events, which last ~90 minutes (depending on the meeting) where participants can discuss all things related to personal websites. I help host the Edinburgh in-person and Europe online meet-ups. If you’re interested in joining, you are more than welcome. Check the <a href="https://events.indieweb.org/">IndieWeb Events list</a> for a list of upcoming events, and don’t hesitate to <a href="https://jamesg.blog/email">email me</a> if you have questions.</p> <!--kg-card-begin: html--> <p><a class="u-syndication" href="https://news.indieweb.org/en">Also posted on IndieNews</a>.</p> <!--kg-card-end: html--><script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a16500543f2a5e57',t:'MTc4MzI0MDE1OQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&amp;&amp;(document.onreadystatechange=e,c())}}}})();</script> <a class="tag" href="https://events.indieweb.org/">IndieWeb Events</a> <a class="tag" href="https://events.indieweb.org/">IndieWeb Events list</a> <a class="tag" href="https://indieweb.org/2026/Nuremberg">IndieWebCamp in Nürnberg, Germany</a> <a class="tag" href="https://indieweb.org/2026/Nuremberg/Schedule">Notes for all of these sessions are archived on the IndieWeb wiki</a> <a class="tag" href="https://indieweb.org/2026/Nuremberg/blogchat">Archiving live sessions / co-creating</a> <a class="tag" href="https://indieweb.org/2026/Nuremberg/thenewweb">Will the way we consume websites permanently change?</a> <a class="tag" href="https://indieweb.org/Create_Day">Create Day</a> <a class="tag" href="https://indieweb.org/IndieWebCamp">IndieWebCamps</a> <a class="tag" href="https://indieweb.org/what_to_make_at_IndieWebCamp">“what to make at IndieWebCamp” page on the wiki</a> <a class="tag" href="https://jamesg.blog/2026/07/04/processing-feed-items-without-urls">Artemis can now process feed items that don’t have URLs</a> <a class="tag" href="https://jamesg.blog/2026/07/04/processing-feed-items-without-urls">I wrote to process web feed entries without URLs</a> <a class="tag" href="https://jamesg.blog/bookshelf">bookshelf</a> <a class="tag" href="https://jamesg.blog/email">email me</a> <a class="tag" href="https://jamesg.blog/indiewebcamp">IndieWebCamp category</a> <a class="tag" href="https://jkphl.is/">Joschi</a> <a class="tag" href="https://news.indieweb.org/en">Also posted on IndieNews</a> <a class="tag" href="https://noracudazzo.com/">Nora</a> <a class="tag" href="https://tantek.com/">Tantek</a> <a class="tag" href="https://thedriftinghealer.com">Una</a> <a class="tag" href="https://tollwerk.de/">Tollwerk</a> <a class="tag" href="https://www.maxhaesslein.de/">Max</a> Time-based background colour transitions with Temporal and CSS color-mix - localghost https://localghost.dev/blog/time-based-background-colour-transitions-with-temporal-and-css-color-mix/ 2026-07-05T00:00:00.000Z <p>I've given my website a bit of a refresh! There's a slightly updated layout if you're on desktop, plus I ditched the <code>etc</code> page and I've revamped my <a href="https://localghost.dev/links">links page</a> to be powered by <a href="https://raindrop.io">raindrop.io</a>. The <a href="https://localghost.dev/blog/time-based-background-colour-transitions-with-temporal-and-css-color-mix/">minimalist theme</a> is still minimalist, but a bit more fancy. The <a href="https://localghost.dev/blog/time-based-background-colour-transitions-with-temporal-and-css-color-mix/">vaporwave theme</a> has a newly jazzed-up nav bar with some adorable little icons. But the biggest change is to the <a href="https://localghost.dev/blog/time-based-background-colour-transitions-with-temporal-and-css-color-mix/">city theme</a>, which was previously a starry-sky dark mode theme.</p> <p>If you're reading this between the hours of 9pm - 5am, you might be wondering what all the fuss is about - it looks pretty much the same as it did before. That's because the theme changes depending on the time of day!</p> <div class="image-grid"> <picture><source type="image/webp" srcset="https://localghost.dev/img/BZAiQcUC0U-280.webp 280w"><img loading="lazy" decoding="async" src="https://localghost.dev/img/BZAiQcUC0U-280.jpeg" alt="A screenshot of the sunrise version of this layout, with pixel art skyscrapers at the bottom. The background is a blue to pink to light orange gradient" width="280" height="300"></picture><picture><source type="image/webp" srcset="https://localghost.dev/img/MeJRe8T7yk-280.webp 280w"><img loading="lazy" decoding="async" src="https://localghost.dev/img/MeJRe8T7yk-280.jpeg" alt="A screenshot of the daytime version of this layout, with pixel art skyscrapers at the bottom. The background is a purple to pink gradient" width="280" height="300"></picture><picture><source type="image/webp" srcset="https://localghost.dev/img/NFDu_9WIlE-280.webp 280w"><img loading="lazy" decoding="async" src="https://localghost.dev/img/NFDu_9WIlE-280.jpeg" alt="A screenshot of the sunset version of this layout, with pixel art skyscrapers at the bottom. The background is a purple to pink to orange gradient" width="280" height="300"></picture><picture><source type="image/webp" srcset="https://localghost.dev/img/6CcP_uABhY-280.webp 280w"><img loading="lazy" decoding="async" src="https://localghost.dev/img/6CcP_uABhY-280.jpeg" alt="A screenshot of the nighttime version of this layout, with pixel art skyscrapers at the bottom. There are pixel art stars in the header and the theme is now dark mode. The background is a dark blue to light blue to purple gradient" width="280" height="300"></picture> </div> <p>You can select the time of day using the picker in the top right, after the theme switcher. I'm persisting the choice in session storage so you don't get attacked by sudden light mode when changing pages, but if you visit again in the future it'll reset back to &quot;now&quot;.</p> <p>I was going to just turn the layout into a pastel lo-fi-aesthetic thing, but then I realised that a) I needed <em>some</em> kind of dark mode and b) I'd miss the stars! So I thought... why not both? And why stop at just night and day? (Hat tip to <a href="https://alistairshepherd.uk/">Alistair Shepherd</a> who did something similar with his beautiful Firewatch-inspired website.)</p> <p>Then I remembered that the Temporal API was available experimentally in Chrome and Firefox, and I'd been looking for an excuse to try it out.</p> <h2 id="introducing-temporal" tabindex="-1">Introducing Temporal</h2> <p>For the uninitiated, Temporal is a solution to the objectively terrible Date API in JavaScript. Date was based on Java's Date library, which was also objectively terrible and has long been deprecated.</p> <p>It's always really confusing that <code>Date</code> instances show either local or UTC time depending on which function you use to display them, and date operations are so fiddly that most of us turn to third party libraries like <code>date-fns</code> or <code>luxon</code>.</p> <p>Temporal massively simplifies the API, introducing some new concepts:</p> <ul> <li><code>PlainDateTime</code>: a date and time with no timezone (TZ)</li> <li><code>PlainDate</code>: a date with no time information and no TZ</li> <li><code>PlainTime</code>: a time with no date information and no TZ</li> <li><code>ZonedDateTime</code>: a date and time in a specified TZ</li> </ul> <p><code>PlainTime</code> came in useful for this project, as we don't really care what the day is - only what time it is, so we know what colours to show.</p> <h3 id="getting-the-users-local-time" tabindex="-1">Getting the user's local time</h3> <p>The first thing to do was figure out the time according to the user's browser.<br> The <code>Temporal.Now</code> <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Temporal/Now">namespace</a> has various methods for interacting with the current time, including <code>plainTimeISO()</code> which by default gives us a <code>PlainTime</code> in local time. (You can also pass in a time zone to get a zoned time.)</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> timeNow <span class="token operator">=</span> Temporal<span class="token punctuation">.</span>Now<span class="token punctuation">.</span><span class="token function">plainTimeISO</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Now we need to know when to show the different colours.</p> <h2 id="defining-the-stages" tabindex="-1">Defining the stages</h2> <p>The day is split into four stages: sunrise, daytime, sunset and night. Daytime and night are long - 11.5 hours each - whereas sunrise and sunset each last 90 minutes.</p> <p>The background of the page has a two-colour gradient:</p> <pre class="language-css"><code class="language-css"> <span class="token property">--background</span><span class="token punctuation">:</span> fixed <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-top<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-mid<span class="token punctuation">)</span> 80%<span class="token punctuation">)</span><span class="token punctuation">;</span> </code></pre> <p>The footer has an additional colour that's created with a linear gradient from transparent <code>oklch(0 0 0 / 0)</code> to the chosen third colour.</p> <pre class="language-css"><code class="language-css"> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">oklch</span><span class="token punctuation">(</span>0 0 0 / 0<span class="token punctuation">)</span> 40%<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-bottom<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>This means the last colour sticks to the bottom of the page rather than stretching across the viewport height (it's hard to control even when you specify a percentage in the gradient). It also gives more of a glow that really looks like the sun rising/setting or the glow of the city, which I love.</p> <p>I defined an object for the stages and colours:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> stages <span class="token operator">=</span> <span class="token punctuation">{</span> <span class="token literal-property property">sunrise</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">start</span><span class="token operator">:</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"06:30:00"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">next</span><span class="token operator">:</span> <span class="token string">"day"</span><span class="token punctuation">,</span> <span class="token literal-property property">color1</span><span class="token operator">:</span> <span class="token string">"oklch(0.618 0.3157 265.76)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color2</span><span class="token operator">:</span> <span class="token string">"oklch(0.8867 0.1222 328.24)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color3</span><span class="token operator">:</span> <span class="token string">"oklch(0.9529 0.1222 106.94)"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">day</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">start</span><span class="token operator">:</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"08:00:00"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">next</span><span class="token operator">:</span> <span class="token string">"sunset"</span><span class="token punctuation">,</span> <span class="token literal-property property">color1</span><span class="token operator">:</span> <span class="token string">"oklch(58% 0.15433 300)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color2</span><span class="token operator">:</span> <span class="token string">"oklch(85% 0.22133 302)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color3</span><span class="token operator">:</span> <span class="token string">"oklch(98 0.22133 302)"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">sunset</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">start</span><span class="token operator">:</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"19:30:00"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">next</span><span class="token operator">:</span> <span class="token string">"night"</span><span class="token punctuation">,</span> <span class="token literal-property property">color1</span><span class="token operator">:</span> <span class="token string">"oklch(0.6933 0.1899 297.53)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color2</span><span class="token operator">:</span> <span class="token string">"oklch(75.504% 0.24612 357.26)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color3</span><span class="token operator">:</span> <span class="token string">"oklch(88.591% 0.1422 62.595)"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token literal-property property">night</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">start</span><span class="token operator">:</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"21:00:00"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">next</span><span class="token operator">:</span> <span class="token string">"sunrise"</span><span class="token punctuation">,</span> <span class="token literal-property property">color1</span><span class="token operator">:</span> <span class="token string">"oklch(25.27% 0.0919 276.73)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color2</span><span class="token operator">:</span> <span class="token string">"oklch(47.35% 0.284 283.78)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color3</span><span class="token operator">:</span> <span class="token string">"oklch(62.831% 0.23521 310.291)"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">;</span></code></pre> <p>CSS custom properties are easy to set via JS - you can use <code>root.style.setProperty</code>:</p> <pre class="language-js"><code class="language-js"> root<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span> <span class="token string">"--bg-gradient-top"</span><span class="token punctuation">,</span> <span class="token string">"oklch(25.27% 0.0919 276.73)"</span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>Unlike <code>Date</code>, we don't have to do any gymnastics to compare Temporal instances: there's literally a <code>compare</code> function on each type of instance. Just like with other JS comparison functions, it returns <code>1</code> if the first instance is greater than the second, <code>0</code> if the two instances are the same, and <code>-1</code> if the first instance is less than the second.</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">const</span> compare <span class="token operator">=</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span>compare <span class="token comment">// extracted for brevity</span> <span class="token keyword">switch</span> <span class="token punctuation">(</span><span class="token boolean">true</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunrise<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"night"</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunrise<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>day<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"sunrise"</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>day<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunset<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"day"</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunset<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"sunset"</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">default</span><span class="token operator">:</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Once we've got the stage name, we can look up the colours and set the custom property values.</p> <pre class="language-js"><code class="language-js"> root<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span> <span class="token string">"--bg-gradient-top"</span><span class="token punctuation">,</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color1<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> root<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span> <span class="token string">"--bg-gradient-mid"</span><span class="token punctuation">,</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color2<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> root<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span> <span class="token string">"--bg-gradient-bottom"</span><span class="token punctuation">,</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color3<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>I'm also setting a data attribute on the root so we can do some additional stage-based customisations, such as showing the stars when it's night.</p> <pre class="language-js"><code class="language-js">root<span class="token punctuation">.</span><span class="token function">setAttribute</span><span class="token punctuation">(</span><span class="token string">"data-time"</span><span class="token punctuation">,</span> currentStageName<span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And that will give us our different gradient colours at different times of day!</p> <p>And <em>then</em> I remembered that <code>color-mix</code> exists. Why restrict ourselves to just 4 times of day and 4 sets of colours, when we could make them... transition into each other?????</p> <h2 id="blending-transitions-with-color-mix" tabindex="-1">Blending transitions with color-mix</h2> <p><code>color-mix</code> is an extremely cool CSS function that lets you, well, mix two colours together. You tell it what colour space you're working with, and the colours, and the browser magically outputs the mix between the two.</p> <pre class="language-css"><code class="language-css"><span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">color-mix</span><span class="token punctuation">(</span>in oklch<span class="token punctuation">,</span> color1<span class="token punctuation">,</span> color2<span class="token punctuation">)</span></code></pre> <p>Much like with gradients, you can also specify a percentage value for the colours, which indicates the proportions of the colours:</p> <pre class="language-css"><code class="language-css"><span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">color-mix</span><span class="token punctuation">(</span>in oklch<span class="token punctuation">,</span> color1 20%<span class="token punctuation">,</span> color2<span class="token punctuation">)</span></code></pre> <p>So I could gradually feed in a bit of the next stage's colour until the next stage took over completely.</p> <p>To get a percentage value for the next stage colour to feed in, I had to figure out how far through the current stage we are.</p> <p>First, I'm calculating the time until the next stage - super simple with the <code>until</code> function on <code>Temporal</code> instances:</p> <pre class="language-js"><code class="language-js">time1<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>time2<span class="token punctuation">)</span></code></pre> <p>This gives us a <code>Temporal.Duration</code> which represents a period between two time points. So, for example, if it's 7:45pm now and we're calculating <code>timeUntilNextStage</code>:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> timeUntilNextStage <span class="token operator">=</span> timeNow<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span> console<span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>timeUntilNextStage<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token comment">// PT1H15M</span> </code></pre> <p><code>Duration</code>s are stringified (and specified) using the <a href="https://en.wikipedia.org/wiki/ISO_8601#Durations">ISO 8601 duration format</a>, so &quot;PT1H15M&quot; means &quot;period, time separator, 1 hour, 15 minutes&quot;.Time information appears after the <code>T</code>; if the duration had any date information in it, it'd appear before the <code>T</code>.</p> <p>We set <code>timeUntilNextStage</code> in the switch statement where we're deciding what stage we're in, for example:</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunrise<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>day<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"sunrise"</span><span class="token punctuation">;</span> timeUntilNextStage <span class="token operator">=</span> timeNow<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Once we've got the duration representing time until the next stage, we need to know the duration between the start of the current stage and the start of the next stage - let's call it the &quot;transition duration&quot;. For sunset-to-night and sunrise-to-day, the transition duration is always 90 minutes; for night-sunrise and day-sunset, it'd be 11.5 hours. I didn't want the colour mixing to happen all throughout the day, only around sunrise/sunset like in real life, so I just decided to hardcode the transition duration for day and night to be 90 minutes so it matches the other two.</p> <p>So for that, I can instantiate a <code>Duration</code> using the same ISO 8601 syntax:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> entireTransitionDuration <span class="token operator">=</span> Temporal<span class="token punctuation">.</span>Duration<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"PT1H30M"</span><span class="token punctuation">)</span></code></pre> <p>Now I need to calculate the difference between the total duration and the time until next stage - basically, how far into the transition period we are, and therefore how much of a percentage we should mix in of the next colour.</p> <p>Handily, Temporal gives us a <code>subtract</code> function as well:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> diff <span class="token operator">=</span> entireTransitionDuration<span class="token punctuation">.</span><span class="token function">subtract</span><span class="token punctuation">(</span>timeUntilNextStage<span class="token punctuation">)</span></code></pre> <p>Then to figure out the transition progress as a percentage, we can divide <code>diff</code> by <code>entireTransitionDuration</code>. We'll do that with the time values in seconds so we can divide them, using the instance's <code>total</code> function:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> entireTransitionDurationInSeconds <span class="token operator">=</span> entireTransitionDuration<span class="token punctuation">.</span><span class="token function">total</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">unit</span><span class="token operator">:</span> <span class="token string">"seconds"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">const</span> diffInSeconds <span class="token operator">=</span> diff<span class="token punctuation">.</span><span class="token function">total</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">unit</span><span class="token operator">:</span> <span class="token string">"seconds"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">const</span> transitionProgressPercent <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span><span class="token punctuation">(</span>diffInSeconds <span class="token operator">/</span> entireTransitionDurationInSeconds<span class="token punctuation">)</span><span class="token operator">*</span><span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toFixed</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token comment">// gives us a string representation with 0 d.p.</span></code></pre> <h3 id="the-midnight-problem" tabindex="-1">The midnight problem</h3> <p>It's a little more complicated for the &quot;night&quot; stage, because that crosses midnight into the next day. Remember that our <code>PlainTime</code> only has time information, not date information - so if it's 10pm and you're asking it how long until sunrise at 6:30am, it'll give you a negative number!</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">const</span> now <span class="token operator">=</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"22:00"</span><span class="token punctuation">)</span> <span class="token keyword">const</span> sunrise <span class="token operator">=</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"06:30"</span><span class="token punctuation">)</span> <span class="token keyword">const</span> d <span class="token operator">=</span> now<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>sunrise<span class="token punctuation">)</span> <span class="token comment">// Temporal.Duration -PT15H30M</span></code></pre> <p>This causes problems at the point where I calculate the diff, as it'll come out as a large number and completely throw off the calculations. I got around this by getting the absolute value of the duration with <code>.abs()</code>, so <code>timeUntilNextStage</code> will always be positive, even if it's before midnight: e.g. what was<code>-PT15H30M</code> will now be <code>PT15H30M</code>. Calculating the diff by subtracting that from a <code>transitionDuration</code> of 90 mins will always yield a negative number.</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunrise<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span> <span class="token operator">||</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"night"</span><span class="token punctuation">;</span> timeUntilNextStage <span class="token operator">=</span> timeNow<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>stages<span class="token punctuation">.</span>sunrise<span class="token punctuation">.</span>start<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">abs</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we only calculate a transition percentage if <code>diff</code> is greater than 0:</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">let</span> transitionProgressPercent <span class="token operator">=</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>diffInSeconds <span class="token operator">></span> <span class="token number">0</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> transitionProgressPercent <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">round</span><span class="token punctuation">(</span><span class="token punctuation">(</span>diffInSeconds <span class="token operator">/</span> entireTransitionDurationInSeconds<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">100</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>This works for the daytime stage too: if it's more than 90 mins before sunset, it'll come out with a negative diff - so that will just display the daytime colours and no transition.</p> <h3 id="lets-mix" tabindex="-1">Let's mix!</h3> <p>Now we can use that percentage value (which will always be a whole number) in the <code>color-mix</code> function to dictate how much of the next colour we should interpolate.</p> <pre class="language-css"><code class="language-css"><span class="token selector">color-mix(in oklch, $</span><span class="token punctuation">{</span>color1<span class="token punctuation">}</span> <span class="token selector">$</span><span class="token punctuation">{</span>transitionProgressPercent<span class="token punctuation">}</span><span class="token selector">%, $</span><span class="token punctuation">{</span>color2<span class="token punctuation">}</span><span class="token punctuation">)</span></code></pre> <p>I updated my <code>stages</code> object to include the next stage name as well:</p> <pre class="language-js"><code class="language-js"> <span class="token literal-property property">night</span><span class="token operator">:</span> <span class="token punctuation">{</span> <span class="token literal-property property">start</span><span class="token operator">:</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span><span class="token function">from</span><span class="token punctuation">(</span><span class="token string">"21:00:00"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token literal-property property">next</span><span class="token operator">:</span> <span class="token string">"sunrise"</span><span class="token punctuation">,</span> <span class="token literal-property property">color1</span><span class="token operator">:</span> <span class="token string">"oklch(25.27% 0.0919 276.73)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color2</span><span class="token operator">:</span> <span class="token string">"oklch(47.35% 0.284 283.78)"</span><span class="token punctuation">,</span> <span class="token literal-property property">color3</span><span class="token operator">:</span> <span class="token string">"oklch(62.831% 0.23521 310.291)"</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// etc</span></code></pre> <p>So we can get both colours dynamically when we set the variables with <code>color-mix</code>:</p> <pre class="language-js"><code class="language-js"> root<span class="token punctuation">.</span>style<span class="token punctuation">.</span><span class="token function">setProperty</span><span class="token punctuation">(</span> <span class="token string">"--bg-gradient-top"</span><span class="token punctuation">,</span> <span class="token template-string"><span class="token template-punctuation string">`</span><span class="token string">color-mix(in oklch, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>stages<span class="token punctuation">[</span>nextStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color1<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string"> </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>transitionProgressPercent<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">%, </span><span class="token interpolation"><span class="token interpolation-punctuation punctuation">${</span>stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color1<span class="token interpolation-punctuation punctuation">}</span></span><span class="token string">)</span><span class="token template-punctuation string">`</span></span><span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>And that's how we transition the colours!</p> <h2 id="transitioning-the-transitions" tabindex="-1">Transitioning the transitions</h2> <p>As a bonus touch, I wanted the colour change to transition smoothly when you switch between stages manually using the picker on the top right. By declaring my <code>bg-gradient-xx</code> variables using <code>@property</code>, I can tell the browsers that yes, they are definitely colours - and therefore they can be animated.</p> <p>Without this explicit custom property declaration, I could set the value of <code>--bg-gradient-top</code> to a number, or a position, or anything I wanted. By saying it's definitely a colour, the browser knows how to transition it into other values of the same type.</p> <p>I initially did this with <code>@property</code> declarations in the CSS:</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@property</span> --bg-gradient-top</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>...<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --bg-gradient-mid</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>...<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token atrule"><span class="token rule">@property</span> --bg-gradient-bottom</span> <span class="token punctuation">{</span> <span class="token property">syntax</span><span class="token punctuation">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">;</span> <span class="token property">inherits</span><span class="token punctuation">:</span> true<span class="token punctuation">;</span> <span class="token property">initial-value</span><span class="token punctuation">:</span> <span class="token function">oklch</span><span class="token punctuation">(</span>...<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> </code></pre> <p>Unfortunately, setting these in the CSS meant that you got a flash of whichever initial values I'd set before the JS kicked in and set the appropriate colours for time of day. If this page were server-driven, or always started from the same colour for everyone, it would've been fine. But the starting colour depends on your time zone and is only calculated when the initial JS runs.</p> <p>I got around this by setting the properties via JS instead:</p> <pre class="language-js"><code class="language-js"> window<span class="token punctuation">.</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">registerProperty</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"--bg-gradient-top"</span><span class="token punctuation">,</span> <span class="token literal-property property">syntax</span><span class="token operator">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">,</span> <span class="token literal-property property">inherits</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">initialValue</span><span class="token operator">:</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color1<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> window<span class="token punctuation">.</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">registerProperty</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"--bg-gradient-mid"</span><span class="token punctuation">,</span> <span class="token literal-property property">syntax</span><span class="token operator">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">,</span> <span class="token literal-property property">inherits</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">initialValue</span><span class="token operator">:</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color2<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> window<span class="token punctuation">.</span><span class="token constant">CSS</span><span class="token punctuation">.</span><span class="token function">registerProperty</span><span class="token punctuation">(</span><span class="token punctuation">{</span> <span class="token literal-property property">name</span><span class="token operator">:</span> <span class="token string">"--bg-gradient-bottom"</span><span class="token punctuation">,</span> <span class="token literal-property property">syntax</span><span class="token operator">:</span> <span class="token string">"&lt;color>"</span><span class="token punctuation">,</span> <span class="token literal-property property">inherits</span><span class="token operator">:</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token literal-property property">initialValue</span><span class="token operator">:</span> stages<span class="token punctuation">[</span>currentStageName<span class="token punctuation">]</span><span class="token punctuation">.</span>color3<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span></code></pre> <p>I had to wrap these in a <code>try/catch</code> as it will throw if the property's already been defined. It wasn't super trivial to figure out if this property had already been set, as the CSS does define some values for these with the regular <code>--bg-gradient-xx: ...</code> syntax.</p> <p>On the <code>body</code> and <code>footer</code> I set <code>transition-property</code> and <code>transition-duration</code> to tell it which properties I want to animate:</p> <pre class="language-css"><code class="language-css"> <span class="token selector">body</span> <span class="token punctuation">{</span> <span class="token property">--background</span><span class="token punctuation">:</span> fixed <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-top<span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-mid<span class="token punctuation">)</span> 80%<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition-property</span><span class="token punctuation">:</span> --bg-gradient-top<span class="token punctuation">,</span> --bg-gradient-mid<span class="token punctuation">;</span> <span class="token property">transition-duration</span><span class="token punctuation">:</span> 0.5s<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token selector">footer</span> <span class="token punctuation">{</span> <span class="token property">background</span><span class="token punctuation">:</span> <span class="token function">linear-gradient</span><span class="token punctuation">(</span><span class="token function">oklch</span><span class="token punctuation">(</span>0 0 0 / 0<span class="token punctuation">)</span> 40%<span class="token punctuation">,</span> <span class="token function">var</span><span class="token punctuation">(</span>--bg-gradient-bottom<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token property">transition</span><span class="token punctuation">:</span> --bg-gradient-bottom 0.5s<span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>And like motherflipping magic, the colours transition seamlessly into each other when the values change! I love CSS. The animation is such an unnecessary touch, but this is my website so unnecessary is the name of the game.</p> <h2 id="polyfilling-temporal-for-safari" tabindex="-1">Polyfilling Temporal for Safari</h2> <p>Alas, Safari is behind the times. We love progressive enhancement, and of course I could have just removed any of the transition logic for people whose browsers don't support Temporal, but that's no fun. They deserve sunsets too!</p> <p>Writing a shim for Temporal was also no fun, but I did it because I love you.</p> <p>There are various Temporal polyfills around and about, but I didn't want to end up importing a whole lot of extra JS when I only needed one or two functions. I'm not using any kind of bundler on this site - I use Eleventy to generate the pages, but scripts are just imported vanilla - so I couldn't import something with NPM and expect it to tree-shake any bits I wasn't using. It was a lot more lightweight to just write my own.</p> <p>To check for Temporal support, it's a matter of just checking if <code>window.Temporal?.PlainTime</code> is undefined:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> supportsTemporal <span class="token operator">=</span> <span class="token keyword">typeof</span> window<span class="token punctuation">.</span>Temporal<span class="token operator">?.</span>PlainTime <span class="token operator">!==</span> <span class="token string">"undefined"</span><span class="token punctuation">;</span></code></pre> <p>I'm checking for <code>PlainTime</code> specifically as some browsers may have very high level Temporal implementations, but we can't do much without <code>PlainTime</code>.</p> <p>To get the user's time in a non-Temporal world, we can just call the good old-fashioned <code>new Date()</code>:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">function</span> <span class="token function">getUserTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>supportsTemporal<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> Temporal<span class="token punctuation">.</span>Now<span class="token punctuation">.</span><span class="token function">plainTimeISO</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>To compare dates, we do it by comparing epoch timestamps. These represent the number of milliseconds since the Unix epoch, 01 Jan 1970.</p> <pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">jsDateCompare</span><span class="token punctuation">(</span><span class="token parameter">date1<span class="token punctuation">,</span> date2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> date1Ms <span class="token operator">=</span> date1<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">const</span> date2Ms <span class="token operator">=</span> date2<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>date1Ms <span class="token operator">===</span> date2Ms<span class="token punctuation">)</span> <span class="token keyword">return</span> <span class="token number">0</span><span class="token punctuation">;</span> <span class="token keyword">return</span> date1Ms <span class="token operator">&lt;</span> date2Ms <span class="token operator">?</span> <span class="token operator">-</span><span class="token number">1</span> <span class="token operator">:</span> <span class="token number">1</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then, we can just assign whichever version of the function we need:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">const</span> compare <span class="token operator">=</span> supportsTemporal <span class="token operator">?</span> Temporal<span class="token punctuation">.</span>PlainTime<span class="token punctuation">.</span>compare <span class="token operator">:</span> jsDateCompare<span class="token punctuation">;</span></code></pre> <p>To polyfill <code>until</code>, I've got a <code>durationBetween</code> function which will call <code>until</code> if Temporal's supported, otherwise it'll subtract two epoch timestamps, and divide the result by 1000 to get the duration as seconds:</p> <pre class="language-js"><code class="language-js"><span class="token keyword">export</span> <span class="token keyword">function</span> <span class="token function">durationBetween</span><span class="token punctuation">(</span><span class="token parameter">time1<span class="token punctuation">,</span> time2</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>supportsTemporal<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> <span class="token punctuation">(</span>time2<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">-</span> time1<span class="token punctuation">.</span><span class="token function">getTime</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token operator">/</span> <span class="token number">1000</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> time1<span class="token punctuation">.</span><span class="token function">until</span><span class="token punctuation">(</span>time2<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>Then I call it like this:</p> <pre class="language-js"><code class="language-js"> <span class="token keyword">case</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>sunset<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">>=</span> <span class="token number">0</span> <span class="token operator">&amp;&amp;</span> <span class="token function">compare</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span> <span class="token operator">&lt;</span> <span class="token number">0</span><span class="token operator">:</span> <span class="token punctuation">{</span> currentStageName <span class="token operator">=</span> <span class="token string">"sunset"</span><span class="token punctuation">;</span> timeUntilNextStage <span class="token operator">=</span> <span class="token function">durationBetween</span><span class="token punctuation">(</span>timeNow<span class="token punctuation">,</span> stages<span class="token punctuation">.</span>night<span class="token punctuation">.</span>start<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">break</span><span class="token punctuation">;</span> <span class="token punctuation">}</span></code></pre> <p>I wrote a whole suite of unit tests (for a PERSONAL project! I know!) to make sure behaviour was exactly the same, and it seems to be working nicely. I'm hoping I can remove the polyfills in time, but given that the web is beautifully backwards-compatible, it's not the end of the world if it stays around longer than it needs to.</p> <h2 id="fixing-a-weird-background-glitch-in-safari" tabindex="-1">Fixing a weird background glitch in Safari</h2> <p><picture><source type="image/webp" srcset="https://localghost.dev/img/cmqGHiVsJL-280.webp 280w, https://localghost.dev/img/cmqGHiVsJL-640.webp 640w, https://localghost.dev/img/cmqGHiVsJL-960.webp 960w" sizes="auto"><img loading="lazy" decoding="async" src="https://localghost.dev/img/cmqGHiVsJL-280.jpeg" alt="A screenshot of the bottom half of my website, with a big white space in the background where the background should be. The background gradient cuts off a third of the way down the screen." width="960" height="616" srcset="https://localghost.dev/img/cmqGHiVsJL-280.jpeg 280w, https://localghost.dev/img/cmqGHiVsJL-640.jpeg 640w, https://localghost.dev/img/cmqGHiVsJL-960.jpeg 960w" sizes="auto"></picture><br> SAFARI WHY.</p> <p>I started experiencing a very odd glitch in Safari for MacOS where the background gradient would only show up in the initial viewport - when you scrolled, it went white or black depending on whether it was light or dark mode. I narrowed it down to the <code>background-attachment: fixed</code> property of the body.</p> <p><video controls="" loading="lazy"><source src="https://localghost.dev/img/background-glitch-safari-demo.mp4" type="video/mp4"></video></p> <p>After a lot of disabling random CSS and diffing against the <code>main</code> branch, which doesn't have that problem, I found out that it really doesn't play nicely with <code>container-type: inline-size</code>. In the course of redesigning the site, I'd added a new container context to the <code>&lt;body&gt;</code> element and in the process broken the gradient rendering for Safari, as something goes wrong when it tries to render the gradient background with a <code>fixed</code> attachment. Chrome, Firefox and iOS Safari were totally fine.</p> <p>When I half-jokingly said I'd have to find out what the modern equivalent of <code>&lt;!--[IF IE]&gt;</code> was, David Bushell <a href="https://social.lol/@db/116867834799255143">pointed me to</a> Eric Meyer's <a href="https://meyerweb.com/eric/thoughts/2026/05/28/accessible-i-think-split-cell-table-headers/#:~:text=@supports%20(font:%20-apple-system-body)">post about accessible table headers</a>, which in turn led me to <a href="https://browserstrangeness.github.io/css_hacks.html#safari">Browser Strangeness</a>. That did indeed have a <code>@supports</code> query that targeted Safari for MacOS:</p> <pre class="language-css"><code class="language-css"><span class="token atrule"><span class="token rule">@supports</span> <span class="token punctuation">(</span><span class="token keyword">not</span> <span class="token punctuation">(</span><span class="token property">-webkit-text-size-adjust</span><span class="token punctuation">:</span>none<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token keyword">and</span> <span class="token punctuation">(</span><span class="token property">font</span><span class="token punctuation">:</span> -apple-system-body<span class="token punctuation">)</span></span> <span class="token punctuation">{</span> <span class="token selector">.selector</span> <span class="token punctuation">{</span> <span class="token property">property</span><span class="token punctuation">:</span>value<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span></code></pre> <p>I stuck a <code>position-attachment: initial</code> in there, and lo and behold, the problem went away. It means the background isn't quite how I wanted it to look in Safari, but I'll survive.</p> <h2 id="this-was-surprisingly-complex" tabindex="-1">This was surprisingly complex</h2> <p>The individual moving parts of this project - getting the time and choosing colours, mixing the colours by percentages, animating the transitions - were not that complicated in isolation. Sure, they required me to learn things and look things up, but it was a fun thing to build (until the point where Safari came into the picture).</p> <p>The challenge came wiring it all together in a way that didn't cause flashes of unstyled content (the dreaded FOUC) or flashes of the wrong stage before we calculate the current time. This is a static site, so it's all client-side JS. Ideally I'd compute user's the current time on the server and serve the content with the correct colour values in the HTML, but my web host only supports static sites.</p> <p>To get around that I had to add another separate <code>init.js</code> script which runs instantly - it's got a bit of a copy and paste job going on with some of the functions, but it does a very rudimentary check of the user's current time and sets the stage accordingly with no transitions, just so there's <em>some</em> styling on initial load. My JS is all modules, so is <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script#defer">deferred by default</a>. I experimented with making <em>all</em> the JS render-blocking with <code>blocking=&quot;render&quot;</code>, but that felt a bit gross and also didn't fix the FOUC in Firefox.</p> <p>But that's fine, y'know? It still loads in well under a second, and still looks good if you have JS disabled. It's my personal site and it doesn't need to be perfect.</p> Do you need a Nintendo 3DS? - Joel's Log Files https://joelchrono.xyz/blog/nintendo-3ds 2026-07-04T18:39:52.000Z <p>Probably not. I bought one anyway. Figured I’d write some of my memories surrounding it growing up, as well as how I set-up my own.</p> <h2 id="some-memories">Some memories</h2> <p>The Nintendo 3DS was one of those consoles that I mostly admired from a distance. There are a few moments in time, sparsed throughout my youth where I interacted with one, an uncommon endeavor compared to my love for the PSP or even the older Game Boy Advance.</p> <p>When it came out I was too poor to even consider it, it was super expensive when it released, and it was still super expensive when the price lowered. I saw very few of them during the first few years, I was simply aware of its existence and a year and a half later I owned a PSP, not thinking much of what I was missing out on. I had an itch for <em>Ocarina of Time 3D</em> but <a href="/blog/first-contact-with-emulation/">I also knew of emulation already</a>, so there was no need for it.</p> <p>Somewhere in 2013, I stayed for a few days with a friend in the US. It was my first time on a whole different country without my parents! I should write about this period at some point, but for now, the important bit is that my friend had a Nintendo 3DS, and… <em>I don’t remember playing anything on it</em>. I think he had <em>Kid Icarus Uprising</em> and maybe some other title, but I didn’t do much. He had a different console I played instead, the Wii U, I’ll write about it some other day.</p> <p>I dit try to convince him to buy <em>Ocarina of Time</em>, and telling him it was a remake of the Nintendo 64 version. He was completely sure it was a fully new game. In any case, he never bought it, at least during my stay.</p> <p>Eventually I saw some other kid playing taht game during some big church event, not sure if it was before or after that time, I just recall the ReDeads in the middle of the ruined streets of Hyrule, with Link frozen with a scream and eaten alive, or something.</p> <p>Some other time, it was my turn to have a friend stay with us for a week or so, and during that whole ordeal he brought his Nintendo 3DS, it was the original <em>Aqua Blue</em> model again—all of them so far have been the <em>Aqua Blue</em> model—and he was playing <em>Super Mario 3D Land</em>, a fantastic title for the system. This was the most amount of time I got to spend playing with one myself. The game was great and I completed a bunch of random levels.</p> <p>My cousins which I interacted with during Christmas and the like also had a Nintendo 2DS each, both playing whatever Pokemon was popular at the time, like <em>Sun</em> &amp; <em>Moon</em>, but I’m really ignorant of this. They continue that tradition with the Switch today, they are absolute Pokemon fans.</p> <p>Weirdly enough, I just recalled some of my best friends that I saw every Sunday at church more than a decade ago owning one too, for some reason I didn’t remember this at all. I have more memories playing on the Xbox 360 with them, but I <em>think</em> I grabbed it a few times when I visited their house, though I can’t recall what games I played with it—<em>this paragraph was written after everything around it, placed here for continuitiy’s sake</em>.</p> <p>Last but not least, when I moved to the city I live in today, I made a new friend who owned a couple of 3DS models! From the original to the New 3DS XL. I also stayed at his house for a couple days one time.</p> <p>I had my PSP and I already was a huge fan of Monster Hunter. My friend owned <em>Monster Hunter 4 Ultimate</em>, but he barely played it at all. I gave it a spin for a few hours and it was awesome—though I kept grumbling about how this game could have run on the PSP with just a couple of compromises and raised my fist at Capcom for abandoning the PSVita—which I never even owned—but whatever.</p> <p>Other than that, I’ve been aware of 3DS emulation for a while, I even went ahead and setup multiplayer with Citra only a couple years ago! This time to play some <em>Monster Hunter 3 Ultimate</em> using the <a href="https://home.mgn.pub/">Madness Network</a> servers. I talked about playing <a href="/blog/the-monster-hunter-online-experience/">Monster Hunter online already</a>, but somehow never mentioned trying 3DS entries. Only played about a day and my setup and save have been lost to some distro hop.</p> <p>All in all, other than <em>Super Mario 3D Land</em> and <em>Mario Kart 7</em> and <em>Face Raiders</em>, the 3DS library remained unexplored for me. That was until a few years ago, when this blog was already a thing!</p> <p>A friend allowed me to borrow his Nintendo 3DS for a couple weeks, I only played two games on it, <em>Fire Emblem Awakening</em>, which I tried for about four hours, and <a href="/blog/metroid-samus-returns/">Metroid Samus Returns</a>, which I finished soon after. That game was so awesome, it is what finally pushed me to write reviews on this site, and that was my last interaction with a 3DS in a while.</p> <p><img src="/assets/img/blogs/2026-07-04-3ds-2.jpg" alt="The device, closed!" /></p> <h2 id="recently">Recently</h2> <p>For many months already, since the <a href="https://www.ayntec.com/products/ayn-thor">AYN Thor</a> and the <a href="https://anbernic.com/products/rgds">Anbernic DS</a> came out in the market, with comparisons and praise and people complaining about the prices or the performance of one or the other, and after being exposed to countless YouTube videos, and lots of discussion over on the TWG Discord server, the itch to acquire a device for this was bound to happen.</p> <p>The hardware of the Thor is excellent for a huge percentage of the 3DS library, and for the price, it would allow me much more than the that, as it would let me have PS2 and GameCube, Switch and even light PC gaming via <a href="https://gamenative.app/">GameNative</a> or <a href="https://gamehub.xiaoji.com/">GameHub</a>; and even though the RGDS is weak for 3DS, at least I would have save states, fast forward and other features emulating the Nintendo DS with it, on an ideal form factor.</p> <p>However, as small as the impact of the 3DS was on my life, I couldn’t help myself. It was something I had wanted for a while. With prices not going down anytime soon, and the Switch 2 tempting me every once in a while, I figured this would be a nice middleground.</p> <p>I looked around for a bit, and eventually landed on a red New Nintendo 3DS XL for about 333 USD with shipping. There were cheaper alternatives out there, such as a regular 3DS XL or a 2DS XL, but this is the top of the line and you can tell the quality is up there once held.</p> <p><em>As I write this today, I saw a posting selling the same model and color I got at $290! which is better though still expensive, but the week I had to return it is over and I’ve already played with it for a dozen hours, so whatever!</em></p> <h2 id="my-set-up">My set-up</h2> <p>My model already came pre-modded, I took some time to make sure the modding tools were up to date. The 3DS modding scene is as crazy as the PSP, and emulation for many systems is available. However, I own plenty of devices that are better at emulation at this point. What I really care about here is the existing library for the Nintendo 3DS, and the original Nintendo DS, as was seen on my current setup.</p> <p>The first thing I did when I purchased it was check the alternative shop every modded 3DS is bound to have, and grabbed myself a few titles. I do not encourage piracy much nowadays, when buying titles officially is possible, but this is not the case here. Nintendo doesn’t sell carts and they have no digital shops, so whatever. here’s a list of things on my 3DS that will probably be expanded soon.</p> <h3 id="nintendo-3ds-titles">Nintendo 3DS titles</h3> <ul> <li>Ace Attorney Trilogy</li> <li>Donkey Kong Country Returns 3D</li> <li>Fire Emblem Awakening</li> <li>Fire Emblem Echoes</li> <li>Mario &amp; Luigi Bowser’s Inside Story + Bowser’s Jr’s Journey</li> <li>Metroid Samus Returns</li> <li>Pushmo</li> <li>Shovel Knight: Treasure Trove</li> <li>The Legend of Zelda: A Link Between Worlds</li> <li>The Legend of Zelda: Ocarina of Time 3D</li> </ul> <h3 id="nintendo-ds-titles">Nintendo DS titles</h3> <ul> <li>Dragon Quest IV</li> <li>Dragon Quest V</li> <li>Ghost Trick</li> <li>Pokémon Black</li> </ul> <h3 id="homebrew">Homebrew</h3> <ul> <li>3DSident</li> <li>Anemone 3DS Theme Manager</li> <li>Cave Story</li> <li>FBI</li> <li>FTPD</li> <li>Luma3DS</li> <li>NDS Forwarder Generator</li> <li>The Hombrew Launcher</li> <li>Univeral Updater</li> <li>hShop</li> </ul> <p><img src="/assets/img/blogs/2026-07-04-3ds.jpg" alt="The device, opened!" /></p> <p>My experience so far has been awesome. Since it came modded I haven’t really had to thinker that much with it, instead, as of the time of writing, I have played about 20 hours of <em>Fire Emblem Awakening</em>—I just beat chapter 13 of the game—and honestly, the hardware is pretty nice! Due to the nature of the game I often quit and reopen it—I’m sorry I can’t handle permadeath—and the loading times are rather quick! I guess it’s up to the title and how well optimized it is, but still impressive. Buttons and battery life on my device have been really nice for me, but I lack a point of reference and I recharge it whenever needed.</p> <p>I made a couple folders for the built-in software, all the homebrew and a couple GBA games I added but will probably delete. Either way I added it for now. I have installed a few themes using <em>Anemone</em>, right now I’m using a <a href="https://themeplaza.art/item/84660">Parasite Eve theme</a>, but I may try and do my own, and share it in the nearby future! I didn’t like any of the ones for <em>Chrono Trigger</em>, though some of the <em>Final Fantasy</em> ones were tempting. In any case. I’m really happy with how everything looks and feels!</p> <p>This is day 89 of <a href="https://100daystooffload.com">#100DaysToOffload</a></p> <p> <a href="mailto:me@joelchrono.xyz?subject=Do you need a Nintendo 3DS?">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/116865637634965192">Reply on Fediverse</a> </p> Happy 250th birthday, America - Werd I/O 6a49135282c96000019ca890 2026-07-04T14:11:51.000Z <img src="https://images.unsplash.com/photo-1432837600650-e430234b632a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDExfHw0dGglMjBvZiUyMGp1bHl8ZW58MHx8fHwxNzgzMTc0Mjk3fDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Happy 250th birthday, America"><p>Celebrating the 250th anniversary of a declaration that <a href="https://blackpast.org/african-american-history/declaration-independence-and-debate-over-slavery/?ref=werd.io" rel="noreferrer">had a passage decrying slavery removed</a> because southern states complained by eating <a href="https://www.pcrm.org/news/news-releases/new-us-poll-almost-9-out-10-adults-dont-know-risks-eating-hot-dogs?ref=werd.io" rel="noreferrer">carcinogenic mass produced meat tubes</a> while <a href="https://www.nbcnews.com/politics/donald-trump/trump-speak-mount-rushmore-one-monument-cant-remake-rcna352734?ref=werd.io" rel="noreferrer">the President tells half of us we&#x2019;re evil Marxists</a> while we swelter through <a href="https://www.nytimes.com/2026/07/03/climate/heat-wave-us-canada-climate-change.html?ref=werd.io" rel="noreferrer">a man made heat wave</a> is incredibly American. Happy 4th!</p><p>All snark aside, genuinely, I do love America. The Declaration of Independence - 250 years ago! - was genuinely important. Most places you go, the people are kind, community-minded, and optimistic. One day, eventually, they will have an equitable government that redresses wrongs and provides real support.</p><p>My entire political worldview is: what if you could maintain the optimism and energy of America but add an inclusive culture of support that gives everyone education, healthcare, a real safety net and a springboard for their lives, in a way that builds communities rather than extracts from them?It&apos;s not our current reality, but I&apos;m certain we can get there.</p><p>One day, we&apos;ll look back, and the corporatism, the militarism, the racism, and the subjugation will be a thing of the past. In their place will be thriving communities. And, finally, all of this will be great.</p> This blog is written in en-GB - Kev Quirk https://kevquirk.com/this-blog-is-written-in-en-gb 2026-07-04T13:04:00.000Z <div class="link card"><h2>This blog is written in en-GB</h2><p class="post-author">by Terence Eden</p><p>Terrence talks about some of the wonderful idiosyncrasies of the British language and that, no, he won't be making his writing more global.</p><p><a class="button" target="_blank" href="https://shkspr.mobi/blog/2026/07/this-blog-is-written-in-en-gb/">Read post ➡</a></p></div> <hr> <p>I really enjoyed this post and like that Terrance <em>could</em> have said <em>"year sure, I'll try and be more inclusive for you non-Brits"</em>, but he didn't. Instead he said:</p> <blockquote> <p>Here's the thing. No. [...] There's a reason for that. It is more than the language I speak; it is the culture I live in, the way that I think, and the accent I use.</p> </blockquote> <p>Love this, and I appreciate Terrance holding firm on our wonderful British culture - just like everyone should do on their blog. That's part of the fun - to learn about the idiosyncrasies of difference languages and cultures.</p> <p>It still surprises me that someone had the gall to leave a comment effectively saying <em>"can you change the way you write to be more inclusive, because <strong>I</strong> don't understand some of the references, and <strong>I</strong> can't be bothered to learn."</em></p> <p>Some people...</p> <div class="email-hidden"> <hr /> <p>Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️</p> <p>You can <a href="mailto:19gy@qrk.one?subject=This%20blog%20is%20written%20in%20en-GB">reply to this post by email</a>, or <a href="https://kevquirk.com/this-blog-is-written-in-en-gb#comments">leave a comment</a>.</p> </div> Combined 1D and 2D Barcodes - Terence Eden’s Blog https://shkspr.mobi/blog/?p=70974 2026-07-04T11:34:57.000Z <p>This was a little idea gnawing at the back of my brain. The humble barcode has been in use <a href="https://en.wikipedia.org/wiki/Universal_Product_Code">since the 1970s</a>. In the next few years it will likely be replaced with a <a href="https://www.gs1uk.org/knowledge-hub/qr-codes-powered-by-gs1/will-qr-codes-replace-barcodes-by-2027">2D QR Code</a>.</p> <p>I couldn't find anyone who'd made a QR code with an embedded UPC - so I decided to make one.</p> <img src="https://shkspr.mobi/blog/wp-content/uploads/2026/05/Combined-QR-1D-Gap.webp" alt="A QR code with a 1D barcode embedded in it." width="400" height="400" class="aligncenter size-full wp-image-70975"> <p>If you move your phone close to the code (so it can't see the squares in the corners) it should read the number in the 1D barcode. Zoom out and it'll read the URl in the QR code.</p> <p>The QR code has a high level of error correction - which allows graphics to be placed within it, <a href="https://shkspr.mobi/blog/2010/11/hiding-space-invaders-in-qr-codes/">as I discussed in 2010</a>.</p> <p>The UPC has some whitespace padding around its edges - which makes it easier for some scanners to find, although not all scanners seem to accept it.</p> <p>Is this in any way useful or desirable? I doubt it! I guess most point-of-sale barcode scanners are somewhat regularly updated - so they should all have the ability to scan newer codes. The embedded code destroys some of the error correction, thus making the QR code more fragile. <a href="https://mastodon.social/@PhilA/116504516987179728">It isn't a good idea</a>.</p> <p>Still, nice to fiddle about with something, eh?</p> <p>You may also enjoy:</p> <ul> <li><a href="https://shkspr.mobi/blog/2025/03/a-recursive-qr-code/">A Recursive QR Code</a>.</li> <li><a href="https://shkspr.mobi/blog/2025/02/why-are-qr-codes-with-capital-letters-smaller-than-qr-codes-with-lower-case-letters/">Why are QR Codes with capital letters smaller than QR codes with lower-case letters?</a></li> <li>Or see <a href="https://shkspr.mobi/blog/tag/qr+QR-codes/?order=asc">all my posts about QR Codes since 2007</a>!</li> </ul> <img src="https://shkspr.mobi/blog/wp-content/themes/edent-wordpress-theme/info/okgo.php?ID=70974&HTTP_REFERER=Atom" alt width="1" height="1" loading="eager"> Small wishes, II - James' Coffee Blog https://jamesg.blog/2026/07/04/small-wishes-ii 2026-07-04T00:00:00.000Z <blockquote><a href="https://jamesg.blog/2026/06/21/small-wishes" rel="noreferrer">June 21st, 2026</a> — After my coffee, a thought came to mind: I hope that one day I can see the countryside from a double-decker bus. It’s a small wish. It may never happen but I hold onto the thought any way.</blockquote><p>I stood at the bus terminal today thinking that it was only a few weeks ago I had wished to go on a double decker bus. Today, it happened: a most delightful surprise.</p><p>I arrived at the bus early enough that I had my pick of seats. I sat on the top deck in the front seat, where I had almost panoramic views of the countryside. Everything looked different. I was thrilled.</p><p>Toward the end of the journey, I saw two children jumping up and down with excitement. Their granny was with them. Getting on the bus, the family came to the top deck and sat in the three remaining chairs at the front, the granny sitting next to me. The granny mentioned how they were only going to the next stop, a few hundred meters away. The driver had stopped the bus until they were all in their seats, and knew exactly where to stop.</p><p>Looking forward with awe from the seats with the best view on the bus, the children were elated.</p><p>Toward the end of the journey, said in the kind of excited voice that a child can speak with such ease, I heard:</p><blockquote>“Granny, your house is like a double decker!”</blockquote><p>I smiled at this observation, and the tone in which it was delivered.</p><p>The family got off and continued their journey, the children delighted from their view of the world from a double decker. I stayed on for a few more stops, watching the world go by and thinking about how the small things really bring joy to the day.</p><script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a1608abb79f2d8c0',t:'MTc4MzE5MzM5OQ=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&amp;&amp;(document.onreadystatechange=e,c())}}}})();</script> <a class="tag" href="https://jamesg.blog/2026/06/21/small-wishes">June 21st, 2026</a> Deduplicating posts after URL changes in Artemis - James' Coffee Blog https://jamesg.blog/2026/07/04/artemis-url-deduplication 2026-07-04T00:00:00.000Z <style media="(prefers-color-scheme: dark)">pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #49483e } .highlight { background: #272822; color: #F8F8F2 } .highlight .c { color: #959077 } /* Comment */ .highlight .err { color: #ED007E; background-color: #1E0010 } /* Error */ .highlight .esc { color: #F8F8F2 } /* Escape */ .highlight .g { color: #F8F8F2 } /* Generic */ .highlight .k { color: #66D9EF } /* Keyword */ .highlight .l { color: #AE81FF } /* Literal */ .highlight .n { color: #F8F8F2 } /* Name */ .highlight .o { color: #FF4689 } /* Operator */ .highlight .x { color: #F8F8F2 } /* Other */ .highlight .p { color: #F8F8F2 } /* Punctuation */ .highlight .ch { color: #959077 } /* Comment.Hashbang */ .highlight .cm { color: #959077 } /* Comment.Multiline */ .highlight .cp { color: #959077 } /* Comment.Preproc */ .highlight .cpf { color: #959077 } /* Comment.PreprocFile */ .highlight .c1 { color: #959077 } /* Comment.Single */ .highlight .cs { color: #959077 } /* Comment.Special */ .highlight .gd { color: #FF4689 } /* Generic.Deleted */ .highlight .ge { color: #F8F8F2; font-style: italic } /* Generic.Emph */ .highlight .ges { color: #F8F8F2; font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #F8F8F2 } /* Generic.Error */ .highlight .gh { color: #F8F8F2 } /* Generic.Heading */ .highlight .gi { color: #A6E22E } /* Generic.Inserted */ .highlight .go { color: #66D9EF } /* Generic.Output */ .highlight .gp { color: #FF4689; font-weight: bold } /* Generic.Prompt */ .highlight .gs { color: #F8F8F2; font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #959077 } /* Generic.Subheading */ .highlight .gt { color: #F8F8F2 } /* Generic.Traceback */ .highlight .kc { color: #66D9EF } /* Keyword.Constant */ .highlight .kd { color: #66D9EF } /* Keyword.Declaration */ .highlight .kn { color: #FF4689 } /* Keyword.Namespace */ .highlight .kp { color: #66D9EF } /* Keyword.Pseudo */ .highlight .kr { color: #66D9EF } /* Keyword.Reserved */ .highlight .kt { color: #66D9EF } /* Keyword.Type */ .highlight .ld { color: #E6DB74 } /* Literal.Date */ .highlight .m { color: #AE81FF } /* Literal.Number */ .highlight .s { color: #E6DB74 } /* Literal.String */ .highlight .na { color: #A6E22E } /* Name.Attribute */ .highlight .nb { color: #F8F8F2 } /* Name.Builtin */ .highlight .nc { color: #A6E22E } /* Name.Class */ .highlight .no { color: #66D9EF } /* Name.Constant */ .highlight .nd { color: #A6E22E } /* Name.Decorator */ .highlight .ni { color: #F8F8F2 } /* Name.Entity */ .highlight .ne { color: #A6E22E } /* Name.Exception */ .highlight .nf { color: #A6E22E } /* Name.Function */ .highlight .nl { color: #F8F8F2 } /* Name.Label */ .highlight .nn { color: #F8F8F2 } /* Name.Namespace */ .highlight .nx { color: #A6E22E } /* Name.Other */ .highlight .py { color: #F8F8F2 } /* Name.Property */ .highlight .nt { color: #FF4689 } /* Name.Tag */ .highlight .nv { color: #F8F8F2 } /* Name.Variable */ .highlight .ow { color: #FF4689 } /* Operator.Word */ .highlight .pm { color: #F8F8F2 } /* Punctuation.Marker */ .highlight .w { color: #F8F8F2 } /* Text.Whitespace */ .highlight .mb { color: #AE81FF } /* Literal.Number.Bin */ .highlight .mf { color: #AE81FF } /* Literal.Number.Float */ .highlight .mh { color: #AE81FF } /* Literal.Number.Hex */ .highlight .mi { color: #AE81FF } /* Literal.Number.Integer */ .highlight .mo { color: #AE81FF } /* Literal.Number.Oct */ .highlight .sa { color: #E6DB74 } /* Literal.String.Affix */ .highlight .sb { color: #E6DB74 } /* Literal.String.Backtick */ .highlight .sc { color: #E6DB74 } /* Literal.String.Char */ .highlight .dl { color: #E6DB74 } /* Literal.String.Delimiter */ .highlight .sd { color: #E6DB74 } /* Literal.String.Doc */ .highlight .s2 { color: #E6DB74 } /* Literal.String.Double */ .highlight .se { color: #AE81FF } /* Literal.String.Escape */ .highlight .sh { color: #E6DB74 } /* Literal.String.Heredoc */ .highlight .si { color: #E6DB74 } /* Literal.String.Interpol */ .highlight .sx { color: #E6DB74 } /* Literal.String.Other */ .highlight .sr { color: #E6DB74 } /* Literal.String.Regex */ .highlight .s1 { color: #E6DB74 } /* Literal.String.Single */ .highlight .ss { color: #E6DB74 } /* Literal.String.Symbol */ .highlight .bp { color: #F8F8F2 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #A6E22E } /* Name.Function.Magic */ .highlight .vc { color: #F8F8F2 } /* Name.Variable.Class */ .highlight .vg { color: #F8F8F2 } /* Name.Variable.Global */ .highlight .vi { color: #F8F8F2 } /* Name.Variable.Instance */ .highlight .vm { color: #F8F8F2 } /* Name.Variable.Magic */ .highlight .il { color: #AE81FF } /* Literal.Number.Integer.Long */ .highlight .nn, .highlight .n{color: light-dark(black, var(--dark-foreground-color)) }</style><style media="(prefers-color-scheme: light)">pre { line-height: 125%; } td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } .highlight .hll { background-color: #ffffcc } .highlight { background: #f8f8f8; } .highlight .c { color: #3D7B7B; font-style: italic } /* Comment */ .highlight .err { border: 1px solid #F00 } /* Error */ .highlight .k { color: #008000; font-weight: bold } /* Keyword */ .highlight .o { color: #666 } /* Operator */ .highlight .ch { color: #3D7B7B; font-style: italic } /* Comment.Hashbang */ .highlight .cm { color: #3D7B7B; font-style: italic } /* Comment.Multiline */ .highlight .cp { color: #9C6500 } /* Comment.Preproc */ .highlight .cpf { color: #3D7B7B; font-style: italic } /* Comment.PreprocFile */ .highlight .c1 { color: #3D7B7B; font-style: italic } /* Comment.Single */ .highlight .cs { color: #3D7B7B; font-style: italic } /* Comment.Special */ .highlight .gd { color: #A00000 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */ .highlight .gr { color: #E40000 } /* Generic.Error */ .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ .highlight .gi { color: #008400 } /* Generic.Inserted */ .highlight .go { color: #717171 } /* Generic.Output */ .highlight .gp { color: #000080; font-weight: bold } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ .highlight .gt { color: #04D } /* Generic.Traceback */ .highlight .kc { color: #008000; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008000 } /* Keyword.Pseudo */ .highlight .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #B00040 } /* Keyword.Type */ .highlight .m { color: #666 } /* Literal.Number */ .highlight .s { color: #BA2121 } /* Literal.String */ .highlight .na { color: #687822 } /* Name.Attribute */ .highlight .nb { color: #008000 } /* Name.Builtin */ .highlight .nc { color: #00F; font-weight: bold } /* Name.Class */ .highlight .no { color: #800 } /* Name.Constant */ .highlight .nd { color: #A2F } /* Name.Decorator */ .highlight .ni { color: #717171; font-weight: bold } /* Name.Entity */ .highlight .ne { color: #CB3F38; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #00F } /* Name.Function */ .highlight .nl { color: #767600 } /* Name.Label */ .highlight .nn { color: #00F; font-weight: bold } /* Name.Namespace */ .highlight .nt { color: #008000; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #19177C } /* Name.Variable */ .highlight .ow { color: #A2F; font-weight: bold } /* Operator.Word */ .highlight .w { color: #BBB } /* Text.Whitespace */ .highlight .mb { color: #666 } /* Literal.Number.Bin */ .highlight .mf { color: #666 } /* Literal.Number.Float */ .highlight .mh { color: #666 } /* Literal.Number.Hex */ .highlight .mi { color: #666 } /* Literal.Number.Integer */ .highlight .mo { color: #666 } /* Literal.Number.Oct */ .highlight .sa { color: #BA2121 } /* Literal.String.Affix */ .highlight .sb { color: #BA2121 } /* Literal.String.Backtick */ .highlight .sc { color: #BA2121 } /* Literal.String.Char */ .highlight .dl { color: #BA2121 } /* Literal.String.Delimiter */ .highlight .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */ .highlight .s2 { color: #BA2121 } /* Literal.String.Double */ .highlight .se { color: #AA5D1F; font-weight: bold } /* Literal.String.Escape */ .highlight .sh { color: #BA2121 } /* Literal.String.Heredoc */ .highlight .si { color: #A45A77; font-weight: bold } /* Literal.String.Interpol */ .highlight .sx { color: #008000 } /* Literal.String.Other */ .highlight .sr { color: #A45A77 } /* Literal.String.Regex */ .highlight .s1 { color: #BA2121 } /* Literal.String.Single */ .highlight .ss { color: #19177C } /* Literal.String.Symbol */ .highlight .bp { color: #008000 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #00F } /* Name.Function.Magic */ .highlight .vc { color: #19177C } /* Name.Variable.Class */ .highlight .vg { color: #19177C } /* Name.Variable.Global */ .highlight .vi { color: #19177C } /* Name.Variable.Instance */ .highlight .vm { color: #19177C } /* Name.Variable.Magic */ .highlight .il { color: #666 } /* Literal.Number.Integer.Long */</style> <style> @font-face { font-family: 'MonaspaceArgon'; src: url('/assets/fonts/MonaspaceArgon-Regular.woff2') format('woff2'); font-weight: 400; font-style: normal; } pre, code { font-family: 'MonaspaceArgon', ui-monospace, monospace; } </style> <p>Occasionally, sites that publish web feeds change the URL of a post that has just been published. This can be for several reasons. For example, I sometimes change post permalinks immediately after publishing because I notice a typo, or because the CMS I use calculated a default permalink that I don’t want to use and I forgot to change it.</p><p>Until now, <a href="https://artemis.jamesg.blog" rel="noreferrer">Artemis</a>, the calm web reader I maintain, has done nothing if a post has changed its permalink. Importantly, Artemis considers the post URL as a unique identifier. This means that if a post changes its URL, a new record for the post will be added to the database. Today, however, I have added logic to deduplicate posts where the title is the same but the URL has changed.</p><p>The way it works is as follows. When a user’s feed is computed, Artemis checks how many posts published by the same author have the same title per day. If two or more posts have the same title in a day, Artemis will show the most recent version of the post. All other instances of the post will be hidden. This logic is based on the assumption that the most recent URL should be correct.</p><p>With this logic, posts with these titles and permalinks would be merged:</p><ul><li><code>https://jamesg.blog/2026/01/01/testing</code> with the title "Test", published at 00:01.</li><li><code>https://jamesg.blog/2026/01/01/test</code> with the title "Test", published at 00:04.</li></ul><p>The final entry would be:</p><ul><li><code>https://jamesg.blog/2026/01/01/test</code> with the title "Test".</li></ul><p>This is because the permalink that ends in <code>/test</code> was published most recently.</p><p>There is a notable edge case for this implementation: if a site posts two blog posts in the same day with the same title, only the most recent permalink will be displayed. While writing this post, I took a detour to see if I should also compare the hashes of the post content to determine whether the posts are the same. Looking at the instance that inspired me to work on this change, the content on the page whose URL was later changed didn’t have the same hash after the URL was changed: the post content had also changed.</p><p>In addition, this implementation depends on the post title staying the same even if the URL changes, which means that if an author changes the title and URL of a post then there will still be two entries in a user’s feed <sup class="footnote-reference" id="f-1"><a href="https://jamesg.blog/longform-feed#1">1</a></sup>.</p><p>As I write, I am thinking about whether Artemis should visually show that a URL has been chosen as canonical. This would allow a user to see that two posts are likely to be the same, and then the user could manually check if they were interested. With that said, perhaps this would add too much clutter to the interface? I will have to think more about this.</p><p>In any case, the post deduplication feature is now live in Artemis. I hope this change will keep the reader interface cleaner in cases where authors change post URLs.</p> <div class="footnote-definition" id="1"><sup class="footnote-definition-label" id="f-2">1</sup> <p>Ideally, a site will add a redirect to the new page in any case, but this doesn’t always happen.</p> <a href="https://jamesg.blog/longform-feed#f-1">[↩]</a></div> <a class="tag" href="https://jamesg.blog/longform-feed#1">1</a> <a class="tag" href="https://jamesg.blog/longform-feed#f-1">[↩]</a> Notable links: July 3, 2026 - Werd I/O 6a47bfb182c96000019ca43f 2026-07-03T14:09:16.000Z <img src="https://storage.ghost.io/c/18/7c/187cc681-d3f3-49fc-87de-b01d06b76821/content/images/2026/07/lianhao-qu-LfaN1gswV5c-unsplash.jpg" alt="Notable links: July 3, 2026"><p><em>Most Fridays, I share a handful of pieces that caught my eye at the intersection of technology, media, and society.</em></p><p><em>Did I miss something important? </em><a href="mailto:ben@werd.io" rel="noreferrer"><em>Send me an email</em></a><em> to let me know.</em></p><p><em>Did someone forward this to you? </em><a href="https://werd.io/notable-links-june-26-2026/#/portal" rel="noreferrer"><em>Subscribe for free</em></a><em>.</em></p><hr><h3 id="openai-proposes-handing-trump-administration-5-stake"><a href="https://www.ft.com/content/7c803eab-8e80-4431-9a87-e943bf00e00b?syn-25a6b1a6=1&amp;ref=werd.io" rel="noreferrer">OpenAI proposes handing Trump administration 5% stake</a></h3><p>In order to ward off backlash against AI and curry favor with the Trump administration, Sam Altman has floated the idea of giving 5% of OpenAI to a wealth fund that pays dividends to both the government and citizens &#x2014; and that every leading AI vendor should do the same.</p><blockquote>&#x201C;Sam Altman, chief executive of the ChatGPT maker, has argued that giving the public a financial stake in the company is the best way to share the upside of AI and has suggested a stake of this size in early conversations with the administration, according to two people familiar with the talks.&#x201D;</blockquote><p>It&#x2019;s transparently a way to align everyone with AI vendor profits. If the sector increases in value, the government and the voting population benefit. If it <em>decreases</em> in value &#x2026; well, the government is incentivized to prevent that from happening. It also wouldn&#x2019;t be without precedent: it&#x2019;s modeled on the Alaska Permanent Fund, which does this with oil profits for Alaskan residents. Intel is also now 10% government-owned, and the administration has reversed course to be behind it since gaining that stake.</p><p>Would a government whose revenues are directly linked to the performance of a sector be likely to enact hard regulations on that sector? Perhaps not. It&#x2019;s not a slam dunk, though: for example, the UK receives significant tax revenue on fossil fuels, but still promoted electric cars. There are lots of factors at play, and profit alignment isn&#x2019;t necessarily outweighed by the effects of other harms. (See also: cigarettes, which are taxed but also tightly controlled as an addictive carcinogen.)</p><p>Meanwhile, Bernie Sanders has pushed for closer to 50% ownership through a sovereign wealth fund. At this much lower stake, Sam Altman&#x2019;s proposal uses Sanders&#x2019;s democratic socialist &#x201C;share the wealth&#x201D; language as a way to launder OpenAI&#x2019;s profits through a thin veneer of good ethics.</p><p>What&#x2019;s also interesting to me is that all of these arguments assume that AI is going to be an enormous driver of wealth and innovation &#x2014; but what if it isn&#x2019;t? It&#x2019;s another great way to advertise the technology as something world-changing that everybody must get behind right now.</p><p>Even if AI turns out to be what the people heavily invested in its success say it will be, it doesn&#x2019;t stand alone as a sea change innovation. The personal computer, the iPhone, word processors, and spreadsheets were pretty transformational technologies. Should there have been a wealth fund attached to each of those? What, exactly, makes AI different?</p><p>The answer is that it represents labor displacement: people will lose their jobs. And if that&#x2019;s actually going to be the case, we need bigger, more structural safety nets and reforms. Dividends from 5% of a sector aren&#x2019;t going to replace wages at scale &#x2014; and are heavily dependent on valuations continuing to rise. This proposal ties the welfare of people who have lost their jobs to the success of the companies that drove those losses. The incentives are perverse.</p><p>We shouldn&#x2019;t accept this proposal. Instead, we should push for stronger protections and stronger regulation. If a sector can&#x2019;t succeed without real damage to working communities, then it must not be allowed to. And if these claims turn out not to be true, then it&#x2019;s an empty gesture designed to add credibility to a self-interested science fiction view of the future.</p><hr><h3 id="companies-are-making-claude-and-codex-talk-like-cavemen-to-stop-ai%E2%80%99s-soaring-costs"><a href="https://www.404media.co/companies-are-making-claude-and-codex-talk-like-cavemen-to-stop-ais-soaring-costs/?ref=werd.io" rel="noreferrer">Companies Are Making Claude and Codex Talk Like Cavemen to Stop AI&#x2019;s Soaring Costs</a></h3><p>I find this very funny:</p><blockquote>&#x201C;Companies are deliberately making their AI tools speak like cavemen in an attempt to stop burning through AI tokens and curb their massive expenditure on AI, 404 Media has found. The tool turns the usually verbose outpost of LLMs like Claude Code, Codex, or Gemini into a much more to the point answer. Think less &#x201C;you&#x2019;re right to push back, I was wrong,&#x201D; and more &#x201C;Hulk smash.&#x201D;&#x201D;</blockquote><p>If only we had other limited-vocabulary lexicons designed to talk to computers efficiently!</p><p>I think we&#x2019;re circling a few different possibilities that may show up over the next few years:</p><ul><li>Literally LLM-specific &#x201C;programming languages&#x201D; that humans can use to talk to models more efficiently, of which Caveman is the hilarious first step</li><li>A proprietary bytecode-like language for LLMs that makes interactions more efficient but also just happens to be owned by one of the major vendors and creates a hitherto-unobtainable moat for their business</li><li>This all becomes moot when local models become viable for most businesses without insanely high hardware prices or configuration costs</li><li>LLM costs eventually fall to a fraction of their existing level</li></ul><p>But who knows? Maybe enterprise businesses will continue to talk in stilted caveman language to achieve their business goals forever.</p><hr><h3 id="journalism-has-the-receipts-it-won%E2%80%99t-use-them"><a href="https://www.backstoryandstrategy.com/p/journalism-has-the-receipts-it-wont?ref=werd.io" rel="noreferrer">Journalism Has the Receipts. It Won&#x2019;t Use Them.</a></h3><p>Arts organizations learned long ago to prove their economic value with hard numbers: attendance, tourism revenue, multiplier effects. News, as Yoni Greenbaum argues here, likes to cling to civic virtue and assume that the work should speak for itself.</p><blockquote>&#x201C;Journalism operated on a commercial advertising revenue model for over 150 years. Publishers sold readers to advertisers, while editors fretted about maintaining a church-and-state divide between the newsroom and business desk. Journalists saw themselves as watchdogs, not wealth generators. Pitching our value based on our own economic impact felt gauche, too close to an advertorial.&#x201D;</blockquote><p>Yoni points out that this is starting to change. <a href="https://www.rebuildlocalnews.org/new-report-reveals-local-news-shortage-is-costing-communities-1-1-billion-a-year/?ref=werd.io">We know that news deserts cost communities at least $1.1B a year</a>, for example, because of a report by Rebuild Local News and the University of Illinois Chicago. But newsrooms themselves tend to shy away from reporting their own economic impact &#x2014; even though they already have the tools to do so.</p><p>It&#x2019;s not obvious to me that this accounting would work as an argument across the board for newsrooms, and particularly for those with a national focus. Does ProPublica (my employer until the end of the month) save anyone money? It certainly does prevent corruption, and there are instances with real dollar amounts attached to them: Intuit, for example, paid back $141 million to its customers over deceptive marketing. But I&#x2019;m not sure that its impact can be quantified easily overall, despite the newsroom&#x2019;s obvious public benefit. On the other hand, for <em>local</em> newsrooms, this makes a lot of sense to me: at their best, they act as connective tissue for their communities. That $1.1B a year was <em>just</em> increased interest costs from lenders who felt they could charge more to unmonitored governments.</p><p>They just need to get more comfortable at telling the economic side of their stories. And there&#x2019;s a wider point here, which is that almost all nonprofit newsrooms need to be able to get more concrete and detailed about their business models. If you&#x2019;re running an organization that wants to be sustainable, it&#x2019;s not enough to care about the journalistic process, and your business accounting cannot be limited to activities like events and merchandise. You actually have to care about building a business holistically, and everything that entails.</p><hr><h3 id="cascade-pbs-launches-local-public-as-standalone-streaming-tech-company"><a href="https://thedesk.net/2026/07/local-public-launches-pbs-member-stations-apps/?ref=werd.io" rel="noreferrer">Cascade PBS launches Local Public as standalone streaming tech company</a></h3><p>I&#x2019;ve got some complicated feelings about this announcement from Cascade PBS:</p><blockquote>&#x201C;Cascade PBS, the non-profit television station serving western Washington state, has spun out its technology division into a separate company that will help similar public broadcasters carve out their own streaming and digital identities.<br><br>The new company, Local Public, will help develop streaming applications for connected TVs, mobile devices and the web, allowing public television stations to offer locally branded streaming experiences featuring their own programming alongside national PBS content.&#x201D;</blockquote><p>On one hand, I absolutely love that they were able to spin out their technology division. Most public media companies don&#x2019;t have the resources or skills to build their own tech, and building this capability outside of any one station so that all of them can take advantage of it makes a lot of sense to me.</p><p>The Local Public site itself <a href="https://www.localpublic.tv/pricing/?ref=werd.io">also makes the ROI transparent</a>. WETA, the public media station for Greater Washington, ran the numbers and said that it would break even in the first year, and a calculator is available for other stations that want to check for themselves. The pricing hinges on Passport-eligible donors: those giving at least $60 a year. Local Public charges $60,000 a year for stations with fewer than 15,000, $75,000 for up to 40,000, and $100,000 a year for everyone else &#x2014; which is not out of bounds. It all seems like a decent business, run in the public interest as a subsidiary of Cascade PBS, that will genuinely help public media stations. I want to see more of this.</p><p>But I do wish it was fully based on open technology. While stations gain the right to modify the source code of their <em>apps</em> after a year, they remain locked into Local Public&#x2019;s back-end services. For the CMS, which builds network effects the more stations use it, stations can only get support, maintenance, and customization through Local Public. Over time, that lock-in does not incentivize great support, and Local Public will need to work hard to buck the trends. NPR&#x2019;s CMS, for example, is notorious among the stations that have to use it. I&#x2019;m certain the will is there to do better, but they will need to proceed with intention. In my opinion it would be better if, at least after establishing a customer base, they open sourced their back-end CMS too.</p><p>I tend to think that <em>any</em> technology provided to support the public interest should be fully open. That doesn&#x2019;t mean there isn&#x2019;t a tidy business available to its creator &#x2014; ask <a href="https://ghost.org/?ref=werd.io">Ghost</a>, which is generating millions of dollars off the back of its open source CMS. If there&#x2019;s a class of organization that absolutely doesn&#x2019;t deserve to be locked into a technology stack, it&#x2019;s public service broadcasters.</p><p>This isn&#x2019;t Cascade PBS&#x2019;s fault. It needs its spinout to be sustainable, and this model feels like it will hit that goal. The best scenario, in my mind, would be if there were central funders who bankrolled open tech that the whole ecosystem could use. But, of course, it&#x2019;s 2026, and central funding for <em>anything</em> public media is hard to come by.</p><p>Still, this is wonderful to see, and anything that encourages collaboration on a technical level between public service media organizations deserves support.</p><hr><h3 id="emergency-mode-for-news"><a href="https://emergencymode.news/?ref=werd.io" rel="noreferrer">Emergency Mode for news</a></h3><p>Emergency Mode is a set of resources, tools, and training that aims to prepare small newsrooms for various disasters. It&#x2019;s a co-production between <a href="https://opennews.org/?ref=werd.io">OpenNews</a>, <a href="https://nlocal.org/?ref=werd.io">NC Local</a>, and <a href="https://newspack.com/?ref=werd.io">Newspack</a>. They&#x2019;ve done a great job. As their about page puts it:</p><blockquote>&#x201C;Emergency Mode for News equips local journalists and their newsrooms with the tools they need to respond to climate disasters. With a disaster reporting action pack, software and a learning community, Emergency Mode is designed to help journalists act nimbly and creatively to serve their communities when the unexpected happens.&#x201D;</blockquote><p>Toolkits include things like <a href="https://docs.google.com/document/d/1iEvYxJf1dnQ8jXS8IhBeqh9GrkTsxCqBjehG66aRJmA/edit?tab=t.0&amp;ref=werd.io#heading=h.t2e0k24sy4l4">a practical checklist for newsrooms covering wildfires</a> and <a href="https://docs.google.com/document/d/1dIeAA0yo3OXJKfjT4cwaPkZd01stJqaPD3kRZMpIsDA/edit?tab=t.0&amp;ref=werd.io#heading=h.k4t85kcanet">a template for maintaining source lists during an emergency</a>. There&#x2019;s also <a href="https://emergencymode.news/training/?ref=werd.io">a hands-on workshop series</a> and tools like WordPress plugins for <a href="https://emergencymode.news/tools/?ref=werd.io">live rolling news updates</a> and providing bandwidth-light versions of sites.</p><p>Most of all, I really appreciate the practical nature of all of it. Rather than hand-waving about principles and ideas, as many newsroom-facing resources do, everything here is a concrete tool that can actively be used in the field. Newsrooms are more squeezed than they&#x2019;ve ever been, so it doesn&#x2019;t hurt that it&#x2019;s all free.</p><p>I&#x2019;d love to see this level of concrete specificity for <em>the normal working of a newsroom</em>. Wouldn&#x2019;t it be cool to have a list of business model checklists you could pull from? Or disaster recovery plans? Or data protection policies? Just as the tools on this site are going to be concretely useful to any newsroom that covers a disaster, checklists, tools, and training for standard operational practices could be really meaningful &#x2014; particularly for smaller newsrooms that don&#x2019;t have the ability to hire CTOs, CFOs, and so on.</p><p>In other words: more, please. This is lovely.</p><hr><h3 id="the-quiet-erosion-of-collective-action-under-digital-surveillance"><a href="https://www.techpolicy.press/the-quiet-erosion-of-collective-action-under-digital-surveillance/?ref=werd.io" rel="noreferrer">The Quiet Erosion of Collective Action Under Digital Surveillance</a></h3><p>The most important outcome of increased surveillance is a chilling effect on free speech and expression. As Gina Romero, the United Nations Special Rapporteur on the Rights to Freedom of Peaceful Assembly, notes here, that extends to the organizations that have been established to protect those rights:</p><blockquote>&#x201C;As organizations operate under the constant assumption that they are being monitored, their core functions are profoundly affected. Their ability to serve as watchdogs, provide rights-based services, protect victims of human rights abuses, and educate the public is severely constrained. Ultimately, the very possibility of advancing and protecting rights, democracy and the rule of law is undermined.&#x201D;</blockquote><p>Civil society organizations and advocates have been mislabeled as national security threats around the world. It&#x2019;s true in some of the nations that we&#x2019;ve long thought of as being authoritarian, but it&#x2019;s also true in the United States. Even places like the United Kingdom have tried to apply pressure to technology companies so that they can gain access to backdoors.</p><p>Tools like <a href="https://signal.org/?ref=werd.io">Signal</a> have become all the more important. We need more easy to use end to end encrypted systems so that we can communicate and organize with each other without fear of government surveillance. That also allows whistleblowers and sources for journalists to reach out with less of a fear they they will suffer repercussions.</p><p>But those tools don&#x2019;t stop you from being surveilled in the real world. Cameras and microphones are everywhere; license plate readers are now commonplace; even <a href="https://theconversation.com/world-cup-propels-surveillance-to-new-heights-284712?ref=werd.io">AI-enabled drones have been deployed</a> for events like the World Cup.</p><p>It&#x2019;s generally true that if government <em>can</em> do something, it will. So the only way to stop this kind of widespread surveillance is to make it impossible. Romero calls for legislative prevention that takes into account the whole systemic impact of surveillance rather than just the immediate first-order effects. Her report also calls out that it can be very difficult to challenge these systems because what they are and who owns them tends to be complicated or obfuscated:</p><blockquote>&#x201C;The study reveals a lack of transparency surrounding the relationship between state power and non-state actors, creating an information vacuum that makes surveillance practices exceedingly difficult to challenge through litigation. As a result, the right to an effective remedy is fundamentally weakened.&#x201D;</blockquote><p>So I think we also need more technical capabilities that interfere with how these systems of surveillance actually work. We need more spaces that are designated privacy-first and enforce an anti-surveillance rulebook. And, just as <a href="https://techcrunch.com/2026/02/23/americans-are-destroying-flock-surveillance-cameras/?ref=werd.io">communities have taken it upon themselves to dismantle Flock cameras</a>, we need to take back our streets.</p><hr><h3 id="openai-will-delay-gpt-56-after-trump-administration-request"><a href="https://www.theverge.com/ai-artificial-intelligence/957372/openai-will-delay-gpt-5-6-after-trump-administration-request?ref=werd.io" rel="noreferrer">OpenAI will delay GPT-5.6 after Trump administration request</a></h3><p>I&#x2019;ve got (at least) two worries about the story that the Trump Administration halted the release of models from both Anthropic and OpenAI.</p><p>Anthropic <a href="https://www.anthropic.com/news/fable-mythos-access?ref=werd.io">recently pulled its Fable model release</a> in response to the government. Now it turns out that OpenAI has done something similar:</p><blockquote>&#x201C;The Information reported that OpenAI CEO Sam Altman told employees Wednesday in a company Q&amp;A that it would release GPT-5.6 in limited preview form &#x2014; granting access only to a small group of enterprise customers &#x2014; in compliance with a request from the federal government. During that preview period, the Trump administration itself would reportedly approve access for customers on a case-by-case basis.&#x201D;</blockquote><p>In some ways, what a coup for the AI industry. This technology is so powerful that the government doesn&#x2019;t think anyone should have it &#x2014; and when it does inevitably release into the public&#x2019;s hands, what a valuable product that will be. Get the tech that&#x2019;s too dangerous to be released! This magical product can be yours for an unbelievable price!</p><p>So one worry is that this is, in essence, great marketing for these vendors.</p><p>But it&#x2019;s worth remembering that these AI models are black boxes that respond to information queries in opaque ways. The more people rely on them for knowledge, the more powerful the models become. The argument being presented is that they can be used in ways that might present traditional security threats &#x2014; but consider that some versions of the truth, to the wrong kind of authoritarian-minded government, might also be considered a threat. (Remember that &#x201C;extremism on migration, race, and gender&#x201D; and hostility to &#x201C;traditional American views&#x201D; <a href="https://theconversation.com/labeling-dissent-as-terrorism-new-us-domestic-terrorism-priorities-raise-constitutional-alarms-269161?ref=werd.io">are now considered markers of domestic terrorism</a>.)</p><p>This is a golden opportunity, in other words, to hit pause on frontier model releases, at a time when models are becoming more prevalent, in order to make sure models are shaped to represent a certain version of the world. The administration <a href="https://www.whitehouse.gov/fact-sheets/2025/07/fact-sheet-president-donald-j-trump-prevents-woke-ai-in-the-federal-government/?ref=werd.io">has already signaled a willingness to do this</a>; there is nothing to say they aren&#x2019;t. The only way to <em>prove</em> that they aren&#x2019;t is to open source not just the models but the training process and make the whole thing transparent and verifiable. The industry is a long way off from doing that.</p><hr><h3 id="now-we%E2%80%99re-getting-ai-fake-news-complaining-about-how-ai-fake-news-is-the-death-of-real-news"><a href="https://www.niemanlab.org/2026/07/now-were-getting-ai-fake-news-complaining-about-how-ai-fake-news-is-the-death-of-real-news/?ref=werd.io" rel="noreferrer">Now we&#x2019;re getting AI fake news complaining about how AI fake news is the death of real news </a></h3><p>A bunch of people &#x2014; <a href="https://werd.io/a-right-wing-media-chain-tried-to-replace-47-newspapers-with-ai-they-all-died/">including, unfortunately, me</a> &#x2014; were taken in by this AI-generated newsroom earlier this week. The story was decently written and seemed to be well-cited, but it turned out to be nonsense. Ironically, it was about a would-be media empire that purchased struggling papers, fired their staff, and replaced them with AI, leading to the death of each newsroom. All false.</p><p>So the big question about The Editorial is: why does it exist?</p><p><a href="https://www.niemanlab.org/2026/07/now-were-getting-ai-fake-news-complaining-about-how-ai-fake-news-is-the-death-of-real-news/?ref=werd.io">As Joshua Benton put it:</a></p><blockquote>&#x201C;Fake news isn&#x2019;t new, obviously. And while AI-generated slop is newer, it&#x2019;s hardly unfamiliar at this point. But why would a spam site bother making up a story about Alabama weekly newspapers, of all things? Whose interest is it in to get that niche?&#x201D;</blockquote><p>Here&#x2019;s my theory: I think it&#x2019;s a two-headed LLM poisoning scheme.</p><p>On one hand, most of the content relates to Chinese-specific interests: articles about Taiwan or African nations where China is making inroads. These are all articles from a China-friendly perspective. If an LLM were to ingest them and <em>trust the site</em>, it might start repeating the assertions made in each one as fact.</p><p>One way to make sure a site is trusted is to get other, trusted sources to point to it. That&#x2019;s where the stories about journalism come in: there are few things that journalists engage in more than stories about their own industry. Get enough patsies (like, again, to my chagrin, <em>me</em>) to point links in their direction and journalists might post them in high-trust communities on high-trust sites like Reddit, as well as their own, and Bob&#x2019;s your uncle. <a href="https://werd.io/all-you-need-to-poison-an-llm-is-13-words/">We already know that it takes as little as 13 words</a> to poison an LLM with falsehoods.</p><p>Of course, that might not be it at all. Frank, the site&#x2019;s owner (who lists himself as CEO of Nordiso Group on LinkedIn), at least appears to be a Finnish solopreneur. If he wanted to clear the air, he could write a post (himself) about what he was up to. It might be that he&#x2019;s running an experiment to see how easily an LLM can be poisoned with propaganda! Until then, I think it&#x2019;s reasonable to assume that something underhand is going on.</p> A new media spinout provides streaming apps for public service broadcasters. I just wish it was open. - Werd I/O 6a47bbda82c96000019ca432 2026-07-03T13:40:42.000Z <p>Link: <a href="https://thedesk.net/2026/07/local-public-launches-pbs-member-stations-apps/?ref=werd.io"><em>Cascade PBS launches Local Public as standalone streaming tech company, by Matthew Keys at The Desk</em></a></p><p>I&#x2019;ve got some complicated feelings about this announcement from Cascade PBS:</p><blockquote>&#x201C;Cascade PBS, the non-profit television station serving western Washington state, has spun out its technology division into a separate company that will help similar public broadcasters carve out their own streaming and digital identities.<br><br>The new company, Local Public, will help develop streaming applications for connected TVs, mobile devices and the web, allowing public television stations to offer locally branded streaming experiences featuring their own programming alongside national PBS content.&#x201D;</blockquote><p>On one hand, I absolutely love that they were able to spin out their technology division. Most public media companies don&#x2019;t have the resources or skills to build their own tech, and building this capability outside of any one station so that all of them can take advantage of it makes a lot of sense to me.</p><p>The Local Public site itself <a href="https://www.localpublic.tv/pricing/?ref=werd.io">also makes the ROI transparent</a>. WETA, the public media station for Greater Washington, ran the numbers and said that it would break even in the first year, and a calculator is available for other stations that want to check for themselves. The pricing hinges on Passport-eligible donors: those giving at least $60 a year. Local Public charges $60,000 a year for stations with fewer than 15,000, $75,000 for up to 40,000, and $100,000 a year for everyone else &#x2014; which is not out of bounds. It all seems like a decent business, run in the public interest as a subsidiary of Cascade PBS, that will genuinely help public media stations. I want to see more of this.</p><p>But I do wish it was fully based on open technology. While stations gain the right to modify the source code of their <em>apps</em> after a year, they remain locked into Local Public&#x2019;s back-end services. For the CMS, which builds network effects the more stations use it, stations can only get support, maintenance, and customization through Local Public. Over time, that lock-in does not incentivize great support, and Local Public will need to work hard to buck the trends. NPR&#x2019;s CMS, for example, is notorious among the stations that have to use it. I&#x2019;m certain the will is there to do better, but they will need to proceed with intention. In my opinion it would be better if, at least after establishing a customer base, they open sourced their back-end CMS too.</p><p>I tend to think that <em>any</em> technology provided to support the public interest should be fully open. That doesn&#x2019;t mean there isn&#x2019;t a tidy business available to its creator &#x2014; ask <a href="https://ghost.org/?ref=werd.io">Ghost</a>, which is generating millions of dollars off the back of its open source CMS. If there&#x2019;s a class of organization that absolutely doesn&#x2019;t deserve to be locked into a technology stack, it&#x2019;s public service broadcasters.</p><p>This isn&#x2019;t Cascade PBS&#x2019;s fault. It needs its spinout to be sustainable, and this model feels like it will hit that goal. The best scenario, in my mind, would be if there were central funders who bankrolled open tech that the whole ecosystem could use. But, of course, it&#x2019;s 2026, and central funding for <em>anything</em> public media is hard to come by.</p><p>Still, this is wonderful to see, and anything that encourages collaboration on a technical level between public service media organizations deserves support.</p> Newsrooms need to get comfortable expressing their business value - and raising money on it. - Werd I/O 6a47b56282c96000019ca42c 2026-07-03T13:13:06.000Z <p>Link: <a href="https://www.backstoryandstrategy.com/p/journalism-has-the-receipts-it-wont?ref=werd.io"><em>Journalism Has the Receipts. It Won&#x2019;t Use Them., by Yoni Greenbaum in Backstory &amp; Strategy</em></a></p><p>Arts organizations learned long ago to prove their economic value with hard numbers: attendance, tourism revenue, multiplier effects. News, as Yoni Greenbaum argues here, likes to cling to civic virtue and assume that the work should speak for itself.</p><blockquote>&#x201C;Journalism operated on a commercial advertising revenue model for over 150 years. Publishers sold readers to advertisers, while editors fretted about maintaining a church-and-state divide between the newsroom and business desk. Journalists saw themselves as watchdogs, not wealth generators. Pitching our value based on our own economic impact felt gauche, too close to an advertorial.&#x201D;</blockquote><p>Yoni points out that this is starting to change. <a href="https://www.rebuildlocalnews.org/new-report-reveals-local-news-shortage-is-costing-communities-1-1-billion-a-year/?ref=werd.io">We know that news deserts cost communities at least $1.1B a year</a>, for example, because of a report by Rebuild Local News and the University of Illinois Chicago. But newsrooms themselves tend to shy away from reporting their own economic impact &#x2014; even though they already have the tools to do so.</p><p>It&#x2019;s not obvious to me that this accounting would work as an argument across the board for newsrooms, and particularly for those with a national focus. Does ProPublica (my employer until the end of the month) save anyone money? It certainly does prevent corruption, and there are instances with real dollar amounts attached to them: Intuit, for example, paid back $141 million to its customers over deceptive marketing. But I&#x2019;m not sure that its impact can be quantified easily overall, despite the newsroom&#x2019;s obvious public benefit. On the other hand, for <em>local</em> newsrooms, this makes a lot of sense to me: at their best, they act as connective tissue for their communities. That $1.1B a year was <em>just</em> increased interest costs from lenders who felt they could charge more to unmonitored governments.</p><p>They just need to get more comfortable at telling the economic side of their stories. And there&#x2019;s a wider point here, which is that almost all nonprofit newsrooms need to be able to get more concrete and detailed about their business models. If you&#x2019;re running an organization that wants to be sustainable, it&#x2019;s not enough to care about the journalistic process, and your business accounting cannot be limited to activities like events and merchandise. You actually have to care about building a business holistically, and everything that entails.</p> SBS Zoom minutes for 2026-07-02 - Johnny.Decimal https://johnnydecimal.com/blog/0228-sbs-minutes-20260702/ 2026-07-03T05:29:11.000Z <h2 id="thoughts-on-purpose-mission-and-vision-statements">Thoughts on purpose, mission, and vision statements</h2> <p>In yesterday&#39;s Small Business Zoom call, as well as continuing our system-review experiment,<sup><a href="#user-content-fn-experiment" id="user-content-fnref-experiment" data-footnote-ref="" aria-describedby="footnote-label" class="footnote">1</a></sup> we had a chat about purpose, mission, and vision statements. In the corporate world these are often cheesy and pointless, but are they useful in a small business? We don&#39;t have this kind of thing yet so we&#39;re looking for advice.</p> <p>Don<sup><a href="#user-content-fn-don" id="user-content-fnref-don" data-footnote-ref="" aria-describedby="footnote-label" class="footnote">2</a></sup> showed us a one-page statement from their office manual that is a kind of mash-up of what they do, why, and how. It&#39;s clearly written in normal, human words, and conveys a lot of information and personality.</p> <p>On the first read I totally get what they do for a living, their values, and what would be expected of me as an employee. There&#39;s also a &quot;short&quot; and &quot;really short&quot; version of this page that, I&#39;m guessing, is handy for marketing and other low-word-count situations.</p> <p>And if you have staff, his advice for writing internal policies was to keep them simple and clear so they don&#39;t need to be policed.<sup><a href="#user-content-fn-rules" id="user-content-fnref-rules" data-footnote-ref="" aria-describedby="footnote-label" class="footnote">3</a></sup> Drinking at work? No. Dress code? Wear clothes. Understood!</p> <p><strong>Writing these statements can help you define and carve out your niche.</strong></p> <p>Jeff<sup><a href="#user-content-fn-jeff" id="user-content-fnref-jeff" data-footnote-ref="" aria-describedby="footnote-label" class="footnote">4</a></sup> told us how he&#39;s been working on his statements with a couple of other local business owners. He said it&#39;s been a worthwhile exercise for a few reasons. Jeff&#39;s family business has been running for about 10 years and it&#39;s given him a fresh focus on what they do and why. And it&#39;s helped him &quot;define their niche&quot; more clearly by thinking about all the reasons they&#39;re different from their competitors.</p> <p>At JDHQ, we&#39;ve been wondering if we could use this type of statement to help us make better decisions around time management. Before throwing ourselves head first into a new project or product, can we use it to stop and think:</p> <ul> <li>How does it serve the business?</li> <li>Does it align with our values and goals?</li> <li>Is it a good use of our limited time or should we park the idea?</li> </ul> <p>We&#39;ll have a go at writing something soon, which will be filed in <code>11.31 Internal policies</code>. And we&#39;ll keep Don and Jeff&#39;s thoughts in mind. Although I already know that the dress code will be <a href="https://www.uniqlo.com">Uniqlo</a>.</p> <p>If you have the <a href="https://johnnydecimal.com/sbs/">Small Business System</a>, the next call for this fortnight is <strong>Tuesday the 7 July</strong><sup><a href="#user-content-fn-calendar" id="user-content-fnref-calendar" data-footnote-ref="" aria-describedby="footnote-label" class="footnote">5</a></sup> - <a href="mailto:hello@johnnydecimal.com">email us</a> your questions, mission statements, or cries for help!</p> <div data-footnotes="" class="footnotes"><h2 class="sr-only" id="footnote-label">Footnotes</h2> <ol> <li id="user-content-fn-experiment"> <p>Where over the course of a year, we all commit to touching every part of our Small Business System, filing and neatening as we go. Here&#39;s a <a href="https://youtu.be/4iEr31Qjjk0">video</a> that talks about the idea. <a href="mailto:hello@johnnydecimal.com">Email Johnny</a> if you&#39;d like a copy of the Baserow prototype. <a href="#user-content-fnref-experiment" data-footnote-backref="" aria-label="Back to reference 1" class="data-footnote-backref footnoteBackLink">↩</a></p> </li> <li id="user-content-fn-don"> <p><a href="https://oldstructures.com">Old Structures Engineering</a>. <a href="#user-content-fnref-don" data-footnote-backref="" aria-label="Back to reference 2" class="data-footnote-backref footnoteBackLink">↩</a></p> </li> <li id="user-content-fn-rules"> <p>On this note, Johnny came across this quote this morning: “Simple, clear purpose and principles give rise to complex and intelligent behavior. Complex rules and regulations give rise to simple and stupid behavior.” Aka <a href="https://www.deewhock.com/quotations/thehockprinciple/">The Hock Principle</a>. <a href="#user-content-fnref-rules" data-footnote-backref="" aria-label="Back to reference 3" class="data-footnote-backref footnoteBackLink">↩</a></p> </li> <li id="user-content-fn-jeff"> <p><a href="https://lovettsundries.com">Lovett Sundries</a>. <a href="#user-content-fnref-jeff" data-footnote-backref="" aria-label="Back to reference 4" class="data-footnote-backref footnoteBackLink">↩</a></p> </li> <li id="user-content-fn-calendar"> <p>Subscribe to the events calendar to see <a href="https://johnnydecimal.com/support/knowledge-base/sbs-events-calendar/">upcoming calls</a>. <a href="#user-content-fnref-calendar" data-footnote-backref="" aria-label="Back to reference 5" class="data-footnote-backref footnoteBackLink">↩</a></p> </li> </ol> </div> 📝 2026-07-02 23:05: I would say DeepSeek was the most accurate. 🤷🏼‍♂️ https://intheweights.com/p/kev-quirk - Kev Quirk https://kevquirk.com/2026-07-02-2305 2026-07-02T22:05:00.000Z <p>I would say DeepSeek was the most accurate. 🤷🏼‍♂️</p> <p><a href="https://intheweights.com/p/kev-quirk">https://intheweights.com/p/kev-quirk</a></p> <p><img loading="lazy" src="https://kevquirk.com/content/images/2026-07-02-2305/1000010062.webp" alt="1000010062" /></p> <div class="email-hidden"> <hr /> <p>Thanks for reading this post via RSS. RSS is ace, and so are you. ❤️</p> <p>You can <a href="mailto:19gy@qrk.one?subject=%F0%9F%93%9D%202026-07-02%2023%3A05">reply to this post by email</a>, or <a href="https://kevquirk.com/2026-07-02-2305#comments">leave a comment</a>.</p> </div> June 2026 Summary - Joel's Log Files https://joelchrono.xyz/blog/june-summary 2026-07-02T13:40:00.000Z <p>June is over! Did you know this month was my birthday? Yeah it’s kinda crazy, a lot has changed, I am twenty six now yet more than half the movies I watched were Direct-to-DVD 50 minute films made to sell action figures!</p> <p>More things happened of course. The World Cup is going great for my country, Mexico has managed to break out of the group stage and win their first knockout game in 40 years, I am incredibly hyped to see the match against England next, to any readers from there, hope you guys have fun with the game! I know us Mexicans will absolutely party!</p> <p>Anyway, my Summer Game Challenge is in full swing, with progress made for multiple games as well. I decided to also add an update to the music, which I often ignore because I listen to the same albums a lot, especially to sleep, or while reading <em>The Expanse</em>. Here’s the gist of it all.</p> <h2 id="podcasts">Podcasts</h2> <ul> <li> <p><strong>Into The Aether</strong> wins with the regular weekly episodes. A lot of chatter about <em>Mina The Hollower</em>, <em>Adventures of Elliot</em>, and Brendon’s new gaming news site <a href="https://overworld.vg">Overworld</a>, really cool stuff.</p> </li> <li> <p><strong>Eye of the Duck</strong> was second with a couple of episodes focusing on <em>The Chronicles of Narnia</em> and also Spielberg’s <em>Artificial Inteligence</em>. Really interesting points were brought up.</p> </li> <li> <p><strong>Trash Taste</strong> made for some excellent background noise again, with many entertaining conversations about anime films and shows.</p> </li> <li> <p><strong>Boring Books for Bedtime Readings</strong> continues to deliver. H.G. Wells’ <em>A Short History of the World</em> is super interesting and nice to sleep to.</p> </li> <li> <p><strong>99% Invisible</strong> showed up again! This time with one episode talking about the history of the Vuvuzuela and its rise during the 2010’s world cup.</p> </li> </ul> <h2 id="books">Books</h2> <h3 id="completed">Completed</h3> <p><a href="/blog/tiamats-wrath/"><strong>Tiamat’s Wrath</strong></a> really subverted my expectations with, changing what I didn’t know I expect from<em>The Expanse</em>, it was a roller coaster of emotions, there were losses I was not expecting and events I couldn’t see coming. I already wrote my thoughts on my review for it.</p> <h3 id="ongoing">Ongoing</h3> <p><strong>Clarkesworld Magazine #211</strong> is still being poked at, but my focus on <em>The Expanse</em> kind of stopped me. The current sci-fi short story I’m reading for it is one of those absurdist ones that are interesting but not fully gripping me yet.</p> <h3 id="started">Started</h3> <p><strong>Leviathan Falls</strong> already changed things in the very prologue of it all. This is the final book of The Expanse but I’ve heard both good and bad things, so I am going to get to it for July!</p> <h2 id="videogames">Videogames</h2> <h3 id="completed-1">Completed</h3> <ul> <li> <p><strong>Monument Valley: Ida’s Dream</strong> was an extra set of levels for <em>Monument Valley</em> that I found challenging and with some nice extra mechanics. It took me less than an hour to beat but I enjoyed it!</p> </li> <li> <p><a href="/blog/metroid-prime-remastered/"><strong>Metroid Prime Remastered</strong></a> grabbed me and didn’t let me know. The perfect transition for Metroid into a new dimension. It was a lovely game and I wrote a BUNCH of thoughts about it on my review already. Tl;dr: A must play on the Nintendo Switch.</p> </li> </ul> <h3 id="ongoing-1">Ongoing</h3> <ul> <li> <p><strong>Hollow Knight: Silksong</strong> finally saw some playtime! I finally have done what I need to unlock the quest that will then let me unlock the final act of the game, I am looking forward to it!</p> </li> <li> <p><strong>Full Metal Furies</strong> also saw some action, completing a couple of stages with friends!</p> </li> </ul> <h3 id="started-1">Started</h3> <ul> <li> <p><strong>Transistor</strong> took me by surprise. I started it suddenly when I didn’t feel like going for a cartridge to play on my Switch and remembered this game from Supergiant existed before Hades was a thing. I have been delighted to play it and I’m very close to the end…</p> </li> <li> <p><strong>Fire Emblem Awakening</strong> however, also took me away from <em>Transistor</em> now that I have a New Nintendo 3DS XL that happens to be extremely easy to carry on my commutes compared to the Switch. The game is a delight and I’m glad to return to it after many years, when I tried it on a friend’s 3DS. With my experience playing <em>The Blazing Blade</em>, it’s quite great!</p> </li> </ul> <h2 id="manga">Manga</h2> <p>Thought it would be a good idea to split the Manga I’m reading between those that I follow on a weekly or monthly basis, and those that I often fly through because I have a backlog to catch up to, finished or not! Some of the ones I’m caught up with may be behind by a couple chapters, but nothign significant.</p> <h3 id="caught-up">Caught up</h3> <ul> <li> <p><strong>Blue Lock</strong> - Chapter 351. The next match is about to unravel, though the team is going into a bit of a struggle as the <em>Blue Lock</em> ideology itself was challenged during the last match.</p> </li> <li> <p><strong>Spy x Family</strong> - Chapter 136. As fun as ever, this arc with Loid stuck in a party hosted by secret service agents was just hilarious. Loved how it only got crazier over time.</p> </li> <li> <p><strong>Heavenly Delusion</strong> - Chapter 82. The mystery going on is ever-growing, I kind of want to wait again to have some chapters to catch up with, because this is a monthly release and now I have to wait a bunch.</p> </li> <li> <p><strong>Smoking Behind the Supermarket with You</strong> - Chapter 62. Some very stressful events have been happening lately, the drama is growing, but I hope the protagonists manage to grow from this for the better!</p> </li> </ul> <h3 id="catching-up">Catching up</h3> <ul> <li> <p><strong>Shikimori’s Not Just Cute</strong> - Chapter 160. Super nice to return to this rom-com. I didn’t realize how I missed these characters, the main couple continues to be great and it has been more stable than <em>Komi Can’t Communicate</em>, though a bit more bland at times. Still great tho.</p> </li> <li> <p><strong>My Wife is from a Thousand Years Ago</strong> - Chapter 287. Some cute moments and a vacation trip. Some other things happened. Good manhua (Chinese comic) rom-com as always.</p> </li> </ul> <h2 id="anime">Anime</h2> <p><strong>Blue Lock (Season 1)</strong> - Episode 2. Well this is just the anime of Blue Lock and I already know Blue Lock is great. The art is nowhere near the manga and this is supposed to be the best looking season. It’s still cool and the epic moments are hype with the music and the japanese dub.</p> <h2 id="tv-shows">TV Shows</h2> <p><strong>Avatar The Last Airbender (Live Action Season 2)</strong> - Episode 3. I do like that Toph shows up, she fits well. I don’t really think I like the rest that much, a lot of the story has been butchered. No Library, no enchanted jungle, or forest spirit or anything, weird.</p> <h2 id="movies">Movies</h2> <ul> <li><strong>Disclosure Day</strong> - Spielberg does another movie with aliens and proves they are not AI generated.</li> <li><strong>The Martian</strong> - The most accurate sci-fi film ever? Probably, gotta read the book soon.</li> <li><strong>Mission to Mars</strong> - An honest attempt to do <em>2001: A Space Odyssey</em> again, it wasn’t close but it tried.</li> <li><strong>Max Steel: Bio Crisis</strong> - Max gets a robot friend and saves a random island from radiation.</li> <li><strong>Max Steel vs The Mutant Menace</strong> - Max gets a new boss and recaptures an enemy he let get away.</li> <li><strong>Max Steel vs The Toxic Legion</strong> - Every enemy unites to wreak havoc, but Max’s best friend saves the day.</li> <li><strong>Max Steel: Makino’s Revenge</strong> - A guy makes Max famous, then cancels him, until Max exposes him instead.</li> <li><strong>Max Steel: Monstrous Alliance</strong> - Every enemy unites against Max, but the boss’s daughter saves the day.</li> <li><strong>Terminator Salvation</strong> - This is a pretty good film but not a good Terminator film.</li> <li><strong>Rocky</strong> - This is actually beautiful, cinema. An awesome slice-of-life with a boxing match in the end.</li> <li><strong>Toy Story 5</strong> - This is a worthy sequel with a great and nuanced message and fantastic nods to previous movies. Go watch it.</li> </ul> <h2 id="music">Music</h2> <p>I pretty much just played these albums before going to sleep every night.</p> <ul> <li><strong>FTL Original Soundtrack</strong> by Ben Prunty</li> <li><strong>Outer Wilds Original Soundtrack</strong> by Andrew Prahlow</li> <li><strong>Into the Breach Soundtrack</strong> by Ben Prunty</li> <li><strong>Plants vs Zombies Original</strong> Soundtrack by Laura Shigihara</li> <li><strong>Minecraft - Volume Beta</strong> by C418</li> </ul> <h2 id="finishing-thoughts">Finishing thoughts</h2> <p>This month was quite crazy, with a lot of things happening that altered the course of many plans. From my bike rides during the morning, making it so I listened to more music and podcasts, to my sister visiting and making me watch movies and shows I didn’t foresee.</p> <p>Zero complaints though, the month was wild, the Summer Games Challenge is going quite well and the</p> <h2 id="goals">Goals</h2> <p>As you can see, procrastinating is my second name</p> <ul class="task-list"> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Participate in <a href="https://robertbirming.com/julyreply-2026-blog-connecting/">July Reply</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Cycling every weekend</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Try a bicycle commute to work</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Create some pixel bears for a few friends (2/10)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Play all the games from the UFO 50 collection (3/50)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />A full website redesign</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Finish a pending commission for a friends</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Finish a pending commission for my parents</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Complete 15 videogames (6/15)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Complete 15 books (7/15)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Read the whole bible in a year (66/365)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" />Finish Listening to Wolf 359 (45.5/61)</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Fully sorting and labelling everything on my shelves for once</li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" />Make something to simplify keeping track of these goas</li> </ul> <h2 id="finishing-thoughts-1">Finishing thoughts</h2> <p>It seems like my “goals” section remains stagnant, my bible reading definitely slowed down a bit, I didn’t listen to Wolf 359, and whatever else I missed.</p> <p>Getting a 3DS definitely reshaped my last week or so, as I’ve spent some time configuring things and getting games for it.</p> <p>I also <em>need</em> to setup my bike for a ride to work, I want to give it a go for once! Use it a bit more than just weekends. No promises, but I even returned to the gym after a two month long hiatus, I want to give it another shot. My muscles are sore! So that’s pretty good I’d say. I realized my weight went up by almost 3 kilos, I really need to get a grip on that.</p> <p>All in all, a good month with lots of things going on, I am 26 years old now, and the fear of adulthood is kind of sinking in a bit more for some reason, we’ll see how things go.</p> <p>This is day 88 of <a href="https://100daystooffload.com">#100DaysToOffload</a></p> <p> <a href="mailto:me@joelchrono.xyz?subject=June 2026 Summary">Reply to this post via email</a> | <a href="https://fosstodon.org/@joel/116850688193616561">Reply on Fediverse</a> </p> OpenAI wants to give us 5% of its success. It's a bad bargain. - Werd I/O 6a4668193cca500001eadb44 2026-07-02T13:31:05.000Z <p>Link: <a href="https://www.ft.com/content/7c803eab-8e80-4431-9a87-e943bf00e00b?syn-25a6b1a6=1&amp;ref=werd.io"><em>OpenAI proposes handing Trump administration 5% stake, by Cristina Criddle in the Financial Times</em></a></p><p>In order to ward off backlash against AI and curry favor with the Trump administration, Sam Altman has floated the idea of giving 5% of OpenAI to a wealth fund that pays dividends to both the government and citizens &#x2014; and that every leading AI vendor should do the same.</p><blockquote>&#x201C;Sam Altman, chief executive of the ChatGPT maker, has argued that giving the public a financial stake in the company is the best way to share the upside of AI and has suggested a stake of this size in early conversations with the administration, according to two people familiar with the talks.&#x201D;</blockquote><p>It&#x2019;s transparently a way to align everyone with AI vendor profits. If the sector increases in value, the government and the voting population benefit. If it <em>decreases</em> in value &#x2026; well, the government is incentivized to prevent that from happening. It also wouldn&#x2019;t be without precedent: it&#x2019;s modeled on the Alaska Permanent Fund, which does this with oil profits for Alaskan residents. Intel is also now 10% government-owned, and the administration has reversed course to be behind it since gaining that stake.</p><p>Would a government whose revenues are directly linked to the performance of a sector be likely to enact hard regulations on that sector? Perhaps not. It&#x2019;s not a slam dunk, though: for example, the UK receives significant tax revenue on fossil fuels, but still promoted electric cars. There are lots of factors at play, and profit alignment isn&#x2019;t necessarily outweighed by the effects of other harms. (See also: cigarettes, which are taxed but also tightly controlled as an addictive carcinogen.)</p><p>Meanwhile, Bernie Sanders has pushed for closer to 50% ownership through a sovereign wealth fund. At this much lower stake, Sam Altman&#x2019;s proposal uses Sanders&#x2019;s democratic socialist &#x201C;share the wealth&#x201D; language as a way to launder OpenAI&#x2019;s profits through a thin veneer of good ethics.</p><p>What&#x2019;s also interesting to me is that all of these arguments assume that AI is going to be an enormous driver of wealth and innovation &#x2014; but what if it isn&#x2019;t? It&#x2019;s another great way to advertise the technology as something world-changing that everybody must get behind right now.</p><p>Even if AI turns out to be what the people heavily invested in its success say it will be, it doesn&#x2019;t stand alone as a sea change innovation. The personal computer, the iPhone, word processors, and spreadsheets were pretty transformational technologies. Should there have been a wealth fund attached to each of those? What, exactly, makes AI different?</p><p>The answer is that it represents labor displacement: people will lose their jobs. And if that&#x2019;s actually going to be the case, we need bigger, more structural safety nets and reforms. Dividends from 5% of a sector aren&#x2019;t going to replace wages at scale &#x2014; and are heavily dependent on valuations continuing to rise. This proposal ties the welfare of people who have lost their jobs to the success of the companies that drove those losses. The incentives are perverse.</p><p>We shouldn&#x2019;t accept this proposal. Instead, we should push for stronger protections and stronger regulation. If a sector can&#x2019;t succeed without real damage to working communities, then it must not be allowed to. And if these claims turn out not to be true, then it&#x2019;s an empty gesture designed to add credibility to a self-interested science fiction view of the future.</p>