<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="en">
	<title>James' Blog</title>
	<subtitle>James' blog on the web, the universe, and everything.</subtitle>
	<link href="https://jamesbasoo.com/blog/feed.xml" rel="self"/>
	<link href="https://jamesbasoo.com/"/>
	<updated>2025-03-27T00:00:00Z</updated>
	<id>https://jamesbasoo.com/</id>
	<author>
		<name>James Basoo</name>
		<email>jamesbasoo@gmail.com</email>
	</author>
	
	<entry>
		<title>How To Write CSS And Influence People</title>
		<link href="https://jamesbasoo.com/blog/how-to-write-css-and-influence-people/"/>
		<updated>2025-03-27T00:00:00Z</updated>
		<id>https://jamesbasoo.com/blog/how-to-write-css-and-influence-people/</id>
		<content type="html">&lt;div class=&quot;breakout guttered&quot;&gt;
	&lt;img src=&quot;https://jamesbasoo.com/img/roadblock-speed-bumps/md.jpg&quot; sizes=&quot;auto&quot; srcset=&quot;https://jamesbasoo.com/img/roadblock-speed-bumps/hi.jpg 2634w, https://jamesbasoo.com/img/roadblock-speed-bumps/md.jpg 1200w, https://jamesbasoo.com/img/roadblock-speed-bumps/lo.jpg 800w&quot; alt=&quot;An amateurish illustration of a road with multiple paths. One has speed bumps, the other has a roadblock that would divert the person following the path down another road in the opposite direction.&quot; width=&quot;2634&quot; height=&quot;1757&quot; class=&quot;hero sketchy-border&quot;&gt;
&lt;/div&gt;
&lt;p&gt;Coding standards are generally a good thing. They get everyone on the same page, which reduces ambiguity and headaches down the line. However, despite outlining standards in internal docs and project READMEs, they aren&#39;t always followed. Even enforcing them in IDEs can be hit or miss, especially if you&#39;re cobbling together disparate technologies and need to be more flexible.&lt;/p&gt;
&lt;h2&gt;Is CSS Broken?&lt;/h2&gt;
&lt;p&gt;One of the big draws of Tailwind is that it&#39;s somewhat of an off-the-shelf design system, one that many developers are familiar with. Like it or not, that&#39;s powerful. Its opinionated coding standards help create consistent, predictable codebases, which I don&#39;t think any of us would say no to. Proponents of Tailwind often declare &amp;quot;&lt;a href=&quot;https://thebcms.com/blog/why-tailwind-doesnt-suck&quot;&gt;CSS is broken, Tailwind fixes it&lt;/a&gt;,&amp;quot; but many of the arguments come down to 2 things — long term organisational issues, and that cascading and inheritance are hard.&lt;/p&gt;
&lt;h3&gt;Human Problems&lt;/h3&gt;
&lt;p&gt;Organisation problems can be solved with a design system, thoughtful architecture, or at least some enforced conventions. I&#39;m not gonna lie, that&#39;s hard. It&#39;s a human problem, and humans are messy and dysfunctional. In my experience, however, adding Tailwind to that mess makes things get worse, faster. It&#39;s giving in to the chaos. I&#39;m still maintaining almost decade-old codebases which have passed through scores of developers, but our 2-year-old Tailwind projects give me more CSS maintenance grief than any of those ever did. This is less likely to be the case in greenfield JS framework projects, where it&#39;s a blank slate and everything can be a component, but outside of those I wouldn&#39;t recommend Tailwind as a framework.&lt;/p&gt;
&lt;h3&gt;Technology Problems&lt;/h3&gt;
&lt;p&gt;Features like cascading and inheritance are are hard for a lot of folks to master. The core concepts run counter to most other programming languages which are imperative, not declarative. CSS is broad and far-reaching which is very powerful for creating UIs, but you might feel lost until it clicks. &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@layer&quot;&gt;@layer&lt;/a&gt; and &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/CSS/@scope&quot;&gt;@scope&lt;/a&gt; now help us to target elements more precisely without entirely throwing away the features that make CSS so powerful, but we&#39;re still waiting on Firefox for @scope support. In the meantime, Chris has some good &lt;a href=&quot;https://gomakethings.com/css-isnt-broken-tailwind-utility-classes-and-css-architecture/&quot;&gt;practical examples&lt;/a&gt; of when you should use utility classes.&lt;/p&gt;
&lt;h2&gt;Utility-First CSS&lt;/h2&gt;
&lt;p&gt;Our team has varying views on Tailwind — some are indifferent, some are pro, and some are con. I&#39;ve used it enough to know the maintenance headaches it causes, but I still see a few merits to having off-the-shelf utility classes available for &lt;strong&gt;&lt;em&gt;occasional&lt;/em&gt;&lt;/strong&gt; use. Emphasis on the &lt;strong&gt;&lt;em&gt;occasional&lt;/em&gt;&lt;/strong&gt;.  For example, they&#39;re great for simple layouts where the container has no semantic meaning and solely exists to apply a basic grid or flex layout. 10 years ago, before grid and flex were well supported, these were the kinds of utilities I&#39;d either hand-roll myself or get from a grid system like Susy or 960.gs. Utility classes are indeed a handy tool, but only having 1 tool in your toolbox is very limiting.&lt;/p&gt;
&lt;p&gt;CSS can do so much more than Tailwind abstracts, and only learning the abstraction means that you don&#39;t gain a good understanding of what is being abstracted. This is something I found out the hard way when I learned jQuery before learning vanilla JavaScript. jQuery provided a sensible API to aid in writing cross-browser compatible JavaScript, but that had the downside of having to learn how JavaScript actually worked once jQuery became redundant. Tailwind does have some patterns that could remain useful. However, like many frameworks and libraries before it, it&#39;ll eventually be surpassed by the web platform.&lt;/p&gt;
&lt;h2&gt;Flexible Strategies - Roadblocks VS Speed Bumps&lt;/h2&gt;
&lt;p&gt;The whole idea of influencing people &amp;quot;gives me the ick&amp;quot; (as the kids say these days). I don&#39;t like being tricked into things, and being forced down a certain path is incredibly demoralising. That being said, I find an informed strategy to be generally acceptable, desirable even. However, becoming informed sometimes requires you to slow down and consider the information.&lt;/p&gt;
&lt;p&gt;I&#39;ve started to view this as the difference between &lt;strong&gt;roadblocks&lt;/strong&gt; and &lt;strong&gt;speed bumps&lt;/strong&gt;. The former diverts you away from your destination and the latter prevents you from going too fast in potentially hazardous environments. It&#39;s an idea we can apply to our architecture when we want to encourage adherence to coding standards but still allow going off-script if it&#39;s truly necessary.&lt;/p&gt;
&lt;p&gt;A roadblock is very frustrating. It requires you to find a different route to your destination or prevents you from getting there at all. It may keep you out of danger though, so they certainly have their place, but when something is more of a coding style or preference, I would prefer to be slowed down rather than diverted.&lt;/p&gt;
&lt;h2&gt;Guiding My Team&#39;s Coding Standards&lt;/h2&gt;
&lt;p&gt;It was decided that Tailwind should be available on some of our projects. While I personally find the odd utility class useful, the mere existence of such a prominent tool on a project can encourage its over-use if the team does not have strictly enforced coding standards. As a &amp;quot;fast-paced agency&amp;quot; that utilises a wide range of stacks, we don&#39;t exactly have the discipline to keep this kind of tech debt in check.&lt;/p&gt;
&lt;p&gt;The approach I decided to take was to use Tailwind as a library, not a framework. Include it, but disable its &lt;a href=&quot;https://v3.tailwindcss.com/docs/configuration#core-plugins&quot;&gt;core plugins&lt;/a&gt;. This means that while it&#39;s part of the toolkit, nothing will be output unless the developer explicitly enables the plugin(s) they require. This reduces the risk of Tailwind generating classes that may clash with the existing codebase, or being over-eager and bloating the CSS with code that won&#39;t be used. Those are the technical reasons for this decision, but I also just wanted to discourage the team from over-using Tailwind.&lt;/p&gt;
&lt;p&gt;Let&#39;s pretend you&#39;re new to a project with this setup. You look in the root and see &lt;code&gt;tailwind.config.js&lt;/code&gt;. Sweet! I know Tailwind, I&#39;ll use that everywhere. However when you try, it doesn&#39;t work. You look at the config file and see that some wally has disabled the core modules. You don&#39;t want to change it as that&#39;s a pretty drastic update, so you then ask someone else on the team why this was done and they direct you to the readme.md that you shoud&#39;ve checked in the first place. It reads:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Semantic classes are preferred, however falling back to utilities is an option. As a general rule, if an element needs more than 5 utility classes, then it&#39;s probably complex enough to have a descriptive, semantic class name. This also serves to self-document what the element is for other developers (probably you, 6 months later).&lt;/p&gt;
&lt;p&gt;Tailwind is available, however, most core plugins are disabled to reduce unnecessary CSS and clashes with existing CSS. These can be enabled as needed in the &lt;code&gt;corePlugins&lt;/code&gt; array in &lt;code&gt;tailwind.config.js&lt;/code&gt;. Refer to the &lt;a href=&quot;https://v3.tailwindcss.com/docs/configuration#core-plugins&quot;&gt;Tailwind docs&lt;/a&gt; for core plugin info.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You&#39;ve been successfully slowed down, steered towards reading the documentation and actually talking to your colleagues. This &lt;a href=&quot;https://indiehackers.social/@simeon/114188109517778984&quot;&gt;added friction&lt;/a&gt; made you stop, think, ask questions, and ultimately discouraged you from over-using a tool with considerable downsides. If after all that you still deem it worth it, then there could very well be a good reason to enable a core plugin.&lt;/p&gt;
&lt;p&gt;This did eventually lead to us having a discussion and agreeing upon a handful of core plugins that we&#39;d be likely to enable on most projects. I would&#39;ve preferred to have less plugins enabled by default, but considering Tailwind doesn&#39;t (usually) output anything unless it finds a matching class in your codebase, I&#39;d call it a step in the right direction.&lt;/p&gt;
&lt;h2&gt;Tailwind V4 - Influencing CSS Strategy?&lt;/h2&gt;
&lt;p&gt;A lot has changed with &lt;a href=&quot;https://tailwindcss.com/blog/tailwindcss-v4&quot;&gt;version 4&lt;/a&gt;. It&#39;s finally getting some modern features like OKLCH colors, @property, cascade layers, and color-mix(). The configuration has also been moved out of JS and into the main CSS file, which has some pros and cons.&lt;/p&gt;
&lt;p&gt;There&#39;s plenty of &lt;a href=&quot;https://www.youtube.com/watch?v=q55u3_Nj3Lw&quot;&gt;thoughts&lt;/a&gt; and &lt;a href=&quot;https://nmn.sh/blog/2024-11-30-thoughts-on-tailwind-4?ref=dailydev&quot;&gt;hot takes&lt;/a&gt; on the changes but there&#39;s 2 in particular that stuck out to me — &lt;a href=&quot;https://tailwindcss.com/docs/upgrade-guide#disabling-core-plugins:~:text=The%20corePlugins%2C%20safelist%2C%20and%20separator%20options%20from%20the%20JavaScript%2Dbased%20config%20are%20not%20supported%20in%20v4.0.&quot;&gt;the removal of core plugin and safelist configurations&lt;/a&gt;. The &lt;a href=&quot;https://v3.tailwindcss.com/docs/configuration#core-plugins&quot;&gt;core plugin config&lt;/a&gt; &amp;quot;lets you completely disable classes that Tailwind would normally generate by default if you don’t need them for your project.&amp;quot; &lt;a href=&quot;https://tailwindcss.com/docs/upgrade-guide#disabling-core-plugins&quot;&gt;This&lt;/a&gt; removal could have a number of reasons such as implementation difficulties with the new configuration method or simply low usage. However, considering other moves from the Tailwind team, I can&#39;t help but think that this could be a roadblock to divert users towards complete Tailwind reliance.&lt;/p&gt;
&lt;p&gt;Between the marketing and the changes to the framework, it&#39;s obvious that Tailwind wants to be the one and only tool in your CSS toolbox. That&#39;s fine for most users who want the entire framework, but the removal of core plugin configuration prevents you from using Tailwind as a library. You get the full-fat framework or nothing at all.&lt;/p&gt;
&lt;p&gt;If I put on my tin foil hat, I get the impression that this all-or-nothing approach could be part of a strategy to influence developers. Not unlike I did internally but more widely and forcefully. Not letting you adapt Tailwind to your project pushes you to adapt your project to Tailwind. You need to rip out your existing clashing CSS and rewrite it with utilities if you want to use Tailwind safely and without duplication.&lt;/p&gt;
&lt;h2&gt;Libraries &amp;gt; Frameworks&lt;/h2&gt;
&lt;p&gt;Given this change and that I sometimes make use of the removed &lt;a href=&quot;https://v3.tailwindcss.com/docs/content-configuration#safelisting-classes&quot;&gt;safelist&lt;/a&gt; for CMS generated HTML, I won&#39;t be using V4. As previously stated, I&#39;ve written my own utilities in the past but to be honest, Tailwind&#39;s are usually better so I&#39;ll be sticking with V3. If these features could be re-introduced, then I would happily use V4 as there are clear improvements. Right now, for me, it&#39;s just not a good fit. I gave my 2 cents in a &lt;a href=&quot;https://github.com/tailwindlabs/tailwindcss/discussions/16132#discussioncomment-12312611&quot;&gt;Github issue&lt;/a&gt; but I&#39;m not expecting meaningful change anytime soon.&lt;/p&gt;
&lt;p&gt;Therein lies the problem with frameworks. If you want to take advantage of new features, you often need to let go of old ones. That is rarely the case when you build closer to a slow-moving platform. &lt;a href=&quot;https://csswizardry.com/2025/01/build-for-the-web-build-on-the-web-build-with-the-web/#:~:text=If%20I%20was%20only%20able%20to%20give%20one%20bit%20of%20advice%20to%20any%20company%3A%20iterate%20quickly%20on%20a%20slow%2Dmoving%20platform.&quot;&gt;Your velocity can be faster&lt;/a&gt; because the ground isn&#39;t shifting like sand under your feet. That&#39;s why I&#39;ll continue to architect my codebases with libraries and not frameworks. I&#39;ll use design patterns that encourage building closer to the web, even if that means having a difficult, messy, human interaction from time to time.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>SSR + CSR Theme Switching</title>
		<link href="https://jamesbasoo.com/blog/theme-switching-ssr-csr/"/>
		<updated>2024-08-30T00:00:00Z</updated>
		<id>https://jamesbasoo.com/blog/theme-switching-ssr-csr/</id>
		<content type="html">&lt;p&gt;I&#39;ve had a theme switcher since I made my website, but all the logic was client side so there would be a brief &lt;a href=&quot;https://css-tricks.com/flash-of-inaccurate-color-theme-fart/&quot;&gt;Flash of inAccurate coloR Theme (FART)&lt;/a&gt; on page load. Unfortunately, with the site being hosted on Netlify, I don&#39;t have access to the usual server-side power that I&#39;m used to from years of working with PHP.&lt;/p&gt;
&lt;p&gt;Thankfully, Netlify has Edge Functions which are like a sprinkle of back-end (as a treat) without having to manage a server. They&#39;re somewhat limited, but they&#39;re perfect for what I needed them for, reading a cookie and modifying HTML. Pairing Edge Functions with Javscript means both the SSR and CSR are taken care of.&lt;/p&gt;
&lt;h2&gt;The JavaScript&lt;/h2&gt;
&lt;p&gt;Managing cookies sucks. The methods for handling &lt;code&gt;localStorage&lt;/code&gt; are dead easy but I suppose that updating tehcnologies embedded in the fabric of the web since I was 3 might be a tall order. I&#39;m using &lt;code&gt;js-cookie&lt;/code&gt; to manage these more easily but apart from that, this JS has no dependencies.&lt;/p&gt;
&lt;p&gt;I get the user&#39;s preferred color scheme and if there&#39;s no theme cookie already set, then I map the user&#39;s preferred color to one of my themes, and set the data attribute and cookie. From there, it&#39;s just listening to the click event and updating again.&lt;/p&gt;
&lt;pre class=&quot;language-javascript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-javascript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; Cookies &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;/js-cookie/js.cookie.min.mjs&#39;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; themeSwitcher &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;.theme-switcher&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; defaultTheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;window&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;matchMedia&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;(prefers-color-scheme: dark)&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;matches &lt;span class=&quot;token operator&quot;&gt;?&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;holodeck&#39;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;light&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; currentTheme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; Cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;theme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;currentTheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;	Cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;theme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; defaultTheme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;	document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; defaultTheme&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;	document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;documentElement&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; currentTheme&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;themeSwitcher&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;	themeSwitcher&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;addEventListener&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;click&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token parameter&quot;&gt;event&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;tagName &lt;span class=&quot;token operator&quot;&gt;===&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&#39;BUTTON&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;			document&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;html&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;dataset&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;			Cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;set&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&#39;theme&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; event&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;target&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;value&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;The Edge Function&lt;/h2&gt;
&lt;p&gt;I&#39;m not a TypeScript whizz, but most of the Edge Function examples on Netlify are in TypeScript, so I just went with it.&lt;/p&gt;
&lt;p&gt;The logic here is to get the cookie, if there is one, and make the same update to the HTML as the JavaScript, but before it makes it to the user, avoiding the flash. There&#39;s also some extra config and header sniffing to prevent this from running on non-HTML resources.&lt;/p&gt;
&lt;p&gt;It was only after figuring out 90% of this that I discovered &lt;a href=&quot;https://www.learnwithjason.dev/blog/css-color-theme-switcher-no-flash/&quot;&gt;Jason&#39;s post&lt;/a&gt; where he outlines his approach. It&#39;s different from the way I accomplished it, but response header detection helped me safeguard against file types I may have missed. It would be nice if this could be accomplished in the config though, so the function would only run on HTML files.&lt;/p&gt;
&lt;pre class=&quot;language-typescript&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-typescript&quot;&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;type&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; Config &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;@netlify/edge-functions&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt; HTMLRewriter &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;https://ghuc.cc/worker-tools/html-rewriter/index.ts&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;default&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;async&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;handler&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;request&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Request&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; context&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Context&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; response &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;await&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; theme &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; context&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;cookies&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;theme&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; type &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; response&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;headers&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;token builtin&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;theme &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;type &lt;span class=&quot;token operator&quot;&gt;||&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;!&lt;/span&gt;type&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;startsWith&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;text/html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; rewriter &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;token class-name&quot;&gt;HTMLRewriter&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;html&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token function-variable function&quot;&gt;element&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;element&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;=&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;			element&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;setAttribute&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;data-theme&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt; theme&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;	&lt;span class=&quot;token keyword&quot;&gt;return&lt;/span&gt; rewriter&lt;span class=&quot;token punctuation&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;transform&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;response&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;export&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;const&lt;/span&gt; config&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; Config &lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;	path&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;/*&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;	excludedPath&lt;span class=&quot;token operator&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;[&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.css&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.js&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.mjs&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.svg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.jpg&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.woff2&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;		&lt;span class=&quot;token string&quot;&gt;&quot;/*.mp4&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;	&lt;span class=&quot;token punctuation&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;,&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Future James&#39;s problems&lt;/h2&gt;
&lt;p&gt;There&#39;s still a flash if the user is new and prefers a dark theme, as it defaults to the light theme and then switches when it detects the user&#39;s preference. Eventually, I might get around this by using &lt;code&gt;light-dark()&lt;/code&gt; and &lt;code&gt;prefers-color-scheme&lt;/code&gt;, but they weren&#39;t around when I initially built the site and would require significant re-architecting. Style queries could also help when they become available.&lt;/p&gt;
&lt;p&gt;There is also no &amp;quot;auto&amp;quot; mode to defer to the user&#39;s preference at all times, instead of remembering their previous choice.&lt;/p&gt;
&lt;h2&gt;Bonus&lt;/h2&gt;
&lt;p&gt;This all came about when I tried adding &lt;a href=&quot;https://developer.chrome.com/docs/web-platform/view-transitions#cross-document_view_transitions&quot;&gt;cross-document view transitions&lt;/a&gt; and found that the flash was even more jarring than it used to be. I also changed &lt;code&gt;font-display: swap;&lt;/code&gt; to &lt;code&gt;block&lt;/code&gt; in my &lt;code&gt;@font-face&lt;/code&gt; declarations, which makes the flash of unstyled text briefly invisible instead. It feels smoother, if a touch slower.&lt;/p&gt;
&lt;p&gt;Going further down the rabbit hole, I realised that &lt;a href=&quot;https://fontsource.org/fonts/overpass&quot;&gt;the font I use&lt;/a&gt; now has a variable variant, so I swapped that out to reduce requests and overall payload size.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;As a sidenote, &lt;a href=&quot;https://fontsource.org&quot;&gt;Fontsource&lt;/a&gt; is pretty sweet. Being able to install and update fonts as a node module is just lovely.&lt;/em&gt;&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Converting SVGs to a Shopify Liquid snippet</title>
		<link href="https://jamesbasoo.com/blog/svg-icon-shopify-liquid-snippet/"/>
		<updated>2023-12-06T00:00:00Z</updated>
		<id>https://jamesbasoo.com/blog/svg-icon-shopify-liquid-snippet/</id>
		<content type="html">&lt;p&gt;In today&#39;s episode of automation that I convince myself is necessary because human error always gets the better of me, here&#39;s a happy little shell script to convert a directory of SVGs into a Shopify Liquid snippet. Shopify lacks any sort of svg/icon pipeline and this snippet &lt;a href=&quot;https://community.shopify.com/c/shopify-design/how-to-use-svg-from-another-snippet-in-the-liquid-template/m-p/1782485/highlight/true#M476691&quot;&gt;technique is pretty common&lt;/a&gt;, and I thought it could use a little automation.&lt;/p&gt;
&lt;pre class=&quot;language-shell&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-shell&quot;&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;Converting SVGs to Shopify Liquid Snippet&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;liquid&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;$&#39;{% case icon %}&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;&#39;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;token for-or-select variable&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$1&lt;/span&gt;/*.svg&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;do&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;basename&lt;/span&gt; $svg&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${file&lt;span class=&quot;token operator&quot;&gt;%&lt;/span&gt;.*}&lt;/span&gt;&quot;&lt;/span&gt;&lt;br&gt;&lt;br&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;liquid&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;    {% when &#39;&quot;&lt;/span&gt;&lt;span class=&quot;token variable&quot;&gt;${name}&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;&#39; %}&quot;&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;$&#39;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;&#39;&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;liquid&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;    &lt;span class=&quot;token variable&quot;&gt;&lt;span class=&quot;token variable&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;token function&quot;&gt;cat&lt;/span&gt; $svg&lt;span class=&quot;token variable&quot;&gt;)&lt;/span&gt;&lt;/span&gt;&quot;&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token assign-left variable&quot;&gt;liquid&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;$&#39;&lt;span class=&quot;token entity&quot; title=&quot;&#92;n&quot;&gt;&#92;n&lt;/span&gt;&#39;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token keyword&quot;&gt;done&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token assign-left variable&quot;&gt;liquid&lt;/span&gt;&lt;span class=&quot;token operator&quot;&gt;+=&lt;/span&gt;&lt;span class=&quot;token string&quot;&gt;&quot;{% endcase %}&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token builtin class-name&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;&lt;span class=&quot;token variable&quot;&gt;${liquid}&lt;/span&gt;&quot;&lt;/span&gt; &lt;span class=&quot;token operator&quot;&gt;&gt;&lt;/span&gt; &lt;span class=&quot;token variable&quot;&gt;$2&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add the script to a &lt;code&gt;svg-to-liquid.sh&lt;/code&gt; file and run &lt;code&gt;svg-to-liquid.sh iconSourcePath icons.liquid&lt;/code&gt; to generate the &lt;code&gt;icons.liquid&lt;/code&gt; file. Add it to your theme&#39;s snippets, then you can output the icon using &lt;code&gt;{% render &#39;icons&#39;, icon: &#39;icon-name&#39; %}&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I&#39;m no bash wizard so please go ahead and @ me on &lt;a href=&quot;https://mastodon.social/@Jbasoo&quot;&gt;Mastodon&lt;/a&gt; if you&#39;re nerd-sniped into cleaning it up.&lt;/p&gt;
</content>
	</entry>
	
	<entry>
		<title>Control Pixel Art Sprites With CSS Scroll-Driven Animations</title>
		<link href="https://jamesbasoo.com/blog/scroll-driven-pixel-art-sprite/"/>
		<updated>2023-10-31T00:00:00Z</updated>
		<id>https://jamesbasoo.com/blog/scroll-driven-pixel-art-sprite/</id>
		<content type="html">&lt;p&gt;These fancy, new, CSS scroll-driven animations are proving to be pretty darn powerful. I found &lt;a href=&quot;https://www.bram.us/2023/10/23/css-scroll-detection/&quot;&gt;Bramus&#39; concept&lt;/a&gt; of using them to detect scroll direction intriguing, and after experimenting a bit, discovered that detecting multiple axes allows you to directionally control an image sprite! Without JavaScript! This involves creating &lt;a href=&quot;https://blog.logrocket.com/making-css-animations-using-a-sprite-sheet/&quot;&gt;stepped sprite animations&lt;/a&gt; for each direction and running them when that direction is detected.&lt;/p&gt;
&lt;div class=&quot;breakout&quot;&gt;
  &lt;video controls=&quot;&quot; loop=&quot;&quot; src=&quot;https://jamesbasoo.com/video/pixel-art-scroll-8-dir.mp4&quot;&gt;&lt;/video&gt;
&lt;/div&gt;
&lt;h2&gt;The Concept&lt;/h2&gt;
&lt;p&gt;I&#39;d recommend reading the &lt;a href=&quot;https://www.bram.us/2023/10/23/css-scroll-detection/#the-concept&quot;&gt;single axis explanation&lt;/a&gt; first as Bramus goes into the core concept. The multi axis is essentially the same, just duplicated for the horizontal axis which can be seen in the &lt;a href=&quot;https://www.bram.us/2023/10/23/css-scroll-detection/#demo-wormhole&quot;&gt;Wormhole demo&lt;/a&gt; and my &lt;a href=&quot;https://codepen.io/jbasoo/pen/NWoNvLx&quot;&gt;reduced experiment&lt;/a&gt;. Detecting both axes just comes down to adding another animation timeline for the inline axis, and when you combine this with style queries, you get 4 way control over an image sprite.&lt;/p&gt;
&lt;div class=&quot;breakout&quot;&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@container&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;--scroll-block-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token selector&quot;&gt;.character:before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token property&quot;&gt;animation-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; to-bottom&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@container&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;--scroll-inline-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token selector&quot;&gt;.character:before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token property&quot;&gt;animation-name&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; to-right&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div class=&quot;feature-fallback sda breakout&quot;&gt;
    &lt;div class=&quot;feature&quot;&gt;
        &lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;VwgazqR&quot; data-user=&quot;jbasoo&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
            &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/jbasoo/pen/VwgazqR&quot;&gt;
            4 direction pixel art sprite control with CSS Scroll-Driven Animations&lt;/a&gt; by James Basoo (&lt;a href=&quot;https://codepen.io/jbasoo&quot;&gt;@jbasoo&lt;/a&gt;)
            on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
        &lt;/p&gt;
    &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;fallback&quot;&gt;
&lt;video controls=&quot;&quot; loop=&quot;&quot; src=&quot;https://jamesbasoo.com/video/pixel-art-scroll-4-dir.mp4&quot;&gt;&lt;/video&gt;
&lt;p&gt;&lt;strong&gt;Your browser doesn&#39;t support Scroll-Driven Animations, here&#39;s a video of the demo instead.&lt;/strong&gt; &lt;a href=&quot;https://codepen.io/jbasoo/pen/VwgazqR&quot;&gt;Live demo on Codepen&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;You might notice that diagonal movement is a little rough, however this is totally fixable. My &lt;a href=&quot;https://codepen.io/jbasoo/pen/NWoNvLx&quot;&gt;reduced experiment&lt;/a&gt; shows that &lt;strong&gt;it&#39;s possible to infer a diagonal directions!&lt;/strong&gt; By combining horizontal and vertical detection in &lt;a href=&quot;https://developer.chrome.com/blog/style-queries/&quot;&gt;style queries&lt;/a&gt;, we get 8 detectable directions!&lt;/p&gt;
&lt;div class=&quot;breakout&quot;&gt;
&lt;pre class=&quot;language-css&quot; tabindex=&quot;0&quot;&gt;&lt;code class=&quot;language-css&quot;&gt;&lt;span class=&quot;token atrule&quot;&gt;&lt;span class=&quot;token rule&quot;&gt;@container&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;--scroll-block-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;token keyword&quot;&gt;and&lt;/span&gt; &lt;span class=&quot;token function&quot;&gt;style&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;token property&quot;&gt;--scroll-inline-direction&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; 1&lt;span class=&quot;token punctuation&quot;&gt;)&lt;/span&gt;&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token selector&quot;&gt;h1:before&lt;/span&gt; &lt;span class=&quot;token punctuation&quot;&gt;{&lt;/span&gt;&lt;br&gt;    &lt;span class=&quot;token property&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;token string&quot;&gt;&quot;↘&quot;&lt;/span&gt;&lt;span class=&quot;token punctuation&quot;&gt;;&lt;/span&gt;&lt;br&gt;  &lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;token punctuation&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;This then allows us to use an 8 direction character sprite to handle diagonal movement, albeit with slightly janky results.&lt;/p&gt;
&lt;div class=&quot;feature-fallback sda breakout&quot;&gt;
    &lt;div class=&quot;feature&quot;&gt;
        &lt;p class=&quot;codepen&quot; data-height=&quot;300&quot; data-default-tab=&quot;result&quot; data-slug-hash=&quot;VwgaJGz&quot; data-user=&quot;jbasoo&quot; style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&gt;
            &lt;span&gt;See the Pen &lt;a href=&quot;https://codepen.io/jbasoo/pen/VwgaJGz&quot;&gt;
            8 direction pixel art sprite control with CSS Scroll-Driven Animations&lt;/a&gt; by James Basoo (&lt;a href=&quot;https://codepen.io/jbasoo&quot;&gt;@jbasoo&lt;/a&gt;)
            on &lt;a href=&quot;https://codepen.io&quot;&gt;CodePen&lt;/a&gt;.&lt;/span&gt;
        &lt;/p&gt;
    &lt;/div&gt;
&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;fallback&quot;&gt;
&lt;video controls=&quot;&quot; loop=&quot;&quot; src=&quot;https://jamesbasoo.com/video/pixel-art-scroll-8-dir.mp4&quot;&gt;&lt;/video&gt;
&lt;p&gt;&lt;strong&gt;Your browser doesn&#39;t support Scroll-Driven Animations, here&#39;s a video of the demo instead.&lt;/strong&gt; &lt;a href=&quot;https://codepen.io/jbasoo/pen/VwgaJGz&quot;&gt;Live demo on Codepen&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;&lt;p&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;h2&gt;Caveats&lt;/h2&gt;
&lt;p&gt;The main caveat here is that the character doesn&#39;t remain in the direction they&#39;re pointed at. This is due to the scroll direction not persisting and returning to an idle state. I&#39;m pretty sure I saw a demo around for triggering and persisting a state through scroll-driven animations or maybe &lt;code&gt;animation-fill-mode&lt;/code&gt; might help, but it&#39;s 2AM at the time of writing so that&#39;s a job for future James.&lt;/p&gt;
&lt;p&gt;Another caveat is that the web just wasn&#39;t meant for this. Controlling a character via scroll is pretty janky so I&#39;m not sure it would be viable for an actual game. Still, it&#39;s a fun, zero JS experiment!&lt;/p&gt;
&lt;h2&gt;Pondering Possibilities&lt;/h2&gt;
&lt;p&gt;The original demos utilised scroll velocity but I haven&#39;t tried that yet. The possibilities seem endless here, but I was pondering whether showing cartoony &amp;quot;speed lines&amp;quot; on fast scroll would be feasible. &lt;a href=&quot;https://front-end.social/@bramus/111285106651170862&quot;&gt;Bramus sounded hopeful&lt;/a&gt; so I may give that a shot at some point.&lt;/p&gt;
&lt;p&gt;To be honest I still haven&#39;t fully grasped the original concept, I need to do more tinkering. I&#39;ve only managed to detect 8 directions, but maybe one of you maths boffins can figure out more fine-grained directionality based on the velocities/directions.&lt;/p&gt;
&lt;p&gt;Happy coding, you got this!&lt;/p&gt;
&lt;hr&gt;
&lt;h2&gt;Attributions&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.bram.us/2023/10/23/css-scroll-detection/#the-concept&quot;&gt;Directional control concept&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Assets: &lt;a href=&quot;https://cupnooble.itch.io/sprout-lands-asset-pack&quot;&gt;1&lt;/a&gt; &lt;a href=&quot;https://axulart.itch.io/small-8-direction-characters&quot;&gt;2&lt;/a&gt;&lt;/p&gt;
</content>
	</entry>
</feed>
