<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GrahamTheDev</title>
    <description>The latest articles on DEV Community by GrahamTheDev (@grahamthedev).</description>
    <link>https://dev.to/grahamthedev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F551686%2Ff0514090-260b-4a34-89b0-a2b6e922d5ec.jpg</url>
      <title>DEV Community: GrahamTheDev</title>
      <link>https://dev.to/grahamthedev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/grahamthedev"/>
    <language>en</language>
    <item>
      <title>3 words worth a billion dollars: Drift to Determinism (DriDe)</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sat, 07 Mar 2026 10:44:43 +0000</pubDate>
      <link>https://dev.to/grahamthedev/3-words-worth-a-billion-dollars-drift-to-determinism-dride-dej</link>
      <guid>https://dev.to/grahamthedev/3-words-worth-a-billion-dollars-drift-to-determinism-dride-dej</guid>
      <description>&lt;p&gt;I doubt I am the first to come up with this concept, but I am probably the first to name it.&lt;/p&gt;

&lt;p&gt;Drift to Determinism (DriDe - as in "DRY'd" - Don't Repeat Yourself) is what everyone will be doing in 2 years, and I am telling you to start today.&lt;/p&gt;

&lt;p&gt;And if you want the one liner explanation: I am proposing that while most people are trying to add more AI and then guard it, instead we should be building systems in a way that we write AI out of them entirely / as much as possible over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ok smart boi - what is Drift to Determinism?
&lt;/h2&gt;

&lt;p&gt;Well it isn't the long awaited second instalment of Fast and The Furious 3 (sadly - but that would be an awesome title right?).&lt;/p&gt;

&lt;p&gt;No it is a philosophy on how you should be thinking about AI. &lt;/p&gt;

&lt;p&gt;Everyone is out there using AI agent systems and burning tokens like they are going to run out one day. &lt;/p&gt;

&lt;p&gt;Watching people spend $20 to set a reminder to buy milk just hurts my soul (&lt;a href="https://x.com/BenjaminDEKR/status/2017644773356548532" rel="noopener noreferrer"&gt;yes, really, that happened&lt;/a&gt;...the heartbeat every 30 minutes to check the time was &lt;strong&gt;eating&lt;/strong&gt; tokens!).&lt;/p&gt;

&lt;p&gt;I believe we will wake from this fever dream soon where "all things are solved with AI" and realise there is a simple flow that will make us able to do almost anything, at a fraction of the cost and environmental impact.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Simple steps:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Give the AI agent system a "novel" task it hasn't seen before and let it burn a load of tokens to solve it.&lt;/li&gt;
&lt;li&gt;Put a second agent system at the end watching what could have been worked out deterministically (i.e. in code).&lt;/li&gt;
&lt;li&gt;Build the tools for the repeatable parts.&lt;/li&gt;
&lt;li&gt;Next time a similar task comes along - feed the tools in at step 1.&lt;/li&gt;
&lt;li&gt;See if we always use tool1 and feed it to tool6 - wire them together.&lt;/li&gt;
&lt;li&gt;Repeat the process until you write out every part of the AI that is possible.&lt;/li&gt;
&lt;li&gt;There are loads of little nuances like falling back to AI if a tool doesn't give the right output for this job, running shadow versions of workflows to check we are actually improving, providing final output feedback to fine tune, producing a system that the LLM can understand, full tracking of the process...but you can work that out right :-)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Over time your AI powered non-deterministic workflows that cost $50 to run and only work 50% of the time without you side-prompting it back on track become glorious automations that use $0.02 in AI to categorise them and then just run in code.&lt;/p&gt;

&lt;p&gt;It's faster, it is more consistent, it can be trusted.&lt;/p&gt;

&lt;p&gt;This is where we are headed. &lt;/p&gt;

&lt;h2&gt;
  
  
  Yeah, people are building skills and tools, what is new here?
&lt;/h2&gt;

&lt;p&gt;That is the point - we already kinda have the things we need to make this work, but fundamentally miss the mark on how we treat them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nobody, and I mean nobody has the objective to write AI out of a process they are currently doing with AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You name me one tool / product that has used AI &lt;strong&gt;less&lt;/strong&gt; in the last year. &lt;/p&gt;

&lt;p&gt;Go on, I am waiting.&lt;/p&gt;

&lt;p&gt;And yet that is actually what I am proposing.&lt;/p&gt;

&lt;p&gt;You use AI to get the outline of a process down. It is expensive, slow (in comparison to code) but it solves a repetitive business process problem.&lt;/p&gt;

&lt;p&gt;Then, you analyse that process. Do I really need to pass all 12000 rows of our company client list into AI to know who to call next? Nope, simple tool to grab the next 5 people not called in a month. &lt;/p&gt;

&lt;p&gt;Do I even need to give that tool to the agent? No, I should make it part of context so that it has that info and we save a load of round trips. &lt;/p&gt;

&lt;p&gt;Hang on a minute, are we giving the AI a tool to then go look up their website? Well if it needs that info we should just do that automatically and feed that into the context.&lt;/p&gt;

&lt;p&gt;Hang on a minute, we have scanned their website before? We have the info? Do we even need the agent to fire up at all?&lt;/p&gt;

&lt;p&gt;You get the idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crystallisation is the key
&lt;/h2&gt;

&lt;p&gt;Every time you call an AI you roll the dice - quite literally. &lt;/p&gt;

&lt;p&gt;It has gotten a lot better, but it is and will always be a non-deterministic system. It will always give different output no matter how hard you prompt it. &lt;/p&gt;

&lt;p&gt;Sometimes you need the power of AI - for example to process natural language (or do you)?&lt;/p&gt;

&lt;p&gt;Or to write code (or do you?)&lt;/p&gt;

&lt;p&gt;Every time you call AI, question how much of it needs to be done fully autonomously using a LLM and how much is a deterministic step. &lt;/p&gt;

&lt;p&gt;Writing code - well we have every code snippet in the world available, every challenge can be broken down into code that already exists and has been battle tested. We just need to wire it up differently. &lt;/p&gt;

&lt;p&gt;So should we be letting AI write the code, or give it code we know works and ask it to wire it together to solve a novel problem?&lt;/p&gt;

&lt;p&gt;Processing natural language? Well we have had code based tools that can do 70% of the work a LLM can do - why not get them to do a first pass and find the areas to focus the power of an LLM at, reducing context size, cost and chance of missing key bits?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CRYSTALLISE&lt;/strong&gt; your process. Make it as deterministic and repeatable as possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sounds like a lot of work
&lt;/h2&gt;

&lt;p&gt;Well, yeah, kinda.&lt;/p&gt;

&lt;p&gt;There is certainly a capability gap at the moment where LLMs are decent at spotting where to optimise a process, but not creative enough to work out which bits are best to work on. &lt;/p&gt;

&lt;p&gt;It still needs human judgement and guidance (hurrah - we are still safe for now!).&lt;/p&gt;

&lt;p&gt;But it can certainly look at what it did and then give you areas to poke at. &lt;/p&gt;

&lt;p&gt;It can certainly then take your judgement and offer possible solutions. &lt;/p&gt;

&lt;p&gt;All it needs your grey matter for is what to work on, which way to approach it and how specialised or generalised to make a function / step.&lt;/p&gt;

&lt;p&gt;Once you have built enough of these tools (skills, MCP, workflows, whatever) then you can teach it to build its own workflows. &lt;/p&gt;

&lt;p&gt;You then become the judge of the workflows, rather than the judge of individual parts.&lt;/p&gt;

&lt;h2&gt;
  
  
  My prediction
&lt;/h2&gt;

&lt;p&gt;In 2-5 years you will be sat at your terminal with a novel problem for your business. We need to reconcile the bank automatically for accounting. &lt;/p&gt;

&lt;p&gt;You explain the desired outcome, provide examples of good and bad results, the data etc. &lt;/p&gt;

&lt;p&gt;AI will look at all its tools and build you a workflow to achieve this. It doesn't have all the tools it needs yet so it still uses vision models, LLMs that are good at categorising etc. &lt;/p&gt;

&lt;p&gt;You will run it in test mode, work with the AI to adjust it for edge cases and then run it. It does the job, you push it to "shadow mode" and run it alongside the current process.&lt;/p&gt;

&lt;p&gt;Now it starts optimising itself out of the process.&lt;/p&gt;

&lt;p&gt;It builds out individual parsers for every supplier's invoice format using OCR and pattern matching, running in milliseconds on each invoice. It pulls the bank feed back and checks the amount against the invoice, all in code, the LLM doesn't even fire up, except to kick off the "reconciliation flow". &lt;/p&gt;

&lt;p&gt;It works just as well as the current process with 99%+ accuracy as we are using deterministic steps for 99% of the workflow.&lt;/p&gt;

&lt;p&gt;3 months later one of the documents we feed in changes format - the tensorflow OCR tool fails to find the invoice number. It falls back to a vision model that locates the new location of the invoice number. Prompts you "hey, looks like supplier X's invoices have changed format - is this the right number" showing you a screenshot of the invoice and a highlight around the relevant items. &lt;/p&gt;

&lt;p&gt;You tell it it is good to go and it self heals and runs off to complete this months bank reconciliation. &lt;/p&gt;

&lt;p&gt;Compare that to how we would currently envision doing it: a call to a vision model for every single invoice, provide the LLM with a tool for that. Then we give it a tool to read the bank transactions - sending private data out to the cloud. Then it confuses an invoice number for a account number, asks for help, we prompt it and it updates it's instruction set, only to fail again on the next invoice. &lt;/p&gt;

&lt;p&gt;It is costly, slow, error prone and although it is better than our old fully human process, it is nowhere near ideal.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I think of LLMs
&lt;/h2&gt;

&lt;p&gt;Every single token output by a LLM is a point of failure. &lt;/p&gt;

&lt;p&gt;Even if we get LLMs to 99.999% accuracy (which would be amazing right?), if you had a workflow that had 10000 passes how accurate would your output be? &lt;/p&gt;

&lt;p&gt;No not 99%, it would be 90%. (0.99999^10000 is 90%)&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;90% accuracy is business destroying: you are getting sued or going bankrupt. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But if you build your LLM system with a single goal for the LLM: "make yourself obsolete" - then you can flourish. &lt;/p&gt;

&lt;p&gt;You can remove all of the mundane from the business, all of the human effort going into busy work. &lt;/p&gt;

&lt;p&gt;The LLMs give you the power to reduce the cost of building automations to 1% of what they used to cost to implement. &lt;/p&gt;

&lt;p&gt;Small businesses can out-compete the big players with agility on a scale never seen before. &lt;/p&gt;

&lt;p&gt;But only if their systems are robust.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, are you building a Hallucination Factory or a Deterministic Dynamo?
&lt;/h2&gt;

&lt;p&gt;Are you building a token incinerating, dice throwing monster? &lt;/p&gt;

&lt;p&gt;Or are you building a streamlined, bullet-proof replacement for inefficiency?&lt;/p&gt;

&lt;p&gt;Probably somewhere in-between, but if your guiding principle in everything you do is &lt;strong&gt;DriDe&lt;/strong&gt; - then as you Drift to Determinism and Don't Repeat Yourself you will gain an edge. &lt;/p&gt;

&lt;p&gt;You will have a sharp surgical tool automating key workflows while your competition is bludgeoning their process into submission and veering towards a disaster.&lt;/p&gt;

&lt;p&gt;The reduction in costs, the increase in certainty, the avoidance of lawsuits - all from 3 words must easily be worth a billion dollars.&lt;/p&gt;

&lt;p&gt;Let DriDe guide you to success, start the drift today. &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>automation</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Mean Time to Understanding 🤔: The Irreducible Human Element in the Age of Infinite Code 🤖.</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sun, 21 Dec 2025 22:06:39 +0000</pubDate>
      <link>https://dev.to/grahamthedev/mean-time-to-understanding-the-irreducible-human-element-in-the-age-of-infinite-code--i2o</link>
      <guid>https://dev.to/grahamthedev/mean-time-to-understanding-the-irreducible-human-element-in-the-age-of-infinite-code--i2o</guid>
      <description>&lt;p&gt;This article (talk) is based on an idea I have been thinking about for a while: while everyone is talking about AI, either from a hype perspective or a doomsday perspective...where is the middle?&lt;/p&gt;

&lt;p&gt;Who is actually looking at the practicalities of AI, the gaps we have in organisations, and, above all, the human aspect of the new Intelligence Age?&lt;/p&gt;

&lt;p&gt;So bearing in mind that it is written as a talk, please enjoy my partially formed idea (that needs some editing) as I think the key point of the talk: &lt;strong&gt;that understanding code is the new metric we should be paying attention to&lt;/strong&gt;, is an important one.&lt;/p&gt;

&lt;p&gt;The talk (draft):&lt;/p&gt;

&lt;h2&gt;
  
  
  The Inflation of Syntax
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;“In the age of AI, the most dangerous engineer in your organisation is no longer the slowest one.  It’s the fastest one.”&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The engineer who can ship features at incredible speed.&lt;/li&gt;
&lt;li&gt;The team with the highest velocity.&lt;/li&gt;
&lt;li&gt;The dashboard that looks green every sprint.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not as fantastic as they may first sound.&lt;/p&gt;

&lt;p&gt;In a world where code is cheap and infinite, speed no longer tells you whether you’re winning. It tells you how fast you’re accumulating something you may not be able to understand, fix, or control.&lt;/p&gt;

&lt;p&gt;But I will come to that. &lt;/p&gt;

&lt;p&gt;First, lets look at what software engineering used to be, and what has just fundamentally changed.&lt;/p&gt;

&lt;p&gt;For 50+ years, everyone in this room was paid to know a secret language. We were paid to know the dictionary. We were paid to speak "Machine."&lt;/p&gt;

&lt;p&gt;If a business leader wanted a feature: a new checkout flow, a search bar, a data pipeline, they had a problem. They had intent, but they didn't have the syntax. They needed us to translate their human intent into Java, into SQL, into React. We knew how to say things to machines that other people couldn't.&lt;/p&gt;

&lt;p&gt;And because that skill was scarce, we charged a tax for that translation. That tax justified our salaries, our timelines, our agile rituals, and entire careers.&lt;/p&gt;

&lt;p&gt;But something fundamental has shifted. Today, the machine speaks its own language. The dictionary is public. The translation tax is rapidly approaching zero.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I want to be clear: this doesn't mean software engineering is done.&lt;/strong&gt; But it means the most visible part of our historical value, the part that people could see and appreciate - the actual act of turning English requirements into syntax - has been commoditised.&lt;/p&gt;

&lt;p&gt;The obvious question we have to answer today is: If coding is no longer scarce, where does the value now lie in software engineering?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Crisis: Inflation and The Efficiency Trap
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;I hear a lot of fear about "replacement." But what we are facing isn't a crisis of replacement. It's a crisis of inflation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think about basic economics. When you print too much money, the value of the currency drops. When you print code (which we can now do at almost infinite scale) the value of writing a line of code drops.&lt;/p&gt;

&lt;p&gt;We are moving from an era of scarcity to an era of abundance. And this leads to the biggest mistake I see companies making right now. &lt;/p&gt;

&lt;p&gt;I call it The Efficiency Trap.&lt;/p&gt;

&lt;p&gt;Executives look at this abundance and think: "Great! If AI makes developers 50% faster, I can fire 50% of them and get the same output." (For those of you questioning that statement: that line was written by ChatGPT. &lt;strong&gt;It’s confident, crisp, and perfectly wrong.&lt;/strong&gt; A 50% productivity gain means you fire a third of your team, not half. It ties in with the rest of this talk beautifully, so I left it in.)&lt;/p&gt;

&lt;p&gt;But AI over-confidence aside: &lt;strong&gt;This thinking is fundamentally wrong.&lt;/strong&gt; Executives see the short-term gain, but they’re blind to long-term complexity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Abundance hides risk.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each additional line of code, each new microservice, each new integration can quietly accumulate complexity faster than anyone can track it. The system may appear healthy. Dashboards may look green. But underneath, fragility grows.&lt;/p&gt;

&lt;p&gt;This is where instincts betray us. We look at this explosion of output and think we need to measure it harder. Track it more closely. Optimise it more aggressively.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In the AI era, velocity is no longer a leading indicator. It’s a lagging one.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But worshipping at the altar of velocity metrics will ultimately be your downfall.&lt;/p&gt;

&lt;p&gt;When you optimise directly for speed, you inflate complexity. When you optimise for understanding, for truly knowing what you’ve built and how it behaves, the system flows naturally, and velocity emerges as a by-product.&lt;/p&gt;

&lt;p&gt;Teams that focus only on output will eventually stall, not because they’re slow, but because they are afraid to touch what they’ve built.&lt;/p&gt;

&lt;p&gt;If you think maintaining your current legacy code is hard, wait until you have to maintain the mountain of code your team is about to generate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pivot: Mean Time to Understanding
&lt;/h2&gt;

&lt;p&gt;In any system, value always shifts to the constraint. If writing code is cheap, abundant, and fast... what is the new bottleneck?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The constraint is no longer writing software, it is understanding what was written.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The constraint is knowing what this generated blob of code actually does, where it behaves, and more importantly, where it misbehaves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;We need to stop obsessing over velocity and start obsessing over MTTU: Mean Time to Understanding.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;DevOps and SRE teams have long used this term to measure the gap between an alert and a diagnosis; it is time the rest of us adjusted and adopted it for the code itself.&lt;/p&gt;

&lt;p&gt;MTTU in the context of a developer is the time it takes a human being, who is not the original author, to confidently answer two questions: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What does this code actually do? &lt;/li&gt;
&lt;li&gt;Where would I look in order to fix it if it breaks?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s the kicker: &lt;strong&gt;if you optimise for MTTU...if you design systems and teams to maximise understanding...everything else follows.&lt;/strong&gt; Velocity, reliability, maintainability, all of it improves naturally.&lt;/p&gt;

&lt;p&gt;If you ignore understanding and chase output alone, the system will eventually stall. Not because you’re slow, but because complexity has grown faster than anyone can reason about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every architectural decision you make from now on either compresses MTTU or inflates it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The ability to quickly understand and verify code you didn't write, this is the skill that is the new core skill. This is where seniors have transferable skills in this new age. &lt;/p&gt;

&lt;p&gt;Your years of pattern recognition, your mental models, your hard-won judgment actually pays dividends. &lt;/p&gt;

&lt;p&gt;The AI can generate infinite solutions, but you are the one who can look at those solutions and understand what they actually mean for your system.&lt;/p&gt;

&lt;p&gt;But with that in mind, let's look at &lt;em&gt;why&lt;/em&gt; this metric and principle is so important.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Diagnosis: Plausible Competence
&lt;/h2&gt;

&lt;p&gt;The first problem is counter-intuitive. The problem is not that AI is bad at coding. The problem is that it is good. &lt;strong&gt;AI is good enough to be plausibly competent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is not a junior developer. AI is an infinite yes-man. It biases toward creation.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It will always give you something. &lt;/p&gt;

&lt;p&gt;If you ask an AI to solve a problem where the correct engineering answer is "don't write code, just change this config file," the AI will rarely tell you that. It will write you a 500-line script and bypass the config.&lt;/p&gt;

&lt;p&gt;It builds what you asked for, not what you meant.&lt;/p&gt;

&lt;p&gt;It produces code that looks correct. It uses the right libraries. It follows the right indentation. It might even pass the unit tests (that it wrote for itself of course).&lt;/p&gt;

&lt;p&gt;If you've read Thinking, Fast and Slow by Daniel Kahneman, then you know about System 1 (fast, intuitive) and System 2 (slow, deliberate) thinking and the problem with this plausibly competent code will be familiar to you.&lt;/p&gt;

&lt;p&gt;AI-generated code is good enough that it activates our System 1 thinking. We glance at it, it looks like valid code, the logic looks good at-a-glance, and we nod. "Looks great."&lt;/p&gt;

&lt;p&gt;But this is the trap. &lt;strong&gt;We trust the plausibility, so we turn off our brain.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We stop simulating the code in our head. We accept the solution without stepping back to verify if it fits the nuance of our specific system. We never engage our slow and deliberate thinking.&lt;/p&gt;

&lt;p&gt;And because we trust it too easily, we let it do things we would never let a human do. Which leads to the second trap.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trap: Complexity Sprawl
&lt;/h2&gt;

&lt;p&gt;Here is how this behaviour shows up in the real world: &lt;/p&gt;

&lt;p&gt;Imagine a team asks for a simple feature. Let's say, a web form to submit a username and an email address.&lt;/p&gt;

&lt;p&gt;In the old world, a developer would groan, write a single file, hopefully a little validation logic, and ship it. &lt;/p&gt;

&lt;p&gt;But now? The developer asks the AI to "handle the submission robustly."&lt;/p&gt;

&lt;p&gt;The AI scaffolds. It generates a separate microservice for validation. It adds a retry queue for failed submissions (because queues are "robust"). It spins up a serverless function to sanitise input. It adds a distributed logging service just for completeness.&lt;/p&gt;

&lt;p&gt;Technically? All of it works. Syntactically? It is perfect. But here is the trap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI is a Local Optimiser.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It looks at the ticket "make the form robust" and it solves that specific problem with &lt;strong&gt;maximum force&lt;/strong&gt;. It doesn't care about the rest of your system. &lt;/p&gt;

&lt;p&gt;That is your job. You are the global optimiser. You are the architect. &lt;/p&gt;

&lt;p&gt;Your job is to know how that form fits into the user session, the billing system, and the legacy database.&lt;/p&gt;

&lt;p&gt;But because the AI just generated five new services and three queues, it has created a complexity spike. To judge if this solution is correct, you now have to load all that new complexity into your head. You have to map out the failure modes of five services instead of just one file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is cognitive bloat. And this is where the danger lies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When the solution becomes too complex to hold in your working memory, you stop verifying the architecture. You stop asking "Does this fit?" and you start accepting "It works."&lt;/p&gt;

&lt;p&gt;You surrender your role as architect because the blueprint just became too messy to read.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Constraint: Architectural Verification
&lt;/h2&gt;

&lt;p&gt;This brings us to the hard limit. The limit isn't how fast we can write code. The limit is Serialisation Bandwidth.&lt;/p&gt;

&lt;p&gt;To be the architect, to ensure a solution is safe, compliant, and correct, you must be able to serialise the logic into your brain. You have to be able to "run" the system in your mind. If I change X here, does it break Y over there?&lt;/p&gt;

&lt;p&gt;We now have &lt;strong&gt;infinite generation bandwidth&lt;/strong&gt; (the AI) feeding into &lt;strong&gt;fixed serialisation bandwidth&lt;/strong&gt; (your brain).&lt;/p&gt;

&lt;p&gt;Serialisation bandwidth is your brain's ability to hold the system in memory. No matter how fast AI types, if your mind can't trace the logic, the system is unsafe.&lt;/p&gt;

&lt;p&gt;Now, the AI worshippers and optimists in the room (and the vendors selling you AI tools) will say: "That's fine! We'll just give the AI the context. We'll feed it the docs. We'll prompt it better."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the most dangerous lie in our industry right now. You cannot prompt for context you don't know is relevant yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Context in software is not a static database you can just upload to a vector store. Context is dynamic.&lt;/p&gt;

&lt;p&gt;You don't know that the "User" object has a hidden dependency on the "Billing" object in another project until you see the specific way the code tries to mutate it. You don't know that this specific retry logic violates a new compliance rule until you see the queue being built.&lt;/p&gt;

&lt;p&gt;Context is the collision between the code and the World. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI only sees the code. You are the only one who sees the real-world context.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you cannot understand the code faster than it is created (and you can't), you lose the ability to spot those collisions. You lose the ability to govern the system.&lt;/p&gt;

&lt;p&gt;The bottleneck isn't typing speed. The bottleneck is architectural verification speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Good vs Bad Velocity
&lt;/h2&gt;

&lt;p&gt;And yet, look at our Jira boards. We still measure progress using Velocity. Features per sprint. Tickets closed. Lines shipped. That mismatch is how we manufacture cognitive debt.&lt;/p&gt;

&lt;p&gt;In the new World: &lt;strong&gt;"Good velocity" is shipping features while keeping Mean Time to Understanding flat. "Bad velocity" is shipping features by spiking MTTU.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you ship a feature in four hours using AI, but it takes three days for a senior engineer to understand how to fix it when it breaks next week, then you MTTU is way higher than your code velocity. &lt;/p&gt;

&lt;p&gt;If your MTTU is too high, then you are in cognitive debt. &lt;/p&gt;

&lt;p&gt;And this debt doesn't just stay in code. &lt;strong&gt;It leaks.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It shows up as: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Longer incidents (because nobody knows where the root cause is). &lt;/li&gt;
&lt;li&gt;Slower onboarding (because new hires can't read the map). &lt;/li&gt;
&lt;li&gt;Feature paralysis (because everyone is afraid to touch the "magic code").&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To further illustrate this, let me ask you something:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How many of you have merged a PR in the last month that you didn't fully understand? &lt;/li&gt;
&lt;li&gt;Keep your hand up if you told yourself you'd come back and review it properly later.&lt;/li&gt;
&lt;li&gt;Keep your hand up if you actually did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Yeah. I didn’t do it either!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is why we need a new protocol.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Protocol: Spec-Driven Development
&lt;/h2&gt;

&lt;p&gt;Don't worry. I am not talking about waterfall here with specs...just in case you were about to zone out!&lt;/p&gt;

&lt;p&gt;So far I've told you what's broken and why. But I don't want you walking out of here just feeling anxious. I want you to have a protocol you can use tomorrow. So let me get specific.&lt;/p&gt;

&lt;p&gt;We have to change the protocol. &lt;/p&gt;

&lt;p&gt;We need to move to AI and MTTU friendly Spec-Driven Development. &lt;/p&gt;

&lt;p&gt;In this new world, we don't start by reading code. We start by establishing the structure. &lt;/p&gt;

&lt;p&gt;This happens in three distinct layers:&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 1: The Micro-Spec (Human Priming and Map)
&lt;/h3&gt;

&lt;p&gt;This is your survival tool. This is what keeps you sane day-to-day. It is strictly for you.&lt;/p&gt;

&lt;p&gt;It is the "Cognitive Handle." Before you generate a single line of code, you define the high-level intent in your head (or a sticky note).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Goal:&lt;/strong&gt; "Add a user archive feature." &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Constraint:&lt;/strong&gt; "Must be reversible." &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Architectural Fit:&lt;/strong&gt; "Does this belong in the User Service or the Admin Service?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Micro-Spec is your "BS and bad-fit Detector."&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It allows you to look at the AI's output and instantly answer: does this code fit the shape of our system? Does this code look to have the functionality we asked for?&lt;/p&gt;

&lt;p&gt;If the AI tries to rewrite the database schema for a simple UI change, the Micro-Spec tells you to reject it immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: The Main Spec (The Shared Contract)
&lt;/h3&gt;

&lt;p&gt;This is what keeps everyone aligned. &lt;/p&gt;

&lt;p&gt;Once you know the shape of the code, you need the substance.&lt;/p&gt;

&lt;p&gt;This is the Main Spec. This is the document that both you and the AI rely on.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;For the AI:&lt;/strong&gt; It is the instruction manual. It contains the acceptance criteria, the specific edge cases, the input/output definitions. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For You:&lt;/strong&gt; It is the checklist for Technical Verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the AI generates the code (and you fix the obvious bugs), you switch from "Architect" to "Auditor." You compare the code against the Main Spec.&lt;/p&gt;

&lt;p&gt;"Did it handle the null case we asked for?", "Did it implement the specific retry logic we defined?". You verify the Technical Requirements here. &lt;/p&gt;

&lt;p&gt;If the Micro-Spec checks the Soul of the code, the Main Spec checks the Body.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 3: The Global Context (The Subconscious)
&lt;/h3&gt;

&lt;p&gt;This is your evolutionary system, this is what makes you faster over time. Finally, we have the force multiplier. &lt;/p&gt;

&lt;p&gt;As we build, we will notice that we keep repeating certain instructions: "Use Tailwind." "Don't use ‘any’ types." "Follow the Repository Pattern." &lt;/p&gt;

&lt;p&gt;We don't put these in every Spec. We offload them into Global Context. &lt;/p&gt;

&lt;p&gt;This is where tools like CLAUDE.md files or .cursorrules files come in.&lt;/p&gt;

&lt;p&gt;We treat these files as our Evolutionary Rule Set. &lt;/p&gt;

&lt;p&gt;Every time the AI makes a stylistic mistake, we don't just fix the code. We update the Global Context. We add a rule. &lt;/p&gt;

&lt;p&gt;Over time, this automatically pulls the AI closer to your engineering culture.&lt;/p&gt;

&lt;p&gt;As this Global Context matures, the machine becomes an expert at your patterns. You will spend drastically less time reviewing syntax, imports, and folder structures.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So where does that saved time go? It goes to the one thing that can never be put into a context file: The ambiguity of the real world.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;No matter how well the AI knows our codebase, it doesn't know our strategy. It doesn't know that the marketing team just changed the launch date. It doesn't know that the users hate that specific modal.&lt;/p&gt;

&lt;p&gt;You stop being the Code Reviewer and start being the Reality Reviewer.&lt;/p&gt;

&lt;p&gt;The Micro-spec is the soul, the flavour of the code. &lt;br&gt;
The Main spec is the body, the substance of the code.&lt;br&gt;
The Global Context is the subconscious, the guiding principles of the code.&lt;/p&gt;

&lt;p&gt;But even with all that, you may be wondering where you are still valuable once the prompts and context are nailed down? What can humans do, that AI is very unlikely going to be able to do for the next 20 years?&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3 Human Moats
&lt;/h2&gt;

&lt;p&gt;Once you accept that role shift towards orchestration and evaluation, the "Prompting" argument dies completely. The AI cannot replace you, because the AI cannot cross these three Moats:&lt;/p&gt;

&lt;h3&gt;
  
  
  Moat 1: The Shadow Architecture (Context)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The AI suggests a solution based on the visible code. You reject it based on the invisible reality.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; The Load-Bearing Typo. &lt;/p&gt;

&lt;p&gt;The AI scans your API and finds an error message: “Err (500)”. It suggests a "Best Practice" improvement: "Let's make this closer to common terminology: Internal Server Error (500)."&lt;/p&gt;

&lt;p&gt;It is better DX, more descriptive and a better fit with industry terminology. &lt;/p&gt;

&lt;p&gt;But you know the World Context. You know that the Ops team has a legacy monitoring script that greps the logs specifically for the string “Err (500)”. If it sees that string, it automatically restarts the server.&lt;/p&gt;

&lt;p&gt;If you let the AI "fix" that message, the grep fails. The auto-restart fails. And the next time the server hangs, it stays down all weekend (or until you get a call to fix it...AI isn't going to get the call now is it?).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI sees the Text. You see the real world dependency.&lt;/strong&gt; You cannot prompt for the bash script running on a server the AI doesn't know exists until it becomes relevant.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moat 2: The Problem Definition (Value)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The AI answers the prompt exactly. You answer the implied need.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;The business asks for "fast search." &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The AI spins up Elasticsearch for real-time responses, costing thousands. &lt;/li&gt;
&lt;li&gt;You know that "fast" just means "under one second," and a simple SQL query works for free. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With AI you are not doing less engineering, you are doing higher-order engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moat 3: The System Pessimist (Side Effects)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The AI is a master of the Local Fix.&lt;/strong&gt; If you show it an error, it will make that error go away. It will write defensive code. It will catch the exception. But it is blind to Systemic Side Effects. &lt;/p&gt;

&lt;p&gt;It fixes a database timeout by adding a retry loop. &lt;/p&gt;

&lt;p&gt;It doesn't know that three layers up, the frontend also retries, and the load balancer also retries. It fixed the error locally, but it just created a Retry Storm that will take down your entire platform during the next traffic spike.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI looks at the file. You look at the blast radius. Your job is to ask: "If this code works perfectly, what else does it break?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;But this higher order engineering is not something you learn in bootcamps, so where do Juniors fit in to the new world?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Junior: The Explorer
&lt;/h2&gt;

&lt;p&gt;If you’ve been following along and you're early in your career and you're worried...&lt;strong&gt;good&lt;/strong&gt;. That means you're paying attention. But here's what you need to understand: AI doesn't make you obsolete. &lt;/p&gt;

&lt;p&gt;It makes you dangerous. *&lt;em&gt;Seniors are constrained by their mental models. They know what "should" work, so they'll ask the AI to build the obvious thing. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;You don't have those priors yet. &lt;/p&gt;

&lt;p&gt;You can ask the AI to try five different architectures in an afternoon without cognitive baggage. You're faster at exploration because you have less to unlearn.&lt;/p&gt;

&lt;p&gt;And that brings me to a question the C-suite and hiring managers may still be asking: *&lt;em&gt;why hire Juniors at all? If AI acts like a mid-level developer, why pay a human to learn? *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Because AI is an engine, not a driver.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The value of a Junior today is not "Code Production." It is Option Generation and Verification. &lt;/p&gt;

&lt;p&gt;In the old world, if we weren't sure how to build a feature, we sent a Senior to prototype it for three days. That was expensive. &lt;/p&gt;

&lt;p&gt;Now, we send a Junior. We say: "Here is the Spec. Use the AI to prototype three different ways to solve this. Map the trade-offs. Test the edge cases. Bring me the best one."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Junior provides immediate ROI because they act as a Force Multiplier for the Senior.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;They wade through the AI's hallucinations. They verify the libraries. They filter out the noise. They present the Senior not with blank text files, but with curated options.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Metaphor): The Junior explores the AI jungle, mapping the paths the machine might take, bringing back the safe trails for the Senior. &lt;br&gt;
They are valuable today because they do the legwork of exploration, allowing the Senior to make the high-leverage decision of selection. And in doing that work, they learn the judgment required to become Seniors.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Senior: The Orchestrator
&lt;/h2&gt;

&lt;p&gt;And the seniors in the room? You are the Orchestrators. You define the constraints. You decide where complexity is allowed and where it is forbidden. &lt;/p&gt;

&lt;p&gt;You audit the seams where code touches other parts of the code base. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You ensure that while the AI writes individual notes, the melody remains coherent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Metaphor): Each AI-generated line is an instrument; only the Senior ensures it plays the right melody. You are the only one who can see the whole symphony.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Conclusion: The Great Filter
&lt;/h2&gt;

&lt;p&gt;I want to close by addressing the elephant in the room. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We have established that AI provides infinite Generation Bandwidth. &lt;/li&gt;
&lt;li&gt;We have established that humans have fixed Understanding Bandwidth.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is only one way to resolve that equation. It is not to read faster.&lt;/p&gt;

&lt;p&gt;It is not to just "hire more people." The only way to survive is to become The Great Filter.&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;In an era where adding code is free, the most expensive thing you can do is accept it. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The highest-value engineering activity is no longer creating software. It is rejecting software.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you cannot say no to code, you are no longer an engineer in this new intelligence Age. You are a deployment pipeline with a salary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your value is looking at a "working" solution from the AI and saying: "No. This spikes our Mean Time to Understanding. Delete it. Simplify it. Do it again."&lt;/p&gt;

&lt;p&gt;We need to slow down the ingestion to match the speed of our digestion. &lt;/p&gt;

&lt;p&gt;We need to be the ones who say: "Just because we CAN generate a microservice for this in 30 seconds, doesn't mean we SHOULD."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your job is to stand in front of the floodgates&lt;/strong&gt;, to control the flow, to direct it safely, to ship with care and precision.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In a world where code flows like a river, your responsibility is clear: Hold back the tsunami of code.&lt;/strong&gt; Don’t let complexity and cognitive debt engulf and overwhelm your system.&lt;/p&gt;

&lt;p&gt;Don't just build the software. Be the only reason the software is still understandable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are the great filter&lt;/strong&gt;: AI might be able to generate endlessly, only you can decide what survives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Today your career is no longer defined by how much you ship. It is defined by how much complexity you refuse to ship.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Be the constraint. &lt;/p&gt;

&lt;p&gt;Protect your team's Mean Time to Understanding.&lt;/p&gt;

&lt;p&gt;Thank you.&lt;/p&gt;




&lt;p&gt;As I said, the talk needs some editing and polish, but I hope the theme and concept is thought provoking. I hope the message is clear. &lt;/p&gt;

&lt;p&gt;Our role is evolving, but not so much that all of your skills are obsolete. Just that you might need to spend a little less time on leet code and a little more time on architecture and getting better and more diligent at reviewing code. &lt;/p&gt;

&lt;p&gt;That you should be optimising for Mean Time To Understanding when you commit your code (or that generated by AI).&lt;/p&gt;

&lt;p&gt;Let me know if you think this would make an interesting talk, or if you would be doom scrolling X in the back?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>DOOM...*rendered* using a single DIV and CSS! 🤯🔫💥</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sat, 10 May 2025 08:12:01 +0000</pubDate>
      <link>https://dev.to/grahamthedev/doomrendered-using-a-single-div-and-css-1fal</link>
      <guid>https://dev.to/grahamthedev/doomrendered-using-a-single-div-and-css-1fal</guid>
      <description>&lt;p&gt;For clarity, I have not rebuilt DOOM in CSS...yet.&lt;/p&gt;

&lt;p&gt;No this is far simpler:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rendering the &lt;strong&gt;output&lt;/strong&gt; of DOOM&lt;/li&gt;
&lt;li&gt;into a single div&lt;/li&gt;
&lt;li&gt;using a single &lt;code&gt;background-image: linear-gradient&lt;/code&gt; block.&lt;/li&gt;
&lt;li&gt;all client side in the browser!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Is it silly?&lt;/strong&gt; Yes&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why did I do it?&lt;/strong&gt; I take it you have never read my articles before, I do silly things with web technologies to learn things...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why should you read this nonsense?&lt;/strong&gt; - well you get to play DOOM rendered with CSS for a start! &lt;/p&gt;

&lt;p&gt;But seriously, the code can show you some interesting things about interacting with WASM and scaling an image from a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I also deep dive into averaging pixel values from a 1D array, so if that interests you, that might be useful too!&lt;/p&gt;

&lt;p&gt;Oh and a huge shoutout to Cornelius Diekmann, who did the hard work of &lt;a href="https://github.com/diekmann/wasm-fizzbuzz/tree/main/doom" rel="noopener noreferrer"&gt;porting DOOM to WASM&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Anyway, enough preamble, you are here to play "Doom in CSS*"&lt;/p&gt;

&lt;h2&gt;
  
  
  Doom &lt;em&gt;rendered&lt;/em&gt; in CSS
&lt;/h2&gt;

&lt;p&gt;On mobile, controls are below the game, for PC controls are explained in the pen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You have to click on the game before input is recognised&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(Also if you are on PC clicking the buttons below the game will not work, they are mobile only).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: codepen doesn't seem to like this on some devices, you can play it on my server instead if that happens: &lt;a href="https://grahamthe.dev/demos/doom/" rel="noopener noreferrer"&gt;grahamthe.dev/demos/doom/&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/yyyjyBK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is going on here?
&lt;/h2&gt;

&lt;p&gt;It just looked like low quality Doom right?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BUT&lt;/strong&gt; - if you dared to inspect the output you probably crashed chrome...&lt;/p&gt;

&lt;p&gt;You see, we are doing the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Getting the output from &lt;code&gt;doom.wasm&lt;/code&gt; and putting it on a &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element.&lt;/li&gt;
&lt;li&gt;We are hiding the canvas element and then using JS to gather pixel data&lt;/li&gt;
&lt;li&gt;We take that pixel data, find the average of every 4 pixels to halve the resolution.&lt;/li&gt;
&lt;li&gt;We then convert those new pixels to a CSS linear gradient.&lt;/li&gt;
&lt;li&gt;We apply that linear gradient to the game div using &lt;code&gt;background-image: linear-gradient&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A single linear gradient generated is over 1MB of CSS (actually 2MB), so sadly I can't show you here what it looks like (or on CodePen!) as it is too large!&lt;/p&gt;

&lt;p&gt;And we are creating that 60+ times a second...web browsers and CSS parsing is pretty impressive to be able to handle that!&lt;/p&gt;

&lt;p&gt;Now I am not going to cover everything, but one thing that was interesting was turning the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; data into an array and then getting pixel data for rescaling, so let's cover that:&lt;/p&gt;

&lt;h2&gt;
  
  
  Simple way to get average pixel colour
&lt;/h2&gt;

&lt;p&gt;I had an issue.&lt;/p&gt;

&lt;p&gt;Rendering the game in CSS at 640*400 made Web browsers cry!&lt;/p&gt;

&lt;p&gt;So I needed to downscale the image to 320*200. &lt;/p&gt;

&lt;p&gt;There are loads of ways to do this, but I chose a simple pixel averaging method.&lt;/p&gt;

&lt;p&gt;There are some interesting things in the code, but I think the resizing function is one of the most interesting and may be useful for you at some point. &lt;/p&gt;

&lt;p&gt;It's especially interesting if you have never dealt with an array of pixel data before (as it is a 1D representation of a 2D image, so traversing it is interesting!).&lt;/p&gt;

&lt;p&gt;Here is the code for grabbing the average across pixel data for reference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rgbaToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;averageBlockColour&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;startY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;startX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;startX&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rgbaToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This &lt;code&gt;averageBlockColour&lt;/code&gt; function is useful if you ever want to do simple image resizing (for a thumbnail for example). &lt;/p&gt;

&lt;p&gt;It is limited to clean multiples (2 pixel, 3 pixel block size etc.), but gives a good idea of how to get average colours of a set of pixels.&lt;/p&gt;

&lt;p&gt;The interesting part is &lt;code&gt;const i = (y * width + x) * 4&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This is because we are using a &lt;code&gt;Uint8ClampedArray&lt;/code&gt; where each pixel is represented by 4 bytes, 1 for red, 1 for green, 1 for blue and 1 for the alpha channel.&lt;/p&gt;

&lt;p&gt;We use this as we need to move around an array that is in 1 dimension and grab pixel data in 2 dimensions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pixel data explanation
&lt;/h3&gt;

&lt;p&gt;We need to be able to move around in blocks to average the colours. &lt;/p&gt;

&lt;p&gt;These blocks are X pixels wide and X pixels tall.  &lt;/p&gt;

&lt;p&gt;This means jumping past the rest of an images row data to get the second (or third, or fourth...) rows data as everything is stored in one long line.&lt;/p&gt;

&lt;p&gt;Let me try and explain with a quick "diagram":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Image (3x2 pixels):

Row 0:  RGBA0: (0,0) RGBA1: (1,0) RGBA2: (2,0)
Row 1:  RGBA3: (0,1) RGBA4: (1,1) RGBA5: (2,1)

Array data:   [ RGBA0 | RGBA1 | RGBA2 | RGBA3 | RGBA4 | RGBA5 ]
Image pos:      (0,0)   (1,0)   (2,0)   (0,1)   (1,1)   (2,1)
Array pos:       0-3     4-7     8-11   12-15   16-19   20-23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now you can see how each row of our image is stacked one after the other, you can see why we need to jump ahead.&lt;/p&gt;

&lt;p&gt;So our function takes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;data:&lt;/strong&gt; our array of pixel data, &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startX:&lt;/strong&gt; the left most position of the pixels we want data for (in 2 dimensions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startY:&lt;/strong&gt; the top most position of the pixels we want data for (in 2 dimensions)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;width:&lt;/strong&gt; the total width of our image data (so we can skip rows) &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;blockSize:&lt;/strong&gt; the height and width of the number of pixels we want to average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we wanted to get the average of the first 2 by 2 block of pixels here we would pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;data:&lt;/strong&gt; &lt;code&gt;[ RGBA0 | RGBA1 | RGBA2 | RGBA3 | RGBA4 | RGBA5 ]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startX:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startY:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;width:&lt;/strong&gt; 3 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;blockSize:&lt;/strong&gt; 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within our loops we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//const i = (y * w + x) * 4;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA0&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA3&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA1&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA4&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is pixel data for:&lt;br&gt;
(0,0, 1,0)&lt;br&gt;
(0,1, 1,1)&lt;/p&gt;

&lt;p&gt;Then if we want to get the average of the next 4 pixels we just pass:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;data:&lt;/strong&gt; &lt;code&gt;[ RGBA0 | RGBA1 | RGBA2 | RGBA3 | RGBA4 | RGBA5 ]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startX:&lt;/strong&gt; 1 &amp;lt;-- increment the start by 1&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;startY:&lt;/strong&gt; 0&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;width:&lt;/strong&gt; 3 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;blockSize:&lt;/strong&gt; 2&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Within our loops we &lt;strong&gt;now&lt;/strong&gt; get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;//const i = (y * w + x) * 4;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA1&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA4&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA2&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;start&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt; &lt;span class="nx"&gt;position&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;RGBA5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is pixel data for:&lt;br&gt;
(1,0, 2,0)&lt;br&gt;
(1,1, 2,1)&lt;/p&gt;
&lt;h3&gt;
  
  
  Now we have the raw pixel data
&lt;/h3&gt;

&lt;p&gt;The rest of the process is easier to understand&lt;/p&gt;

&lt;p&gt;We have some RGBA data for a pixel - which might look like &lt;code&gt;[200,57,83,255]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We just add up the values of each part:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;//red&lt;/span&gt;
  &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;//green&lt;/span&gt;
  &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt; &lt;span class="c1"&gt;//blue&lt;/span&gt;
  &lt;span class="c1"&gt;//we deliberately don't grab the "a" (alpha) channel as it will always be 255 - the same as opacity: 1 or non-transparent.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once we have done this for our 4 pixels (our 2 loops for y and x) we will end up with a total R, G and B value for those 4 pixels (y is 0 and 1, x is 0 and 1 in the first instance and y is 0 and 1 and now x is 1 and 2 in the second instance).&lt;/p&gt;

&lt;p&gt;We then just take the average of them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;blockSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// (2 * 2)&lt;/span&gt;
  &lt;span class="c1"&gt;//               avg red,    avg green,  avg blue&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;rgbaToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;g&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;    
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we pass it to a function that turns variables of red, green and blue into a valid hex value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;rgbaToHex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break this down into steps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Start with a &lt;code&gt;#&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Take each of the R, G, B values in order and do the following:

&lt;ul&gt;
&lt;li&gt;Round the value (as we had averages and we need integers)&lt;/li&gt;
&lt;li&gt;Convert the raw value to hexidecimal (0-9A-F to change the string to base16)&lt;/li&gt;
&lt;li&gt;make sure that smaller numbers (0-15) are padded with a 0 so we always get 2 digits for each of the R, G and B values (so we always get a total of 6 characters(&lt;/li&gt;
&lt;li&gt;join the R, G and B hex values together.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;So if we had [200.2,6.9,88.4] as our R, G and B values we would get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A = 10, B = 11, C = 12, D = 13, E = 14, F = 15

- Start -&amp;gt; "#"
- 200.2 -&amp;gt; round (200) -&amp;gt; (12 * 16) + 8 = C8 
- 6.9   -&amp;gt; round (7)   -&amp;gt; (0 * 16)  + 7 =  7
- 88.4  -&amp;gt; round (88)  -&amp;gt; (5 * 16)  + 8 = 58
- Pad   -&amp;gt; C8, 07, 58
- Join  -&amp;gt; #C80758
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And there we have it, R200, G7, B88 is hex code #c80758.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap
&lt;/h2&gt;

&lt;p&gt;There are some really interesting parts around sending commands to WASM applications in there, I would encourage you to explore those yourself, along with the super interesting article by Cornelius Diekmann on &lt;a href="https://github.com/diekmann/wasm-fizzbuzz/tree/main/doom" rel="noopener noreferrer"&gt;porting DOOM to WASM&lt;/a&gt; I mentioned earlier.&lt;/p&gt;




&lt;p&gt;  &lt;/p&gt;

&lt;center&gt;&lt;strong&gt;If you found this article interesting (or distracting...haha) then don't forget to give it a like and share it with others.

It really helps me out!&lt;/strong&gt;&lt;/center&gt;

&lt;h2&gt;
  
  
    
&lt;/h2&gt;

&lt;p&gt;See you all in the next one, and have a great weekend&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>css</category>
      <category>webdev</category>
      <category>webassembly</category>
    </item>
    <item>
      <title>VibeScript - GenZ programming language</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Wed, 16 Apr 2025 08:08:39 +0000</pubDate>
      <link>https://dev.to/grahamthedev/vibescript-genz-programming-language-2l0b</link>
      <guid>https://dev.to/grahamthedev/vibescript-genz-programming-language-2l0b</guid>
      <description>&lt;p&gt;SOLID? DRY? Those days are passed old man.&lt;/p&gt;

&lt;p&gt;If your code isn't a mood, then why bother coding at all?&lt;/p&gt;

&lt;p&gt;Modern software isn't built on frameworks and design patterns, it is built with prompts and vibes. &lt;/p&gt;

&lt;p&gt;To facilitate this shift to a new normal in coding and update out-dated practices we built VibeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  VibeScript
&lt;/h2&gt;

&lt;p&gt;The worst part of coding? Syntax...and actually learning how to code.&lt;/p&gt;

&lt;p&gt;Who has time for that nowadays while trying to get Insta-famous?&lt;/p&gt;

&lt;p&gt;That is no longer a problem with VibeScript.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VibeScript["🤖"]("create a login form that points to our database to authenticate the user")

VibeScript["🤖"]("If the use put the right password in, let them see the dashboard. This page should be a twitter clone with all functionality")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with just those two lines we have a twitter (X) clone.&lt;/p&gt;

&lt;p&gt;Go build!&lt;/p&gt;

&lt;h2&gt;
  
  
  How does it work?
&lt;/h2&gt;

&lt;p&gt;LLMs of course!&lt;/p&gt;

&lt;p&gt;At the moment we use Claude because it can use tools...and that slaps! (we are still working on how to use tools, we need to learn about something called schemas because Claude still uses old-school coding principles and that just sounded boring, so we took a break. For now just use our &lt;code&gt;VibeScript["🛠️"]&lt;/code&gt; command)&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the secret sauce?
&lt;/h2&gt;

&lt;p&gt;Now we aren't going to show you all our code, but here is about 70% of it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;OpenAI&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;openai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;OpenAI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;software&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;completions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;You will return code&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

        &lt;span class="c1"&gt;//the magic part&lt;/span&gt;
        &lt;span class="nf"&gt;eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;software&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VibeScript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Proxy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;prop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🤖&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prop&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛠️&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="c1"&gt;//not sure why but I had to return something, so we will just tell the user thinking until they get bored.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;thinking...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="c1"&gt;// Gemini said we don't need the next line&lt;/span&gt;
            &lt;span class="c1"&gt;//throw new Error(`No AI function defined for: ${String(prop)}`)&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The secret is a super clever piece of engineering on our part, &lt;code&gt;eval&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It let's us instantly run the code the LLM returns. Why other people aren't doing this I have no idea?&lt;/p&gt;

&lt;p&gt;From this you can build anything in just a couple of quick chats.&lt;/p&gt;

&lt;p&gt;We still aren't sure how that proxy thing works exactly, but Grok told us that we need it so we can use emojis to call our AI.&lt;/p&gt;

&lt;p&gt;We needed emojis as otherwise the vibe wasn't right and our code wouldn't run as we wanted it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Documentation
&lt;/h2&gt;

&lt;p&gt;We don't need docs, our system is so intuitive.&lt;/p&gt;

&lt;p&gt;Just call &lt;code&gt;VibeScript["🤖"](&amp;lt;\anything you want to build/&amp;gt;)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;That is it (except for tooling)!&lt;/p&gt;

&lt;h2&gt;
  
  
  Will you implement MCP, A2A?
&lt;/h2&gt;

&lt;p&gt;We understand the need for tools to access your database. But MCP and A2A are just too complicated and ruined the vibe.&lt;/p&gt;

&lt;p&gt;Instead just use &lt;code&gt;VibeScript["🛠️"]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Here is an example (don't forget to pass in your database root user and password as we haven't built an AI database yet so you will have to use old-school):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;VibeScript&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🛠️&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;If a user wants some data from our database, please do whatever the user asks for. You can access the database with user: root, password: ""&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will then connect to your database and do whatever it is databases do to return data.&lt;/p&gt;

&lt;p&gt;Don't worry about security, LLMs are not allowed to do anything bad.&lt;/p&gt;

&lt;h2&gt;
  
  
  What will you &lt;del&gt;build&lt;/del&gt; Vibe?
&lt;/h2&gt;

&lt;p&gt;So what are you going to vibe into existence?&lt;/p&gt;

&lt;p&gt;With no complicated tooling we can't wait to see how many of our customers become the next Zuckerberg or Musk! &lt;/p&gt;

&lt;p&gt;WAGMI!&lt;/p&gt;




&lt;p&gt;This post is satire. I shouldn't have to say it but...vibe coders, you know? &lt;/p&gt;

&lt;p&gt;Please know that if you are using LLMs to learn how to code, explain how code works, discover new things, I am 100% behind it. Just don't over-rely on them and take the time to start learning how things work rather than just trusting LLM code.&lt;/p&gt;

&lt;p&gt;And while the above code is &lt;strong&gt;dangerous&lt;/strong&gt; and nonsense, that proxy pattern is pretty cool, so you might learn something from that...maybe? Not even sure if I coded it right because there was no way I was going to actually run it! hahaha.&lt;/p&gt;

&lt;p&gt;Oh and while &lt;code&gt;VibeScript["🤖"]&lt;/code&gt; will actually run (if my code is right and you swap out the OpenAi call for a &lt;code&gt;console.log&lt;/code&gt; or something), don't use emojis for code...I know, such a boomer.&lt;/p&gt;

&lt;p&gt;**Last warning: &lt;code&gt;eval&lt;/code&gt;...don't use it, just in case you thought "oh that is interesting" and have never heard about it and how dangerous it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  See you all soon.
&lt;/h2&gt;

&lt;p&gt;Have a Skibidi day, no Cap.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>javascript</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Single div ACTUALLY 3D cube📦 in Pure CSS! (They said it was impossible!)</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Wed, 12 Feb 2025 00:19:41 +0000</pubDate>
      <link>https://dev.to/grahamthedev/single-div-actually-3d-cube-in-pure-css-they-said-it-was-impossible-48m5</link>
      <guid>https://dev.to/grahamthedev/single-div-actually-3d-cube-in-pure-css-they-said-it-was-impossible-48m5</guid>
      <description>&lt;p&gt;This seems simple right? &lt;/p&gt;

&lt;p&gt;I mean there are loads of 3D CSS cubes using 2 divs, and loads of cubes using one div that are static, so how much harder can it be to do a truly 3D rotating cube in a single div?&lt;/p&gt;

&lt;p&gt;Spoilers...a LOT harder!&lt;/p&gt;

&lt;p&gt;In fact I asked a few people if they thought it was doable and the answer I got was "impossible", as "CSS just isn't up to it".&lt;/p&gt;

&lt;p&gt;Now the thing you have to know is, I like to do "impossible", especially with CSS (as I suck at CSS and it makes me better at it!).&lt;/p&gt;

&lt;p&gt;I mean, I have built a &lt;a href="https://dev.to/grahamthedev/impossible-css-only-js-syntax-highlighting-with-a-single-element-and-gradients-243j"&gt;CSS only syntax highlighter&lt;/a&gt;, did &lt;a href="https://dev.to/grahamthedev/bubble-sortin-pure-css-no-js-3bb1"&gt;bubble sort in CSS&lt;/a&gt; and even built a &lt;a href="https://dev.to/grahamthedev/pure-css-neural-network-aiits-easier-that-you-think-f02"&gt;neural network in CSS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So surely I can do a CSS only single div 3D cube right?&lt;/p&gt;

&lt;p&gt;Well let me tell you, this challenge nearly broke me. I have tried to do this 3 times and failed.&lt;/p&gt;

&lt;p&gt;But today, I finally did it. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;And I truly believe this may be the single greatest thing I have ever done!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wanna see it?
&lt;/h2&gt;

&lt;p&gt;Of course you do, it is the only reason you are here right, to see if I am lying?&lt;/p&gt;

&lt;p&gt;Well behold the (majesty? chaos?) that is a 3D cube in pure CSS! (be warned, the CSS may break your brain a bit!)&lt;/p&gt;

&lt;p&gt;Go on, check out the HTML, JS and CSS tabs!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There is also an interactive demo where you can set the X and Y rotation with sliders at the end of this article&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/raNNvKd?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Oh and before you say anything about the JS - that purely sets the rotation position in a way that is cross-browser, we could have used CSS Houdini props to do it without CSS if it weren't for certain annoying browsers.&lt;/p&gt;

&lt;p&gt;All it is doing is changing the rotation of the cube using CSS props anyway!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why was it so hard?
&lt;/h2&gt;

&lt;p&gt;The short answer is with a single div it meant I only had 3 sides to play with (the div itself, and the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo elements.&lt;/p&gt;

&lt;p&gt;And in case you didn't know, a cube has 6 sides!&lt;/p&gt;

&lt;p&gt;This meant we had to do what seems like a simple trick, work out which 3 sides are facing the camera and show them.&lt;/p&gt;

&lt;p&gt;However, anyone who has worked in 3D will tell you, working out which side is facing the camera is not simple. &lt;/p&gt;

&lt;p&gt;I mean, it isn't too difficult in code, but in CSS? That is where things get messy!&lt;/p&gt;

&lt;p&gt;Let me step you through a few challenges I came across!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. calculating which side to show
&lt;/h3&gt;

&lt;p&gt;This needed some serious math skills.&lt;/p&gt;

&lt;p&gt;Luckily many others before me have worked this one out and I just grabbed some JS and converted it to CSS.&lt;/p&gt;

&lt;p&gt;Now there is a fair bit to this, but one of the key things is once you do a load of sine, cosine magic you have the X, Y and Z position of a shape in 3D space.&lt;/p&gt;

&lt;h4&gt;
  
  
  Getting the X, Y and Z coordinates of a rotated face of the cube
&lt;/h4&gt;

&lt;p&gt;That is what this bit does:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="c"&gt;/* the "normals" for this side, we repeat these for each side but change  the value to reflect it's position in 3D space (so the back is at z = -1, the left is at x = -1 etc.) */&lt;/span&gt;
  &lt;span class="nt"&gt;--normals-front-x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;--normals-front-y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
  &lt;span class="nt"&gt;--normals-front-z&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

  &lt;span class="c"&gt;/* the calculations to adjust the 3D rotation back to X, Y, Z coordinates */&lt;/span&gt;
 &lt;span class="nt"&gt;--front-y1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--xRot&lt;/span&gt;&lt;span class="o"&gt;)))&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--xRot&lt;/span&gt;&lt;span class="o"&gt;))));&lt;/span&gt;    
  &lt;span class="nt"&gt;--front-z1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--xRot&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--xRot&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt; 
  &lt;span class="nt"&gt;--front-x2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--yRot&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-z1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--yRot&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;    
  &lt;span class="nt"&gt;--front-z2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-front-x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--yRot&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-z1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--yRot&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;      
  &lt;span class="nt"&gt;--front-x3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-x2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--zRot&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-y1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--zRot&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;      
  &lt;span class="nt"&gt;--front-y3&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-x2&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;sin&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--zRot&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-y1&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;cos&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--zRot&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;  
  &lt;span class="nt"&gt;--front-x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-x3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; 
  &lt;span class="nt"&gt;--front-y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-y3&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="nt"&gt;--front-z&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-z2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;  

 &lt;span class="c"&gt;/* repeated for each side of the cube */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  getting the z-axis position or "dot product"
&lt;/h4&gt;

&lt;p&gt;Then once we have these X, Y and Z positions (relative to us in 3D space) we can get their "dot product" to work out the shapes central Z-position relative to the camera direction.&lt;/p&gt;

&lt;p&gt;That is what all this is doing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* getting the magnitude of the camera position. It is worth noting that because I use camera position of X: 0, Y: 0 and Z: 1 this is not really needed as --normalised-cam-z = 1 and --normalised-cam-x and --normalised-cam-y = 0, however I left it in for completeness */&lt;/span&gt;

&lt;span class="nt"&gt;--magnitude-cam&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;sqrt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-x&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-y&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-z&lt;/span&gt;&lt;span class="o"&gt;))));&lt;/span&gt;
    &lt;span class="nt"&gt;--normalised-cam-x&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--magnitude-cam&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nt"&gt;--normalised-cam-y&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--magnitude-cam&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nt"&gt;--normalised-cam-z&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--magnitude-cam&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c"&gt;/* getting the dot-product of the camera normals and the sides X, Y and Z positions and adding them up. */&lt;/span&gt;
&lt;span class="nt"&gt;--dot-prod-front&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-x&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-x&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-y&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-y&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--normals-camera-z&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--front-z&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;span class="c"&gt;/* repeated for each side */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This dot product gives us the distance along the camera's view distance, or the distance along the Z axis (depth).&lt;/p&gt;

&lt;h4&gt;
  
  
  Ok, now we can start working out where sides show up!
&lt;/h4&gt;

&lt;p&gt;That is a lot of setup, but now we have what we need, a way to see which 3 faces are closest to the camera!&lt;/p&gt;

&lt;p&gt;That is what this bit is all about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nt"&gt;--show-front&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-front&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-back&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;--show-back&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-front&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-right&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-left&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
 &lt;span class="nt"&gt;--show-left-dot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   
  &lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-Not-between-90-270&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-left-dot&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-between-90-270&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   &lt;span class="nt"&gt;--show-left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;   
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may not be immediately obvious what is happening here though, so let's break it down.&lt;/p&gt;

&lt;p&gt;We take the z-position of the front and the z-position of the back and compare them.&lt;/p&gt;

&lt;p&gt;If the front is closer to us than the back (a greater z position) then we want to show that, if the back is closer to us then we want to show that.&lt;/p&gt;

&lt;p&gt;So what is all the min max nonsense?&lt;/p&gt;

&lt;p&gt;Well we want to convert decimals into a Boolean.&lt;/p&gt;

&lt;p&gt;So what we do is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiply both numbers by 100 (to ensure that their difference is likely to be greater than 1).&lt;/li&gt;
&lt;li&gt;subtract the "front" from the "back" so we either get a positive number or a negative number.&lt;/li&gt;
&lt;li&gt;Get the maximum of the two numbers and 0 (so if front - back &amp;gt; 0 we would get a positive number as it is larger than 0, but if the front - back &amp;lt; 0 we get 0 as that is now larger than the negative number)&lt;/li&gt;
&lt;li&gt;We then get the minimum of the previous output and 1, this is to cap any positive numbers at 1.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We could do the same with &lt;code&gt;clamp(0, front-back, 1)&lt;/code&gt; but for some reason I always end up writing it this way!&lt;/p&gt;

&lt;h4&gt;
  
  
  We finally have a Boolean!
&lt;/h4&gt;

&lt;p&gt;Phew, that was a lot, but now we have a Boolean value for if the front is closer to the camera than the back and we can then use that in our front/back CSS to move the position of the side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nc"&gt;.cube&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
 &lt;span class="c"&gt;/* other props */&lt;/span&gt;
&lt;span class="err"&gt;translateZ(calc(&lt;/span&gt;
      &lt;span class="err"&gt;(var(--show-front)&lt;/span&gt; &lt;span class="err"&gt;-&lt;/span&gt; &lt;span class="err"&gt;var(--show-back))&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;var(--cube-size)&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="err"&gt;0.5&lt;/span&gt;
    &lt;span class="err"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we either get &lt;code&gt;(1 - 0) * 100px * 0.5&lt;/code&gt; (50px) if the front should show and &lt;code&gt;(0 - 1) * 100px * 0.5&lt;/code&gt; (-50px) if the back should show.&lt;/p&gt;

&lt;p&gt;We then apply this to the z-axis and as if by magic we can move the front and back of the shape depending on which is facing the camera (as we rotate the shape the dot product will change in such a way back &amp;gt; front and so we switch positions so it is facing us)! &lt;/p&gt;

&lt;p&gt;We can do similar tricks for the left / right and the top / bottom, but that one is even simpler as we can move the side by the whole length of the cube:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="c"&gt;/* left / right adjustment to move it one cube length to switch sides*/&lt;/span&gt;
&lt;span class="nt"&gt;translateZ&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--cube-size&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;

&lt;span class="c"&gt;/* top / bottom adjustment */&lt;/span&gt;
&lt;span class="nt"&gt;translateZ&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-top&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--cube-size&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that should be it right?&lt;/p&gt;

&lt;p&gt;Well no, we have a couple of "gotchyas", and that is why this became so difficult!&lt;/p&gt;

&lt;h3&gt;
  
  
  2. the front and back changing position
&lt;/h3&gt;

&lt;p&gt;This one caught me out!&lt;/p&gt;

&lt;p&gt;Because we are using a single div we have a unique problem.&lt;/p&gt;

&lt;p&gt;You see, when we move the front / back face position it also moves the position of the left / right position and top / bottom position by the same amount.&lt;/p&gt;

&lt;p&gt;This completely breaks everything as we only want the front / back to change position relative to the user.&lt;/p&gt;

&lt;p&gt;This is because the &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; pseudo elements are positioned relative to the main &lt;code&gt;.cube&lt;/code&gt; element. The &lt;code&gt;.cube&lt;/code&gt; moves, they move.&lt;/p&gt;

&lt;p&gt;So we have to account for this in our CSS.&lt;/p&gt;

&lt;p&gt;That is why we have two transforms on our &lt;code&gt;::before&lt;/code&gt; and &lt;code&gt;::after&lt;/code&gt; psuedo elements.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;transform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
    &lt;span class="nt"&gt;translate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;-50&lt;/span&gt;&lt;span class="o"&gt;%,&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%)&lt;/span&gt;
    &lt;span class="c"&gt;/* this transform accounts for the front / back changing position and moves this face by the same amount in the opposite direction so that it stays in the same location */&lt;/span&gt;
    &lt;span class="nt"&gt;translateZ&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nt"&gt;-1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-front&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-back&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--cube-size&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="err"&gt;2&lt;/span&gt;
    &lt;span class="o"&gt;))&lt;/span&gt;
    &lt;span class="nt"&gt;rotateY&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="nt"&gt;deg&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="nt"&gt;translateZ&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
      &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--cube-size&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you see it you think "well that is straight forward", but I had to completely change the way I was rotating and positioning the sides of the cube several times to make it this simple and it really broke my brain as I was trying to visualise it in 3D space (badly! Took me like 10 tries!).&lt;/p&gt;

&lt;p&gt;Anyway that fixes that one, we must be done now right?&lt;/p&gt;

&lt;h3&gt;
  
  
  3. rotation changing left / right and up / down
&lt;/h3&gt;

&lt;p&gt;Nearly, this was the last problem!&lt;/p&gt;

&lt;p&gt;Once a shape rotates more than 90 degrees on the X axis (and less than 270 degrees) things break. &lt;/p&gt;

&lt;p&gt;This is because of our switching front and back positions and rotation in 3D space meaning that left becomes right and right becomes left. &lt;/p&gt;

&lt;p&gt;We have the same problem rotating on the y axis at 90 and 270 degrees, our top and bottom positions change (relative to the front / back face).&lt;/p&gt;

&lt;p&gt;This is what this little bit of chaos is about:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;  &lt;span class="nt"&gt;--X-above-90&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--rotX&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;90&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--X-below-90&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-above-90&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--X-above-270&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--rotX&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="err"&gt;270&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--X-below-270&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-above-270&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--X-between-90-270&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-below-90&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-above-270&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
    &lt;span class="nt"&gt;--X-Not-between-90-270&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-between-90-270&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
 &lt;span class="c"&gt;/* repeated for the Y axis rotation */&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It let's us calculate if X rotation is between 90 and 270 so we can invert our left / right positions, and the same for our Y rotation so we can invert the top / bottom positions.&lt;/p&gt;

&lt;p&gt;That is why we have &lt;code&gt;--show-right-dot&lt;/code&gt; and &lt;code&gt;--show-right&lt;/code&gt;, we have to apply those transforms:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt; &lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;Min&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;Max&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-right&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--dot-prod-left&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="err"&gt;100&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="err"&gt;0&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="nt"&gt;--show-left-dot&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right-dot&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-Not-between-90-270&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-left-dot&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--X-between-90-270&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="nt"&gt;--show-left&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nt"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="err"&gt;1&lt;/span&gt; &lt;span class="nt"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;var&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;--show-right&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key bit is &lt;code&gt;--show-right&lt;/code&gt; as we do a branchless if!&lt;/p&gt;

&lt;p&gt;&lt;code&gt;--show-right: calc(var(--show-right-dot) * var(--X-Not-between-90-270) + var(--show-left-dot) * var(--X-between-90-270));&lt;/code&gt; is equivalent to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;right(1) * not between(1) + left(0) * between(0)&lt;/code&gt; if x is &amp;lt; 90 or x &amp;gt; 270 and our dot product says right should be showing (&lt;strong&gt;output is 1&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt; &lt;code&gt;right(1) * not between(0) + left(0) * between(1)&lt;/code&gt; if x is between 90 and 270 and right should be showing (&lt;strong&gt;output is 0&lt;/strong&gt; - we have swapped right to the left).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;right(0) * not between(1) + left(1) * between(0)&lt;/code&gt; if x is &amp;lt; 90 or x &amp;gt; 270 and our dot product says left should be showing (&lt;strong&gt;output is 0 which is left&lt;/strong&gt;).&lt;/li&gt;
&lt;li&gt; &lt;code&gt;right(0) * not between(0) + left(1) * between(1)&lt;/code&gt; if x is between 90 and 270 and right should be showing (&lt;strong&gt;output is 1&lt;/strong&gt; - we now made &lt;code&gt;--show-right&lt;/code&gt; true even though our dot product says left should show).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  That is it!
&lt;/h2&gt;

&lt;p&gt;Well almost, it may still be really hard to follow from that explanation.&lt;/p&gt;

&lt;p&gt;I find the easiest way to understand is to play!&lt;/p&gt;

&lt;p&gt;So here is a demo where you can set the X and Y rotations using sliders and then inspect stuff.&lt;/p&gt;

&lt;p&gt;There is also something interesting at the bottom!&lt;/p&gt;

&lt;p&gt;Those rectangles in red and green have their margins set by CSS properties we use in the application, so you can see the values change as you move the sliders. If you inspect the shapes and check the margin you can see the value for each property.&lt;/p&gt;

&lt;p&gt;show front, show back etc. just move by 300px to the right if they are on (and stay to the left if off).&lt;/p&gt;

&lt;p&gt;Have a play with the sliders and see if it all starts to make sense! 💗&lt;/p&gt;

</description>
      <category>css</category>
      <category>webdev</category>
      <category>showdev</category>
      <category>codepen</category>
    </item>
    <item>
      <title>The Perceptron! [visually see an AI learn in real time! 👀]</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Mon, 03 Feb 2025 17:56:40 +0000</pubDate>
      <link>https://dev.to/grahamthedev/the-perceptron-visually-see-an-ai-learn-2fd9</link>
      <guid>https://dev.to/grahamthedev/the-perceptron-visually-see-an-ai-learn-2fd9</guid>
      <description>&lt;p&gt;I have read loads of articles on how Machine Learning (ML) models work. They all explain the same things in pretty much the same way.&lt;/p&gt;

&lt;p&gt;One thing I haven't found though is the purest form of machine learning.&lt;/p&gt;

&lt;p&gt;Machine learning that is so simple that I can &lt;strong&gt;visually understand&lt;/strong&gt; what is happening. An example where I can see a model go from &lt;em&gt;knowing nothing&lt;/em&gt; to &lt;strong&gt;understanding something&lt;/strong&gt;, and actually follow along exactly how it works!&lt;/p&gt;

&lt;p&gt;I mean, I (kinda) get all the weights, biases etc. I managed to build &lt;a href="https://dev.to/grahamthedev/a-noob-learns-ai-my-first-neural-networkin-vanilla-jswith-no-libraries-1f92"&gt;a neural network (NN) in &lt;strong&gt;vanilla&lt;/strong&gt; JS&lt;/a&gt; (and even built a &lt;a href="https://dev.to/grahamthedev/pure-css-neural-network-aiits-easier-that-you-think-f02"&gt;Neural network in CSS 🤯&lt;/a&gt;!), but I still couldn't wrap my head around what that actually looked like.&lt;/p&gt;

&lt;p&gt;Then I read about "the perceptron" (project PARA), one of the earliest ML experiments from the late 1950s and early 1960s (yes, we have been doing machine learning for that long, in fact since the 1940s!)&lt;/p&gt;

&lt;p&gt;This was what I was looking for as this was a machine learning model that used ANALOGUE systems to work, and so had to be simple(r).&lt;/p&gt;

&lt;p&gt;And from that simplicity I could finally build what I wanted, a simple model where we could &lt;strong&gt;see it learning in real time&lt;/strong&gt; and, more importantly, &lt;strong&gt;understand what is happening&lt;/strong&gt; as it learns!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;If you are inpatient you can skip to the demo, however I would recommend reading about how it works and the rules it uses to learn as that is important to understand what you are seeing!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Shoutout to Welch Labs!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=l-9ALe3U-Fg&amp;amp;ab_channel=WelchLabs" rel="noopener noreferrer"&gt;Welch labs did an amazing explainer video on the perceptron&lt;/a&gt;, which inspired me to make this article. Some of the screenshots are from the physical perceptron machine they built!&lt;/p&gt;

&lt;h2&gt;
  
  
  A Perceptron model you can watch learn!
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshus1zh77sqdeazzmp7x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fshus1zh77sqdeazzmp7x.png" alt="Screenshot of model interface showing 2 input areas for a good and a bad shape, a grid representing dials and weights for each input, a grid showing the resulting " width="800" height="656"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is what I built (or should I say 03-mini built with a lot of prompting and some hand holding - FYI o3 mini produces some horrible code, but it is impressive as a prototyping tool! Still took about 4 hours to put this example together though!).&lt;/p&gt;

&lt;p&gt;Let me explain what you are seeing in that screenshot and then you can have a play with the demo!&lt;/p&gt;

&lt;h3&gt;
  
  
  Input grid
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39su55kg822hs6n77rz9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F39su55kg822hs6n77rz9.png" alt="interface showing 2 input areas for a good and a bad shape" width="800" height="208"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the first two boxes at the top are our inputs.&lt;/p&gt;

&lt;p&gt;They are two 5 by 5 boxes where you can flip "switches". &lt;/p&gt;

&lt;p&gt;A checked box represents a positive voltage, an unchecked checkbox represents a negative voltage.&lt;/p&gt;

&lt;p&gt;For our model to demonstrate learning we need a "good" shape and a "bad" shape.&lt;/p&gt;

&lt;p&gt;These are our code representation of an input grid of switches, like in this example here where you would manually flip switches each time for a "good" or "bad" shape:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr1xgeqsqcd9cxccp49p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmr1xgeqsqcd9cxccp49p.png" alt="A load of switches in either up or down positions wired to a breadboard with lights that are on and off depending on if the switches are up or down" width="402" height="234"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Dials / weights
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92sfsjpbghzdutpe5aoy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F92sfsjpbghzdutpe5aoy.png" alt="a grid representing dials and weights for each input" width="439" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below the input grid we have the dials / weights.&lt;/p&gt;

&lt;p&gt;In the real world analogue version of this they were physical dials that multiplied the input voltage by the amount showed on the dial.&lt;/p&gt;

&lt;p&gt;In our example they are represented by numbers in each box, showing either a positive or negative multiplier.&lt;/p&gt;

&lt;p&gt;You will also notice in the screenshot there is a colour on the cell (for a positive we have green, for a negative we have a red) for easier visual distinction (for those who aren't colourblind).&lt;/p&gt;

&lt;p&gt;And finally you will notice there is a "+" or a "-" in each box, yet again to show if they are making that inputs signal stronger or weaker.&lt;/p&gt;

&lt;p&gt;This is effectively our Neural Network. These are the "weights / biases" you may have heard about in modern neural networks.&lt;/p&gt;

&lt;p&gt;If a grid square is positive / green then it is like turning a dial up, and a negative / red grid square is like turning a dial down.&lt;/p&gt;

&lt;p&gt;They are a representation of a set of dials that look like this: &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F230qrst6yhb1nxf9fxwq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F230qrst6yhb1nxf9fxwq.png" alt="grid of dials" width="334" height="303"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Each perceptron's output voltage visualiser
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6itpu9uev0b0r07ba3rt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6itpu9uev0b0r07ba3rt.png" alt="a grid showing the resulting " width="495" height="560"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is just to help us understand what is going on in the model, it is the output of an input multiplied by a "dial".&lt;/p&gt;

&lt;p&gt;To explain further let's assume the first dial is set to "+18"&lt;/p&gt;

&lt;p&gt;Then let's assume we feed the perceptron a positive voltage (and all inputs are either +10 volts if they are on, or -10 volts if they are off).&lt;/p&gt;

&lt;p&gt;That means that this dial would output +180 volts.&lt;/p&gt;

&lt;p&gt;Now let's assume we changed the switch (the first checkbox) to "off". Now we would get an output of -180v.&lt;/p&gt;

&lt;p&gt;The important part to note is that if this "dial" is negative (say it is set to "-12") then the opposite is true. &lt;/p&gt;

&lt;p&gt;If the switch is "on" then we would get -120v output (+10 volts multiplied by -12) and if the switch is "off" then we would get +120v.&lt;/p&gt;

&lt;p&gt;Then the way the model works is by adding up all the voltages.&lt;/p&gt;

&lt;p&gt;Oh and one last tiny note here, if a dial is "0" then we just output the voltage it received.&lt;/p&gt;
&lt;h3&gt;
  
  
  The voltage output meter
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrkxz0r8fe2mw5hrke0k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbrkxz0r8fe2mw5hrke0k.png" alt="box showing " width="380" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The voltage output meter is our final output. &lt;/p&gt;

&lt;p&gt;It is the result of multiplying the input voltages for each switch (our checkboxes, either +10v or -10v) by each corresponding gain switch and then adding all the results together.&lt;/p&gt;

&lt;p&gt;In our perceptron a positive value is meant to be "good", and a negative value "bad". The amount of the voltage does not matter, just whether it is above 0 or below 0.&lt;/p&gt;

&lt;p&gt;This represents a voltage meter in the real model:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zm654vapeqvygc0461r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4zm654vapeqvygc0461r.png" alt="An analogue voltage meter with a needle that goes from -100v to +100v" width="255" height="279"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How the model learns.
&lt;/h2&gt;

&lt;p&gt;So this perceptron model follows some simple rules.&lt;/p&gt;

&lt;p&gt;It always starts by feeding it a shape, in this instance our "good" shape, with all the dials / weights set at 0.&lt;/p&gt;

&lt;p&gt;Remember that any checked box outputs +10 volts and any unchecked box will output -10 volts.&lt;/p&gt;

&lt;p&gt;So if we draw a shape with 4 checkboxes we will get +40 volts from those, but also get -450 volts from all the others (bear in mind that the actual board is 7 by 7 so 49 total on / off inputs.)&lt;/p&gt;

&lt;p&gt;So as we have no weightings / adjustments we get a total &lt;strong&gt;negative&lt;/strong&gt; output voltage.&lt;/p&gt;

&lt;p&gt;That is the key as then we apply rules on what to do according to that output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the correct output voltage should be positive (a good shape) and we get a negative voltage then we turn all of the cells which are "on" up and all of the other cells down.&lt;/li&gt;
&lt;li&gt;If the correct output voltage should be positive and we get a positive voltage output - &lt;strong&gt;we do nothing&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;If the correct output voltage should be negative (a bad shape) and we get a positive voltage then we turn all of the cells which are "on" down and all of the other cells up.&lt;/li&gt;
&lt;li&gt;If the correct output voltage should be negative and we get a negative voltage output - &lt;strong&gt;we do nothing&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our model we jump in multipliers of 3. So after one training session we get an output like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx7uw3dr3sc03tjrmm9c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvx7uw3dr3sc03tjrmm9c.png" alt="49 boxes with 5 shaded dark green showing +3 in each and the rest shaded red showing -3 in each, the next grid shows all voltages at +30 volts and a total output of 600 volts" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Why the 49 boxes when our input is only 25 boxes?
&lt;/h3&gt;

&lt;p&gt;This is where the magic happens.&lt;/p&gt;

&lt;p&gt;You see if our shape was the same size as our output grid we could argue we are just storing that shape in memory (effectively). The model hasn't learned anything in reality.&lt;/p&gt;

&lt;p&gt;So the next thing we do is to move the shape one square to the right and follow the same procedure. &lt;/p&gt;

&lt;p&gt;This time (due to our previous step) the model will output a different voltage, because now we have some of the off switches being multiplied by a negative number and giving a positive voltage output, and some of our on switches will have moved onto a negative multiplier and become negative.&lt;/p&gt;

&lt;p&gt;So we follow the same rules this time depending on the output voltage.&lt;/p&gt;

&lt;p&gt;Then finally, once we have tried all 9 positions for our "good" shape and adjusted the dials for each one we then feed in the 9 positions for the "bad" shape.&lt;/p&gt;

&lt;p&gt;At the end of this training the model has learned how to identify a shape. It may not always get it right after one learning pass, but it will eventually get it right after many passes (well, nearly always, there are a couple of shape combinations that will never "settle" on a correct dial pattern).&lt;/p&gt;

&lt;p&gt;Ok, so that was a lot of words, not we get to play!&lt;/p&gt;
&lt;h2&gt;
  
  
  The demo
&lt;/h2&gt;

&lt;p&gt;With all of the above in mind, the demo is below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;BEFORE YOU DO&lt;/strong&gt; play with it, read the following instructions. &lt;/p&gt;

&lt;p&gt;Using the model properly is crucial to making it easy to understand what is going on!&lt;/p&gt;
&lt;h3&gt;
  
  
  Instructions
&lt;/h3&gt;

&lt;p&gt;Enter a shape in the "good" box and enter a different shape in the "bad" box by clicking on the checkboxes.&lt;/p&gt;

&lt;p&gt;Once you have your shapes it is time to train!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finnnd11287dcb0sahfwv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finnnd11287dcb0sahfwv.png" alt="5 buttons, Start Training, Cancel Training, Reset Dials, Test All, Self Train and Test" width="800" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Click on the first button "start training".&lt;/p&gt;

&lt;p&gt;You will see the system put the good shape in the "dials and weights" box in the top left most position and instantly adjust the voltages according to the rules.&lt;/p&gt;

&lt;p&gt;Then it will move the shape to the right one position and follow the same rules (keep an eye on the voltage meter as it moves so you can see if the voltage is positive or negative and how that changes the behaviour).&lt;/p&gt;

&lt;p&gt;It will repeat this for 3 column and 3 row combinations so that all possible positions where the shape could be entered are trained upon.&lt;/p&gt;

&lt;p&gt;It will then switch the to "bad" shape and follow the same rules (bearing in mind that this time we &lt;strong&gt;want&lt;/strong&gt; a negative output).&lt;/p&gt;

&lt;p&gt;When training is complete you will see an alert:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzxnn24qhqqur9ervgk3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpzxnn24qhqqur9ervgk3.png" alt="alert box: Training complete! You may now test your shapes manually." width="643" height="139"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  testing the model!
&lt;/h3&gt;

&lt;p&gt;Now you have two options:&lt;br&gt;
&lt;strong&gt;Option 1:&lt;/strong&gt; Click one of the 9 boxes in the "Select Top-Left Position for Testing" box. You will see a modal asking you for which shape to test ("A" is "good" and "B" is "bad"). &lt;/p&gt;

&lt;p&gt;You will get an alert with the perceptron's guess.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vtvqt3rjxkf1ih1czou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6vtvqt3rjxkf1ih1czou.png" alt="alert: " width="643" height="88"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then you will see the shape rendered on the "Select Top-Left Position for Testing" box, along with the resulting voltage outputs for each dial / switch combination.&lt;/p&gt;

&lt;p&gt;Click a few more boxes, choose "A" or "B" to test good and bad shapes and see how often the model gets the guess right or wrong!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the best way to understand how the model is working, so do this a couple of times and check the outputs&lt;/strong&gt;.&lt;/p&gt;



&lt;p&gt;&lt;strong&gt;Option 2:&lt;/strong&gt; Let the system test until it find a failure!&lt;br&gt;
If you just want to see how well the model currently does click "Test All".&lt;/p&gt;

&lt;p&gt;This will run through all of the shape positions for both shapes and will stop with an error if it guesses wrong (outputs a negative voltage for a good shape for example) and let you know which position it failed at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu296csyhrgpmokrwr14.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpu296csyhrgpmokrwr14.png" alt="Alert: Incorrect output for position row: 1, column: 2 for shape A. You should train a second time and try again." width="651" height="101"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or if you are really lucky it may complete all the checks and you will get a success message!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazppvl2kuwfjrnqk3fgd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fazppvl2kuwfjrnqk3fgd.png" alt="alert: fully trained and passed" width="285" height="75"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If it passes then it will correctly identify the shape (Good / A or Bad / B) no matter which valid position you place it in.&lt;/p&gt;
&lt;h3&gt;
  
  
  It failed
&lt;/h3&gt;

&lt;p&gt;Not surprising, you did one training pass!&lt;/p&gt;

&lt;p&gt;Now you could click "start training" again, but that is &lt;strong&gt;deliberately slow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So to speed things up you can click "Self train and test".&lt;/p&gt;

&lt;p&gt;This runs through all training positions at &lt;strong&gt;10x speed&lt;/strong&gt; and then runs tests until it fails on a shape, repeating this process 50 times or until it passes every test!&lt;/p&gt;

&lt;p&gt;Once you understand the principles and have played enough with the slow version, click this button and watch how the voltages for each tile change and how it gradually gets further through the tests until it (hopefully) finally passes!&lt;/p&gt;

&lt;p&gt;If it doesn't pass try a different shape or press "self train and test" again to run another 50 cycles!&lt;/p&gt;

&lt;p&gt;Then once it passes everything try clicking on a few on the "Select Top-Left Position for Testing" squares and trying an "A" or "B" shape.&lt;/p&gt;

&lt;p&gt;Pay special attention to the voltages in each box and the dial settings. Notice where the strongest and weakest cells are etc. &lt;/p&gt;

&lt;p&gt;I find it super interesting how "easy" it is to understand what is going on!&lt;/p&gt;
&lt;h2&gt;
  
  
  Codepen Demo
&lt;/h2&gt;



&lt;center&gt;
&lt;strong&gt;Set the view to 0.5x&lt;/strong&gt;&lt;br&gt; using the button at the bottom of the CodePen so it is easier to use&lt;/center&gt;



&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/XJrQrGY?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h3&gt;
  
  
  Trying new shapes
&lt;/h3&gt;

&lt;p&gt;If you want to try a new shape or just want to watch it learn from scratch again, just press "reset dials" and all of the dials will return to 0.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap
&lt;/h2&gt;

&lt;p&gt;I know that was a lot of words, but I am hoping you find it useful for understanding how a model learns, in it's simplest form.&lt;/p&gt;

&lt;p&gt;I found this a lot easier to understand than matrix multiplication and graphs and charts and although it isn't how modern LLMS / ML models work, it is a great way of getting the principles. &lt;/p&gt;

&lt;p&gt;Let me know in the comments if you found it useful / helped you understand a little better!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>machinelearning</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The &lt;sarcasm&gt; 🙄 component - now Brits can finally be understood on the web!</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sat, 25 Jan 2025 11:43:45 +0000</pubDate>
      <link>https://dev.to/grahamthedev/the-component-now-brits-can-finally-be-understood-on-the-web-33m1</link>
      <guid>https://dev.to/grahamthedev/the-component-now-brits-can-finally-be-understood-on-the-web-33m1</guid>
      <description>&lt;p&gt;I don't think 'Muricans understand our struggle as Brits.&lt;/p&gt;

&lt;p&gt;We make a killer sarcastic comment like "I love listicles, they are super useful" and they think we are being serious!&lt;/p&gt;

&lt;p&gt;Now most of my European friends will recognise the sarcasm, but as a Brit I do find that my cousins from across the pond struggle with identifying sarcasm, especially in written text. &lt;/p&gt;

&lt;p&gt;And the scariest part of that is that they might actually think I like listicles 🤢.&lt;/p&gt;

&lt;h2&gt;
  
  
  They should make me Prime Minister
&lt;/h2&gt;

&lt;p&gt;Do not fear though, dear reader, I have solved the problem. I will be forever heralded as the saviour of USA-UK relations! &lt;/p&gt;

&lt;p&gt;My creation is &lt;strong&gt;that&lt;/strong&gt; ground breaking that I truly believe I would make a better PM than Mr Starmer (although that isn't a high bar to be fair! 😱🤣)! &lt;/p&gt;

&lt;p&gt;At the very least I think I am in the running for a Nobel Peace Prize?&lt;/p&gt;

&lt;p&gt;Prepare yourself to marvel at the majesty and beauty of the &lt;code&gt;&amp;lt;sarcasm&amp;gt;&lt;/code&gt; component!&lt;/p&gt;

&lt;h3&gt;
  
  
  The sarcasm component
&lt;/h3&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/xbKQjJy?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Did you cry?
&lt;/h2&gt;

&lt;p&gt;I know I did when I saw it take it's first breath. The beauty of helping 70 million Brits finally express themselves was overwhelming.&lt;/p&gt;

&lt;p&gt;Air quotes in HTML should have been a thing since day 1 of the internet! &lt;/p&gt;

&lt;p&gt;It is bad enough I have to write &lt;code&gt;color&lt;/code&gt; every day and spell things incorrectly, but I could have easily forgiven that if my needs and those of my fellow Countrymen had been considered elsewhere.&lt;/p&gt;

&lt;p&gt;I have finally solved that injustice!&lt;/p&gt;

&lt;h2&gt;
  
  
  Go forth and multiply
&lt;/h2&gt;

&lt;p&gt;To all my fellow Brits (and other sarcastic so and so's!) - go forth and spread the good word.&lt;/p&gt;

&lt;p&gt;Use the component everywhere you can and rejoice in your ability to fully express yourself.&lt;/p&gt;

&lt;p&gt;And you can rest assured you can use it without fear as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It hides the SVGs from screen readers and adds a visually hidden "(sarcasm)" to the end so it is pretty screen reader friendly (open to suggestions on how to improve it though!).&lt;/li&gt;
&lt;li&gt;If you remove the &lt;code&gt;rotate(1.5deg)&lt;/code&gt; it would be even more accessible FYI.&lt;/li&gt;
&lt;li&gt;It will adapt to font sizes and colours so will look &lt;del&gt;ugly&lt;/del&gt; &lt;strong&gt;great&lt;/strong&gt; in anything you do. &lt;/li&gt;
&lt;li&gt;Comes with my standard 3 year warranty*&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;* this warranty, as with most warranties, is completely useless as we provide no guarantees either implied or explicit and is included purely for marketing purposes&lt;/small&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap.
&lt;/h2&gt;

&lt;p&gt;A silly article (as to be expected from me) just to get back into the swing of writing and to say hi.&lt;/p&gt;

&lt;p&gt;I hope 2025 is treating you well so far and I look forward to writing some more "useful and well thought out" articles and sharing with you all soon. (see why we need the sarcasm component?)&lt;/p&gt;

</description>
      <category>programming</category>
      <category>javascript</category>
      <category>css</category>
      <category>webdev</category>
    </item>
    <item>
      <title>True Alphanumeric / natural sorting in MySQL - why is the answer always recursion?</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sun, 17 Nov 2024 10:57:26 +0000</pubDate>
      <link>https://dev.to/grahamthedev/true-alphanumeric-natural-sorting-in-mysql-why-is-the-answer-always-recursion-2b4a</link>
      <guid>https://dev.to/grahamthedev/true-alphanumeric-natural-sorting-in-mysql-why-is-the-answer-always-recursion-2b4a</guid>
      <description>&lt;p&gt;Yesterday I attempted to solve alphanumeric sorting in MySQL and failed. (&lt;a href="https://dev.to/grahamthedev/alphanumeric-natural-sort-in-mysql-30-years-and-we-still-cant-do-this-402c"&gt;read that article here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;I did get close though and had the right concept, just wrong execution.&lt;/p&gt;

&lt;p&gt;Today, I woke up and had an epiphany...recursion.&lt;/p&gt;

&lt;p&gt;The problem with recursion is that you have to understand recursion to be able to do recursion...and I don't understand recursion enough to do recursion in MySQL.&lt;/p&gt;

&lt;p&gt;However with a bit of Chat Gippity back and forth (by which I mean getting it to write what I asked for, getting back about 25% of what I asked for, fixing it and feeding it into a new chat so it doesn't keep repeating itself for about 2 hours) I got a working answer!&lt;/p&gt;

&lt;h2&gt;
  
  
  To the point
&lt;/h2&gt;

&lt;p&gt;May I present to you my swan song, my masterpiece, the answer to life itself (ok fine, the only working solution to true alphanumeric sorting in MySQL I have seen).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;process_numbers&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; 
        &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;data_value&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;''&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;CHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;processed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;

    &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

    &lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="k"&gt;CASE&lt;/span&gt; 
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;LOCATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                &lt;span class="k"&gt;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;LOCATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt; 
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;processed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;CASE&lt;/span&gt; 
                &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;LOCATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                    &lt;span class="k"&gt;LEFT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;LOCATE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;
            &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="k"&gt;CASE&lt;/span&gt;
                &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                    &lt;span class="k"&gt;RIGHT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0000000000'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
            &lt;span class="k"&gt;END&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;processed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

        &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;process_numbers&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
          &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;iteration&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;processed_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;process_numbers&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;remaining_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;""&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And if you want to try it out (and try to break it) you can play with this &lt;a href="https://www.db-fiddle.com/f/rv2gf7d2UhfAfRFWZ2Cp6m/0" rel="noopener noreferrer"&gt;DB fiddle&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  So how does this work?
&lt;/h2&gt;

&lt;p&gt;It does what I originally wanted to do, take every group of numbers and pads them out to 10 digits total.&lt;/p&gt;

&lt;p&gt;So obviously if you feed this a couple of strings with 11 consecutive numeric digits it won't work without adjustment, but other than that it works fine!&lt;/p&gt;

&lt;p&gt;You see, MySQL can sort numbers correctly, even in lexicographical ordering mode, but it has one flaw.&lt;/p&gt;

&lt;p&gt;It counts "11" as smaller than "2" because of the fact it does sorting one character at a time (effectively). So "2" is bigger than "1" so it comes first. Then it checks the next character, by which point the sorting is incorrect (for numbers at least). &lt;/p&gt;

&lt;p&gt;To understand this better, imagine if 1 was actually the letter "b" and 2 was the letter "c". &lt;/p&gt;

&lt;p&gt;That is how MySQL "sees" numbers, they are just another character.&lt;/p&gt;

&lt;p&gt;So if I had "bb" and "c" you would &lt;em&gt;expect&lt;/em&gt; "bb" to come before "c". Now swap the numbers back in and you can see why "11" comes before "2".&lt;/p&gt;

&lt;h3&gt;
  
  
  So this is a hack?
&lt;/h3&gt;

&lt;p&gt;Yes, we remove the issue by moving the numbers "back" through padding.&lt;/p&gt;

&lt;p&gt;Going back to our example, if we padded "11" and "2" to 3 in length and use "a" as 0, this is what happens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;011 = abb
002 = aac 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;notice how now the sorting would go:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;character 1: is "a" bigger than "a" - no, they are the same.&lt;/li&gt;
&lt;li&gt;character 2: is "b" bigger than "a" - yes, put the "a" before the "b"&lt;/li&gt;
&lt;li&gt;character 3: is now irrelevant and we have already found an occurence earlier that was different and larger.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So by that logic we now have:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;002 = aac (the second "a" comes before the second "b" in the next row)
011 = abb
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And that is how it works!&lt;/p&gt;

&lt;h2&gt;
  
  
  Are you going to explain the recursion thing?
&lt;/h2&gt;

&lt;p&gt;Kind of. I have been "round the houses" with this one and my knowledge is surface level, but I will give it a go.&lt;/p&gt;

&lt;p&gt;The problem came with how RegEx works in MySQL. &lt;code&gt;REGEX_SUBSTR&lt;/code&gt; will only ever find one match and then keep returning that for every other match it finds. So that was why my solution from yesterday was not working correctly.&lt;/p&gt;

&lt;p&gt;But &lt;code&gt;REGEX_REPLACE&lt;/code&gt; has its own issues where it doesn't seem to correctly expose the string length of a match (so we can't &lt;code&gt;LPAD&lt;/code&gt; with it correctly)&lt;/p&gt;

&lt;p&gt;That is why I thought about recursion as the answer. &lt;/p&gt;

&lt;p&gt;I can use &lt;code&gt;REGEX_SUBSTR&lt;/code&gt; to get the correct padding behaviour, and as each loop through the RegEx is essentially a new function call it doesn't "remember" the previous match, so it solves that problem.&lt;/p&gt;

&lt;p&gt;And if you want a brief step through of the logic it is actually not as scary as it looks!&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;We loop over a given string, looking for any numbers (the entire number, not just a single character).&lt;/li&gt;
&lt;li&gt;We then remove that from &lt;code&gt;remaining_data&lt;/code&gt; so we don't match it again.&lt;/li&gt;
&lt;li&gt;We take that number we just matched and pad it out to be 10 digits long total.&lt;/li&gt;
&lt;li&gt;We then search for the next numeric part in the string and repeat the process, building up &lt;code&gt;processed_data&lt;/code&gt; as our final string.&lt;/li&gt;
&lt;li&gt;finally once we have no more numbers to process, we add any remaining letters to the end of &lt;code&gt;processed_data&lt;/code&gt; to complete the transformation and we return this as &lt;code&gt;sort_key&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then we can use this &lt;code&gt;sort_key&lt;/code&gt; in our query to order the column correctly.&lt;/p&gt;

&lt;p&gt;And the &lt;code&gt;iteration&lt;/code&gt; part is purely a safeguarding tool, to make sure it doesn't completely run the MySQL server out of memory or crash the query if a sufficiently complex string is processed (or there is an error in the logic that means it would recurse forever).&lt;/p&gt;

&lt;h2&gt;
  
  
  That's a wrap!
&lt;/h2&gt;

&lt;p&gt;Isn't it funny how sleeping on things brings new perspective?&lt;/p&gt;

&lt;p&gt;Perhaps I should try out &lt;a href="https://en.wikipedia.org/wiki/Polyphasic_sleep" rel="noopener noreferrer"&gt;polyphasic sleep&lt;/a&gt; so I can sleep on problems 2-3 times more often each day and become a 10x developer? haha.&lt;/p&gt;

&lt;p&gt;Anyway there you have it, a reasonably robust &lt;strong&gt;true&lt;/strong&gt; alphanumeric sort.&lt;/p&gt;

&lt;p&gt;Oh and in reality you should probably convert the &lt;code&gt;sort_key&lt;/code&gt; to a stored column on your database using &lt;code&gt;GENERATE&lt;/code&gt; or a stored procedure. Sadly the playground I use doesn't seem to support that and it is a Sunday so I will leave that to you, dear viewer!&lt;/p&gt;

&lt;p&gt;Have a great rest of your weekend and a great week.&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>sql</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>I hope these quick posts don't take off, that feels like a lot of clutter would be created! Also I love that as I type the input box gets 1 px taller per character, that is an amusing bug lol.</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sat, 16 Nov 2024 16:48:38 +0000</pubDate>
      <link>https://dev.to/grahamthedev/i-hope-these-quick-posts-dont-take-off-that-feels-like-a-lot-of-clutter-would-be-created-also-i-226p</link>
      <guid>https://dev.to/grahamthedev/i-hope-these-quick-posts-dont-take-off-that-feels-like-a-lot-of-clutter-would-be-created-also-i-226p</guid>
      <description></description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Alphanumeric / Natural sort in MySQL - 30 years and we still can't do this? 😤</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sat, 16 Nov 2024 15:23:00 +0000</pubDate>
      <link>https://dev.to/grahamthedev/alphanumeric-natural-sort-in-mysql-30-years-and-we-still-cant-do-this-402c</link>
      <guid>https://dev.to/grahamthedev/alphanumeric-natural-sort-in-mysql-30-years-and-we-still-cant-do-this-402c</guid>
      <description>&lt;p&gt;Our QA team are absolute monsters, they're monsters I tell you!&lt;/p&gt;

&lt;p&gt;No matter how much I try to bribe them with drinks at our next company gathering to stop finding bugs in my code, they keep coming up with unreasonable requests and "expected behaviours"...ewwwww, gross!&lt;/p&gt;

&lt;p&gt;Don't believe me? &lt;/p&gt;

&lt;p&gt;Recently they asked for a &lt;strong&gt;list to be ordered alphanumerically.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Can you believe that? Monsters I tell you!&lt;/p&gt;

&lt;p&gt;I mean, it isn't like an end user would expect a list to be sorted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test 1
test 2
test 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...why can't it just be like MySQL thinks it should be sorted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;test 1
test 12
test 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Makes perfect sense right? &lt;strong&gt;Right?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Anyway lets not get too deep into who is right and who is wrong here (they're right FYI), I thought I better try and fix it.&lt;/p&gt;

&lt;p&gt;I mean...how hard can it be? (foreshadowing)&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;I know you might be busy, so the short answer is &lt;strong&gt;sort it in JS / PHP / whatever&lt;/strong&gt;. Don't try and do it with MySQL.&lt;/p&gt;

&lt;p&gt;Like seriously, just return your data set and then use &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator" rel="noopener noreferrer"&gt;&lt;code&gt;Intl.Collator&lt;/code&gt;&lt;/a&gt; if you are using Node, or &lt;a href="https://www.php.net/manual/en/function.natsort.php" rel="noopener noreferrer"&gt;&lt;code&gt;natsort()&lt;/code&gt;&lt;/a&gt; in PHP.&lt;/p&gt;

&lt;p&gt;And you know what, that would have been fine. It would have solved the problem and I could have moved on with my day. QA would have been happy, list would be sorted, all good!&lt;/p&gt;

&lt;p&gt;However, if you are like me then when your CTO reviews your PR and says:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsx7039bgunc8s4ydnv70.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsx7039bgunc8s4ydnv70.png" alt="" width="800" height="55"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I take that to heart! &lt;/p&gt;

&lt;p&gt;I mean, he is so right! &lt;/p&gt;

&lt;p&gt;Why should we have to sort things in code, MySQL should be able to do that for us!&lt;/p&gt;

&lt;p&gt;What ensued was over 4 hours of fiddling and swearing to try and get MySQL to bend to my will.&lt;/p&gt;

&lt;p&gt;Did I manage it? No...but kinda.&lt;/p&gt;

&lt;p&gt;What I ended up with &lt;em&gt;works well enough&lt;/em&gt; for user input in our specific scenario, and is much better than other solutions I found, but has it's limitations.&lt;/p&gt;

&lt;p&gt;But we are getting ahead of ourselves, I want to show you what is it like trying to solve this in MySQL!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data for testing
&lt;/h2&gt;

&lt;p&gt;First thing is first, let's start with the test data I used, just so you can see if I missed anything:&lt;/p&gt;

&lt;h3&gt;
  
  
  The Test Data
&lt;/h3&gt;

&lt;p&gt;
  Test Data
  &lt;br&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;INSERT INTO test_data (data_value) VALUES
('2 test'),
('12 test'),
('1 test'),
('20 test'),
('10 test'),
('1test'),
('2test'),
('test 1'),
('my data 2020-01-01'),
('my data 2020-02-01'),
('01-02-24 data'),
('12 test'),
('4a test'),
('my 2020-01-01 data 2020-01-01'),
('my 2020-02-01 data 2020-01-01'),
('my 2020-02-01 data 202-01-01'),
('my 2020-02-01 data 20-01-01'),
('my 2020-02-01 data 1-01-01'),
('my 2020-02-01 data 2-01-01'),
('my 2020-02-01 data 12-01-01'),
('my 2020-02-01 data 01-01-01'),
('my 2020-01-01 data 2020-02-01'),
('my 2020-01-01 data 2021-01-01'),
('my 2020-01-01 data 2120-01-01'),
('my 2120-01-01 data 2020-01-01'),
('4b test'),
('my test'),
('my 12 magic test'),
('my magic 12 test'),
('cheese and test 12'),
('42-a-1'),
('40-a-1'),
('40a'),
('FoClSy4727'),
('Pthw068bf'),
('6bfS'),
('HOFAp_Yx7920'),
('25hWTX'),
('dnjLlW1'),
('RHrIt72402eaLr'),
('cIhb42WFNQ'),
('9244uVCpGa'),
('yDKrkCp7960'),
('GeGIrPM-H86'),
('wrOae537LGCT'),
('WffSPaBA318'),
('kQ33596c'),
('3uEKHmHePf'),
('796h-eYWy'),
('833HufIZAS'),
('utjtV03Xns'),
('dlCSh87811'),
('13IUkOxEVl'),
('VHCok55901XYVk'),
('2RnSVwq'),
('AwtwQdn09'),
('gvSV6z'),
('uxWLO039hb'),
('vTg946');
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;/p&gt;

&lt;p&gt;Some nonsense with numbers in various places throughout the strings, seemed reasonable enough to me.&lt;/p&gt;

&lt;p&gt;It is probably not a perfect data set, but good enough for what I was after!&lt;/p&gt;

&lt;p&gt;So once I had some data I did what every developer does, asked the Google God for an answer:&lt;/p&gt;

&lt;h2&gt;
  
  
  Googling for natural sort in MySQL
&lt;/h2&gt;

&lt;p&gt;Seems like the logical first step right?&lt;/p&gt;

&lt;p&gt;MySQL has been around for 30 years, and alphanumeric sorting is something you would think people need to do often, so it must have some answers out there already right?&lt;/p&gt;

&lt;p&gt;Short answer...it was exhausting. Every single article, Stack Overflow post etc. spitting out the same nonsense that doesn't work or is hyper specific to a particular data format.&lt;/p&gt;

&lt;p&gt;I wanted something generic, so I could just sort alphanumerically everywhere!&lt;/p&gt;

&lt;p&gt;So let me walk you through the pain I endured so you don't have to, and so you can decide if my answer is any better...or if I have just added to the pain!&lt;/p&gt;

&lt;p&gt;Let's try some of the generic sort methods people suggested:&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 1: the "+0" method
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;test_data&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
    &lt;span class="nv"&gt;`data_value`&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The idea being that it tries to force MySQL to do numeric ordering over lexicographical ordering.&lt;/p&gt;

&lt;p&gt;This would work fine if we had numbers &lt;strong&gt;or&lt;/strong&gt; words in each row (the numbers would indeed sort correctly and then the words).&lt;/p&gt;

&lt;p&gt;However because we have mixed data, it just spat out a load of nonsense:&lt;/p&gt;

&lt;h4&gt;
  
  
  Test 1 results
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;my 2120-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;my 12 magic test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;my magic 12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;
  Full Results for test 1
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;my 2120-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;my 12 magic test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;my magic 12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Pthw068bf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;HOFAp_Yx7920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;dnjLlW1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;RHrIt72402eaLr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;cIhb42WFNQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;yDKrkCp7960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;cheese and test 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;wrOae537LGCT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;WffSPaBA318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;kQ33596c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;utjtV03Xns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;dlCSh87811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;VHCok55901XYVk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;AwtwQdn09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;gvSV6z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;uxWLO039hb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;vTg946&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 20-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;test 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;my data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;my data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 202-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 1-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 01-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2021-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 12-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2120-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;01-02-24 data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;2RnSVwq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;3uEKHmHePf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;4a test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;4b test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;6bfS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;13IUkOxEVl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;25hWTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;40a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;40-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;42-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;796h-eYWy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;833HufIZAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;9244uVCpGa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;/p&gt;

&lt;p&gt;Ok so that didn't work (and I didn't really expect it to).&lt;/p&gt;

&lt;p&gt;Let's try something else:&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 2: The "length" hack
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="n"&gt;alphanumeric&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;sorting_test&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
    &lt;span class="k"&gt;LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alphanumeric&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;alphanumeric&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is another interesting approach. &lt;/p&gt;

&lt;p&gt;obviously "12" is longer than "1" so it would work there.&lt;/p&gt;

&lt;p&gt;It also works if you have a standard format for data, like the "test 1, test 12, test 2" I mentioned at the beginning.&lt;/p&gt;

&lt;p&gt;However, for our data it returns more nonsense:&lt;/p&gt;

&lt;h4&gt;
  
  
  Test 2 results
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;40a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;6bfS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;25hWTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;40-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;42-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;gvSV6z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;test 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;vTg946&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10 test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;
  Full Results for test 2
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;40a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;6bfS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;25hWTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;40-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;42-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;gvSV6z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;test 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;vTg946&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;2RnSVwq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;4a test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;4b test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;dnjLlW1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;kQ33596c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;796h-eYWy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;AwtwQdn09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Pthw068bf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;13IUkOxEVl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;3uEKHmHePf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;833HufIZAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;9244uVCpGa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;cIhb42WFNQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;dlCSh87811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;utjtV03Xns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;uxWLO039hb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;WffSPaBA318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;yDKrkCp7960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;HOFAp_Yx7920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;wrOae537LGCT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;01-02-24 data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;RHrIt72402eaLr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;VHCok55901XYVk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;my 12 magic test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;my magic 12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;cheese and test 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;my data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;my data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 1-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 01-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 12-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 20-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 202-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2021-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2120-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;my 2120-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;/p&gt;

&lt;p&gt;And that also makes sense, all it is doing is ordering on length first so:&lt;/p&gt;

&lt;p&gt;"a1" would come before "1potato" by length. &lt;/p&gt;

&lt;h3&gt;
  
  
  Test 3: The Hardouken!
&lt;/h3&gt;

&lt;p&gt;Just take a look at this beauty:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
    &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; 
    &lt;span class="n"&gt;test_data&lt;/span&gt; 
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
  &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                  &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                      &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
                      &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ā'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                    &lt;span class="s1"&gt;'1'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ā'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                  &lt;span class="s1"&gt;'2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ă'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
                &lt;span class="s1"&gt;'3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ă'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
              &lt;span class="s1"&gt;'4'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ą'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
            &lt;span class="s1"&gt;'5'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ą'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
          &lt;span class="s1"&gt;'6'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ć'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
        &lt;span class="s1"&gt;'7'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ć'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
      &lt;span class="s1"&gt;'8'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ĉ'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; 
    &lt;span class="s1"&gt;'9'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'ĉ'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
  &lt;span class="k"&gt;COLLATE&lt;/span&gt; &lt;span class="n"&gt;utf8_bin&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The meme is real!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2s18x7bkr7q8btvy1ffe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2s18x7bkr7q8btvy1ffe.png" alt="lots of nested if statements that indent further and further to form a triangle shape. Ryu from Stree fighter is firing a Hardouken in the middle" width="479" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But despite that, this one pretty much works (albeit slightly differently to how I would have done it).&lt;/p&gt;

&lt;p&gt;This one is smart in that it replaces each number with a character that will be sorted based on their &lt;strong&gt;binary representation&lt;/strong&gt; in Unicode. &lt;/p&gt;

&lt;h4&gt;
  
  
  Test 3 results
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;AwtwQdn09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;HOFAp_Yx7920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Pthw068bf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;RHrIt72402eaLr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;VHCok55901XYVk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;WffSPaBA318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;cIhb42WFNQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;cheese and test 12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;
  Full Results for test 3
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;td&gt;AwtwQdn09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;37&lt;/td&gt;
&lt;td&gt;HOFAp_Yx7920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;Pthw068bf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;RHrIt72402eaLr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;VHCok55901XYVk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;WffSPaBA318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;cIhb42WFNQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;cheese and test 12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52&lt;/td&gt;
&lt;td&gt;dlCSh87811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;39&lt;/td&gt;
&lt;td&gt;dnjLlW1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;gvSV6z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;kQ33596c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;my data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;my data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;my magic 12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;my 12 magic test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2020-02-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2021-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;my 2020-01-01 data 2120-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 01-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 1-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 12-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 20-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 202-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;my 2020-02-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;my 2120-01-01 data 2020-01-01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;test 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;utjtV03Xns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;uxWLO039hb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;vTg946&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;wrOae537LGCT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;yDKrkCp7960&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;01-02-24 data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;10 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;13IUkOxEVl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;2RnSVwq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;20 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;25hWTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;48&lt;/td&gt;
&lt;td&gt;3uEKHmHePf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;4a test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;4b test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;td&gt;40-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;40a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;td&gt;42-a-1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;6bfS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;796h-eYWy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;50&lt;/td&gt;
&lt;td&gt;833HufIZAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;9244uVCpGa&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;/p&gt;

&lt;p&gt;You know what, I would use this if it wasn't for one thing. It would mean that we could no longer accept 'Ā' etc as input. &lt;/p&gt;

&lt;p&gt;Unfortunately our software will be used internationally, so we can't make that call. &lt;/p&gt;

&lt;p&gt;Additionally if you do decide to use this, it is worth noting your database column must use &lt;code&gt;COLLATE 'utf8_bin'&lt;/code&gt; on it as well or you may get errors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat GPT Time
&lt;/h2&gt;

&lt;p&gt;When googling fails, chat GPT can always help right?&lt;/p&gt;

&lt;p&gt;I am not going to show all the results, but let's just say the following are just a few of the "did not work" ones it produced!&lt;/p&gt;

&lt;h3&gt;
  
  
  Test 4: GPT fever dream
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;
    &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="c1"&gt;-- Replace numbers with 'a' and letters with 'b' dynamically&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;GROUP_CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;CASE&lt;/span&gt;
                &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="n"&gt;REGEXP&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'a'&lt;/span&gt; &lt;span class="c1"&gt;-- Replace digits with 'a'&lt;/span&gt;
                &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="n"&gt;REGEXP&lt;/span&gt; &lt;span class="s1"&gt;'[a-zA-Z]'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'b'&lt;/span&gt; &lt;span class="c1"&gt;-- Replace letters with 'b'&lt;/span&gt;
                &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="c1"&gt;-- Leave other characters as-is&lt;/span&gt;
            &lt;span class="k"&gt;END&lt;/span&gt;
            &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;SEPARATOR&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;SUBSTRING&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;
            &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;
                &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;
                &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt;
                    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;
                    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt; &lt;span class="c1"&gt;-- Handle up to 255 characters in a string&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;seq_table&lt;/span&gt;
                &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;seq&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="k"&gt;CHAR_LENGTH&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;char_table&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;char_map&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;sort_key&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It didn't run, and I couldn't quite work out why, but isn't it impressive looking!&lt;/p&gt;

&lt;p&gt;What it seemed to be trying to do was replace all numbers with "a" and all letters with "b" to form strings like "aabaaa, aabba, abbaa" for comparison.&lt;/p&gt;

&lt;p&gt;I would have probably spent more time trying to fix this if it wasn't flawed.&lt;/p&gt;

&lt;p&gt;"11ab1" would produce "aabba"&lt;br&gt;
"111ab1" would produce "aaaba".&lt;/p&gt;

&lt;p&gt;So with the way MySQL sorts, 111ab1 would have been above 11ab1 anyway as "aaa" comes before "aab".&lt;/p&gt;

&lt;p&gt;Nice idea, but sadly not quite it.&lt;/p&gt;
&lt;h3&gt;
  
  
  Test 5: GPT nearly nailed it!
&lt;/h3&gt;

&lt;p&gt;This one surprised me. &lt;/p&gt;

&lt;p&gt;I had seen it elsewhere already (as we know GPT is great at just spitting things out that it has already seen and isn't actually creative), but the explanation it gave made more sense!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="o"&gt;*&lt;/span&gt; 
&lt;span class="k"&gt;FROM&lt;/span&gt; 
  &lt;span class="n"&gt;test_data&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
  &lt;span class="k"&gt;CAST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_value&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;UNSIGNED&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data_value&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahhhh nearly. I really thought this was the answer. &lt;/p&gt;

&lt;p&gt;But sadly it fails on "Test 1, Test 12, Test 2". It is good for sorting if the number comes first though!&lt;/p&gt;

&lt;h2&gt;
  
  
  My Turn
&lt;/h2&gt;

&lt;p&gt;I still couldn't get what I wanted after several attempts. But I was now much closer to an answer after seeing a few techniques.&lt;/p&gt;

&lt;p&gt;After a bit of noodle scratching and crying I thought I had solved it!&lt;/p&gt;

&lt;h3&gt;
  
  
  It works...kinda!
&lt;/h3&gt;

&lt;p&gt;Here is what I came up with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;test_data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="n"&gt;AUTO_INCREMENT&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;data_value&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
    &lt;span class="n"&gt;transformed_column&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;CASE&lt;/span&gt;
            &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                &lt;span class="n"&gt;REGEXP_REPLACE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;LPAD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;REGEXP_SUBSTR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'[0-9]+'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="n"&gt;data_value&lt;/span&gt; &lt;span class="c1"&gt;-- Or another default value, e.g., 'No Numbers'&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;STORED&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can query it like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; 
  &lt;span class="o"&gt;*&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; 
  &lt;span class="n"&gt;test_data&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; 
  &lt;span class="n"&gt;transformed_column&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data_value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this gives us &lt;em&gt;nearly&lt;/em&gt; the results we would expect.&lt;/p&gt;

&lt;h4&gt;
  
  
  Test 6 results
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;th&gt;transformed_column&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;td&gt;00000001 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01-02-24 data&lt;/td&gt;
&lt;td&gt;00000001-00000001-00000001 data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;td&gt;00000001test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;td&gt;00000002 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2RnSVwq&lt;/td&gt;
&lt;td&gt;00000002RnSVwq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;td&gt;00000002test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3uEKHmHePf&lt;/td&gt;
&lt;td&gt;00000003uEKHmHePf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4a test&lt;/td&gt;
&lt;td&gt;00000004a test&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;
  Full Results for test 6
  &lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;th&gt;transformed_column&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 test&lt;/td&gt;
&lt;td&gt;00000001 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;01-02-24 data&lt;/td&gt;
&lt;td&gt;00000001-00000001-00000001 data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1test&lt;/td&gt;
&lt;td&gt;00000001test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2 test&lt;/td&gt;
&lt;td&gt;00000002 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2RnSVwq&lt;/td&gt;
&lt;td&gt;00000002RnSVwq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2test&lt;/td&gt;
&lt;td&gt;00000002test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3uEKHmHePf&lt;/td&gt;
&lt;td&gt;00000003uEKHmHePf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4a test&lt;/td&gt;
&lt;td&gt;00000004a test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4b test&lt;/td&gt;
&lt;td&gt;00000004b test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6bfS&lt;/td&gt;
&lt;td&gt;00000006bfS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10 test&lt;/td&gt;
&lt;td&gt;00000010 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;td&gt;00000012 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12 test&lt;/td&gt;
&lt;td&gt;00000012 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13IUkOxEVl&lt;/td&gt;
&lt;td&gt;00000013IUkOxEVl&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20 test&lt;/td&gt;
&lt;td&gt;00000020 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25hWTX&lt;/td&gt;
&lt;td&gt;00000025hWTX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40-a-1&lt;/td&gt;
&lt;td&gt;00000040-a-00000040&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;40a&lt;/td&gt;
&lt;td&gt;00000040a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;42-a-1&lt;/td&gt;
&lt;td&gt;00000042-a-00000042&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;796h-eYWy&lt;/td&gt;
&lt;td&gt;00000796h-eYWy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;833HufIZAS&lt;/td&gt;
&lt;td&gt;00000833HufIZAS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9244uVCpGa&lt;/td&gt;
&lt;td&gt;00009244uVCpGa&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AB-34Y67846&lt;/td&gt;
&lt;td&gt;AB-00000034Y00000034&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AwtwQdn09&lt;/td&gt;
&lt;td&gt;AwtwQdn00000009&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cheese and test 12&lt;/td&gt;
&lt;td&gt;cheese and test 00000012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cIhb42WFNQ&lt;/td&gt;
&lt;td&gt;cIhb00000042WFNQ&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dlCSh87811&lt;/td&gt;
&lt;td&gt;dlCSh00087811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;dnjLlW1&lt;/td&gt;
&lt;td&gt;dnjLlW00000001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E5-RMT893Y9&lt;/td&gt;
&lt;td&gt;E00000005-RMT00000005Y00000005&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EV-489RY3DA&lt;/td&gt;
&lt;td&gt;EV-00000489RY00000489DA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FoClSy4727&lt;/td&gt;
&lt;td&gt;FoClSy00004727&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GeGIrPM-H86&lt;/td&gt;
&lt;td&gt;GeGIrPM-H00000086&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;gvSV6z&lt;/td&gt;
&lt;td&gt;gvSV00000006z&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HOFAp_Yx7920&lt;/td&gt;
&lt;td&gt;HOFAp_Yx00007920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kQ33596c&lt;/td&gt;
&lt;td&gt;kQ00033596c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 12 magic test&lt;/td&gt;
&lt;td&gt;my 00000012 magic test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-01-01 data 2020-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-01-01 data 2020-02-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-01-01 data 2021-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-01-01 data 2120-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 01-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 1-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 12-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 2-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 20-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 202-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 2020-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2120-01-01 data 2020-01-01&lt;/td&gt;
&lt;td&gt;my 00002120-00002120-00002120 data 00002120-00002120-00002120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my data 2020-01-01&lt;/td&gt;
&lt;td&gt;my data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my data 2020-02-01&lt;/td&gt;
&lt;td&gt;my data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my magic 12 test&lt;/td&gt;
&lt;td&gt;my magic 00000012 test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;td&gt;my test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pthw068bf&lt;/td&gt;
&lt;td&gt;Pthw00000068bf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RHrIt72402eaLr&lt;/td&gt;
&lt;td&gt;RHrIt00072402eaLr&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;test 1&lt;/td&gt;
&lt;td&gt;test 00000001&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;utjtV03Xns&lt;/td&gt;
&lt;td&gt;utjtV00000003Xns&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uxWLO039hb&lt;/td&gt;
&lt;td&gt;uxWLO00000039hb&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VHCok55901XYVk&lt;/td&gt;
&lt;td&gt;VHCok00055901XYVk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;vTg946&lt;/td&gt;
&lt;td&gt;vTg00000946&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WffSPaBA318&lt;/td&gt;
&lt;td&gt;WffSPaBA00000318&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;wrOae537LGCT&lt;/td&gt;
&lt;td&gt;wrOae00000537LGCT&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;yDKrkCp7960&lt;/td&gt;
&lt;td&gt;yDKrkCp00007960&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;/p&gt;

&lt;p&gt;It breaks on the rows:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;data_value&lt;/th&gt;
&lt;th&gt;transformed_column&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 1-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 12-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;my 2020-02-01 data 2-01-01&lt;/td&gt;
&lt;td&gt;my 00002020-00002020-00002020 data 00002020-00002020-00002020&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As you can see, but overall I think it will suit our needs for now!&lt;/p&gt;

&lt;p&gt;This was based on a &lt;code&gt;LPAD&lt;/code&gt; example I saw. But it had the same flaw that it only worked on Strings with numbers at the beginning.&lt;/p&gt;

&lt;p&gt;So I took that principle and applied it to all number series.&lt;/p&gt;

&lt;p&gt;Also this now uses a generated column so that it is fast on reads (although we could soon make it part of the query if it didn't hurt read performance too much or we were more bothered about write performance).&lt;/p&gt;

&lt;p&gt;The big issue is the fact that this works through "brute force" by working around the fact that MySQL sees "11" as smaller than "2" due to checking one character at a time (the dumbed down version of what happens) by making all numbers 8 digits long.&lt;/p&gt;

&lt;p&gt;It also replaces all numbers in the &lt;code&gt;transformed_column&lt;/code&gt; with the first number it finds and as far as I can tell, other than nesting about 20 statements and using &lt;code&gt;coalesce&lt;/code&gt; or something, there is no way to avoid this behaviour I could find.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I am not really sure? MySQL should join the 21st century and provide an alphanumeric sort option like other DBs?&lt;/p&gt;

&lt;p&gt;While we wish for things to happen I think the short answer in the scope of this article is that I still haven't solved this. &lt;/p&gt;

&lt;p&gt;But with that being said, I feel like test 6 is much closer to the answer than anything I found out in the wild and may be useful to someone someday?&lt;/p&gt;

&lt;p&gt;Obviously if you are expecting numbers longer than 8 digits, you may want to adjust the &lt;code&gt;LPAD&lt;/code&gt; value as that is one limitation.&lt;/p&gt;

&lt;p&gt;Also it isn't perfect so you can't rely on it for scenarios other than sorting user input so they can find things easily.&lt;/p&gt;

&lt;p&gt;I think at this point my best bet is to throw it out into the world and see if some smarter people than me can solve this thing properly! (I am probably needing some much deeper knowledge of sorting in MySQL to really solve it!)&lt;/p&gt;

&lt;p&gt;If someone can solve the problem I have where I can't change &lt;strong&gt;every&lt;/strong&gt; number to 8 digits long, then this would work even better! (so "my 2020-02-01 data 2-01-01" would become "my 00002020-00000002-00000001 data 00000002-00000001-00000001"). &lt;/p&gt;

&lt;p&gt;Or maybe there is a literal one liner that I missed in all my research that solves this? (I will only cry a little bit if there is, I promise!).&lt;/p&gt;

&lt;p&gt;Anyway, that is the best I have, but would love to see something better!&lt;/p&gt;

&lt;h3&gt;
  
  
  Finally here is a DB Fiddle to play with
&lt;/h3&gt;

&lt;p&gt;Here is a DB fiddle of the generated column version to start from if you think you can spot a quick win / improvement:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.db-fiddle.com/f/o2ohcGVAgHZQg4teg1s9jW/1034" rel="noopener noreferrer"&gt;https://www.db-fiddle.com/f/o2ohcGVAgHZQg4teg1s9jW/1034&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading, catch you all soon!&lt;/p&gt;

</description>
      <category>mysql</category>
      <category>webdev</category>
      <category>sql</category>
      <category>programming</category>
    </item>
    <item>
      <title>window.ai - running AI LOCALLY from DevTools! 🤯</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Sun, 30 Jun 2024 08:17:39 +0000</pubDate>
      <link>https://dev.to/grahamthedev/windowai-running-ai-locally-from-devtools-202j</link>
      <guid>https://dev.to/grahamthedev/windowai-running-ai-locally-from-devtools-202j</guid>
      <description>&lt;p&gt;On-device AI in the browser is here - kinda.&lt;/p&gt;

&lt;p&gt;It is currently in Chrome canary which means, it will be here soon(ish).&lt;/p&gt;

&lt;p&gt;In this article I will show you how to get it running on your device, so that you can have a play with it and see what use cases you can think of.&lt;/p&gt;

&lt;p&gt;And I will just say this: Running &lt;code&gt;window.ai&lt;/code&gt; from DevTools &lt;strong&gt;without an internet connection&lt;/strong&gt; is pretty fun, even if the results are "meh"!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Getting up and running only takes 5 minutes!&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Download Chrome Canary
&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href="https://www.google.com/intl/en_uk/chrome/canary/" rel="noopener noreferrer"&gt;the Chrome Canary site&lt;/a&gt; and download Chrome Canary.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Enable "Prompt API for Gemini Nano".
&lt;/h3&gt;

&lt;p&gt;Open Chrome Canary and type "chrome://flags/" in the URL bar and press enter. &lt;/p&gt;

&lt;p&gt;Then in the search box at the top type "prompt API"&lt;/p&gt;

&lt;p&gt;You should see "Prompt API for Gemini Nano" as the only option&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vprwfza1gq6g6ontsy4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5vprwfza1gq6g6ontsy4.png" alt="prompt API in search box on chrome experiments page, there is one item highlighted called " width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Switch that to "enabled".&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Enable "Enables optimization guide on device"
&lt;/h3&gt;

&lt;p&gt;While you are on the "chrome://flags" page, you need to enable a second item.&lt;/p&gt;

&lt;p&gt;Remove your previous search and search for "optimization guide on".&lt;/p&gt;

&lt;p&gt;You should see "Enables optimization guide on device" as your only option.&lt;/p&gt;

&lt;p&gt;This time you want to enable it, but with the "Enabled ByPassPerfRequirement" option.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8deyzeeyhzppmra5bn1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg8deyzeeyhzppmra5bn1.png" alt="" width="800" height="430"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Install Gemini Nano
&lt;/h3&gt;

&lt;p&gt;Last step, we need to install Gemini Nano on our device.&lt;/p&gt;

&lt;p&gt;This is actually part of a bigger tool, but we don't need to worry about that, except for the fact that it helps us know what to download.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; This file is 1.5gb. It doesn't tell you that anywhere so if you have a slow connection / pay per Gb of data / low storage space you may not want to do this!&lt;/p&gt;

&lt;p&gt;Head to: "chrome://components/". &lt;/p&gt;

&lt;p&gt;Hit Ctrl + f and search for "Optimization Guide".&lt;/p&gt;

&lt;p&gt;You will see an item "Optimization Guide On Device Model".&lt;/p&gt;

&lt;p&gt;Click "Check for Update" and it will install the file.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnvvjmcbkrewzhqvghvb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnvvjmcbkrewzhqvghvb.png" alt="On the chrome components page, search box is showing with " width="740" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. DONE!
&lt;/h3&gt;

&lt;p&gt;Last step: Restart Chrome Canary for the changes to take effect.&lt;/p&gt;

&lt;p&gt;Add that is it, now we can move on to using AI locally!&lt;/p&gt;

&lt;h2&gt;
  
  
  Using &lt;code&gt;window.ai&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;If everything worked as expected then you should now be able to open DevTools (F12), go to the "Console" tab and start playing!&lt;/p&gt;

&lt;p&gt;The easiest way to check is to type &lt;code&gt;window.&lt;/code&gt; into the console and see if &lt;code&gt;ai&lt;/code&gt; comes up as an option. &lt;/p&gt;

&lt;p&gt;If not, go back and check you didn't miss a step!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating our first session.
&lt;/h3&gt;

&lt;p&gt;Just one command is needed to start a session with our AI model.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;chatSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Don't forget the &lt;code&gt;await&lt;/code&gt;. I did originally 🤦🏼‍♂️!&lt;/p&gt;

&lt;p&gt;There is also an option of &lt;code&gt;createGenericSession()&lt;/code&gt; but I haven't worked out what the difference is yet!&lt;/p&gt;

&lt;p&gt;Now we can use that session to ask questions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sending a prompt
&lt;/h3&gt;

&lt;p&gt;For this we just use the &lt;code&gt;.prompt&lt;/code&gt; function on our &lt;code&gt;chatSession&lt;/code&gt; object!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;chatSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hi, what is your name?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Yet again, all async, don't forget the &lt;code&gt;await&lt;/code&gt; (I didn't make the same mistake twice...honest!).&lt;/p&gt;

&lt;p&gt;Depending on the complexity of your prompt and your hardware this can take anywhere from a few milliseconds to several seconds, but you should eventually see &lt;code&gt;undefined&lt;/code&gt; in the console once it has done.&lt;/p&gt;

&lt;h3&gt;
  
  
  Getting the response.
&lt;/h3&gt;

&lt;p&gt;Now we just have to &lt;code&gt;console.log&lt;/code&gt; the &lt;code&gt;result&lt;/code&gt;!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we get:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  As a large language model, I do not have a name.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pretty underwhelming, but at least it works!&lt;/p&gt;

&lt;h3&gt;
  
  
  Quick and Dirty Reusable example
&lt;/h3&gt;

&lt;p&gt;Obviously you don't want to have to keep sending multiple commands, so you can copy and paste this function into your console to make things easier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;askLocalGPT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatSession&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;starting chat session&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
      &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createTextSession&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;chat session created&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatSession&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;promptText&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; 
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And now you can just type &lt;code&gt;askLocalGPT("prompt text")&lt;/code&gt; into your console.&lt;/p&gt;

&lt;p&gt;I personally have that saved as a snippet in &lt;code&gt;Sources &amp;gt; snippets&lt;/code&gt; for quick access when I want to play with it.&lt;/p&gt;

&lt;p&gt;Have fun!&lt;/p&gt;

&lt;h2&gt;
  
  
  Is it any good?
&lt;/h2&gt;

&lt;p&gt;No&lt;/p&gt;

&lt;h2&gt;
  
  
  Really? It isn't any good?
&lt;/h2&gt;

&lt;p&gt;I mean, it depends on the measuring stick you are using.&lt;/p&gt;

&lt;p&gt;If you are trying to compare it to Claude or ChatGPT, it is terrible.&lt;/p&gt;

&lt;p&gt;However for local playing and experimentation it is awesome!&lt;/p&gt;

&lt;p&gt;Also bear in mind that each time you ask a question, it does not automatically have memory of what you asked previously.&lt;/p&gt;

&lt;p&gt;So if you want to have a conversation where the model "remembers" what was said previously you need to feed previous questions and answers in with your new question. &lt;/p&gt;

&lt;h2&gt;
  
  
  Is it fun to play with?
&lt;/h2&gt;

&lt;p&gt;Yes. &lt;/p&gt;

&lt;p&gt;The fact I can get it to work locally in my browser is pretty cool. Plus it can do simple coding questions etc. &lt;/p&gt;

&lt;p&gt;And the beauty is no big bills! You can use the full 32k context window as often as you want without worrying about racking up a big bill by mistake.&lt;/p&gt;

&lt;p&gt;Oh and while I said it isn't very good, it can do summaries quite well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;askLocalGPT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;can you summarise this HTML 
for me please and explain what the page is 
about etc, please return a plain text response 
with the summary and nothing else:&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;article&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And with a little playing it outputs:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This article explains how to run window.ai locally in your browser using Google's large language model (LGBL). &lt;/p&gt;

&lt;p&gt;It describes the necessary steps, including enabling the "Prompt API for Gemini Nano" and "Optimization Guide on Device Model" flags in Google Chrome Canary, installing Gemini Nano, and restarting Chrome Canary. &lt;/p&gt;

&lt;p&gt;The article then demonstrates how to use window.ai by creating a text session, prompting the AI model, and receiving the response. It concludes by discussing the possibilities and future enhancements of window.ai.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What will you build?
&lt;/h2&gt;

&lt;p&gt;I have only just scratched the surface of the new API, but I can see it being really handy for creating "custom GPTs" for your own use for now.&lt;/p&gt;

&lt;p&gt;In the future once AI is available in-browser for everybody, who knows what amazing things will be created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;While I find this exciting as a developer and the possibilities it opens up, there is a large part of me that dislikes / is cautious of this.&lt;/p&gt;

&lt;p&gt;People are already throwing "AI" into everything for no reason. Having it run locally on people's machines will only encourage them to use it for even stupider things!&lt;/p&gt;

&lt;p&gt;Plus there are probably about 50 other things around security, remote AI farms, etc. etc. that are likely to make me cry in the future the more I think about it. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Video encoded as emojis⁉️ Stored in a DB? 😱🤯</title>
      <dc:creator>GrahamTheDev</dc:creator>
      <pubDate>Tue, 23 Apr 2024 08:24:56 +0000</pubDate>
      <link>https://dev.to/grahamthedev/video-encoded-as-emojis-stored-in-a-db-mp1</link>
      <guid>https://dev.to/grahamthedev/video-encoded-as-emojis-stored-in-a-db-mp1</guid>
      <description>&lt;p&gt;OK, even I think I have gone too far this time!&lt;/p&gt;

&lt;p&gt;Can we convert video to emojis (silly) and then save those emojis in a DB (a row per line of "pixels") and serve the "image" from the DB...apparently yes!&lt;/p&gt;

&lt;p&gt;Should you do this? No.&lt;/p&gt;

&lt;p&gt;Was it fun? A bit.&lt;/p&gt;

&lt;p&gt;Will it make you smile? I hope so, it was a little more work than I though it was going to be!&lt;/p&gt;

&lt;p&gt;To give you an idea of how silly this was, I had to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;convert a video to frames at a low resolution&lt;/li&gt;
&lt;li&gt;read each of those frames and get the pixel data&lt;/li&gt;
&lt;li&gt;convert that pixel data into emojis&lt;/li&gt;
&lt;li&gt;store those emojis in a DB (Postgres using Supabase)&lt;/li&gt;
&lt;li&gt;create a stored procedure to retrieve a frame so I could make API calls in quick succession to play a "video".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a lot to unpack here, and some interesting things we can learn along the way, but first...the demo!&lt;/p&gt;

&lt;p&gt;Click the big play button in the following CodePen to see it in all of it's glory!&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;WARNING: This will use 20MB of data, so if you are on 4G, don't press play if you have an expensive data plan!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/dyLdMBK?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The most amazing part of that experiment is the fact that I am calling each frame individually, one after the other from Supabase, pretty impressive latency I have to admit!&lt;/p&gt;

&lt;p&gt;More on how I store the data (and how I created it) later.&lt;/p&gt;

&lt;h2&gt;
  
  
  One last note before the demo
&lt;/h2&gt;

&lt;p&gt;There is a reason we don't store video as emojis, it takes up loads of space.&lt;/p&gt;

&lt;p&gt;As the above demo is on the free tier of Supabase it may suddenly stop working as 5GB of egress will only allow the video to be played 250 times before I use too much data! &lt;/p&gt;

&lt;p&gt;If you press play and nothing happens, I probably hit that limit, here is a video of what you would have seen:&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/d7sSwKDkZWE"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Interesting parts of this experiment.
&lt;/h2&gt;

&lt;p&gt;*&lt;em&gt;As always, this is not a tutorial. *&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;But, there were a few interesting things along the way that may be useful for reference:&lt;/p&gt;

&lt;h3&gt;
  
  
  Converting and storing the data
&lt;/h3&gt;

&lt;p&gt;There were a few interesting things to consider here. &lt;/p&gt;

&lt;p&gt;You see, storing pixel data as emojis is not very efficient (I eluded to this early). &lt;/p&gt;

&lt;p&gt;So we needed to find a way to minimise the amount of data we stored.&lt;/p&gt;

&lt;p&gt;Once I converted the video into frames and down-sampled it I was left with this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5lo5sewntan5wxndkzw.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff5lo5sewntan5wxndkzw.jpg" alt="Rick Astley never gunna give you up single frame with large black bars each side" width="256" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And there were a couple of problems with that.&lt;/p&gt;

&lt;h4&gt;
  
  
  Removing the black bars.
&lt;/h4&gt;

&lt;p&gt;Ahhh the 80s, where TVs were nearly square and mullets were fashionable.&lt;/p&gt;

&lt;p&gt;Unfortunately some things have changed since then.&lt;/p&gt;

&lt;p&gt;We now use 16:9 vs 4:3 aspect ratios, which means we have some huge black bars to contend with.&lt;/p&gt;

&lt;p&gt;There is no point storing those as emojis.&lt;/p&gt;

&lt;p&gt;Now, we will cover retrieving the pixel data later, but all you need to know is I retrieve it a row at a time and loop through each pixel on a row.&lt;/p&gt;

&lt;p&gt;So in order to remove those black bars there were many things I could have done here. &lt;/p&gt;

&lt;p&gt;But I like simple and my solution was this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;f&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pixelCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;pixelCounter&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;219&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
  &lt;span class="c1"&gt;//do something with the pixel data otherwise ignore it.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As my image is 256 pixels wide, I simply skip over the first and the last 36 pixels, so I only process the 182 pixels on each row that have meaningful data.&lt;/p&gt;

&lt;p&gt;Is it ugly? yes!&lt;/p&gt;

&lt;p&gt;Is it inefficient? Yes!&lt;/p&gt;

&lt;p&gt;Does it work? Also yes!&lt;/p&gt;

&lt;h4&gt;
  
  
  Removing some row data.
&lt;/h4&gt;

&lt;p&gt;The other issue I had was size. (that's what she said...😱)&lt;/p&gt;

&lt;p&gt;Even with the black bars removed I still had to store 182 pixels x 144 rows = 26,208 pixels!&lt;/p&gt;

&lt;p&gt;The problem with this is that each pixel would be stored as 4 emojis, so we would actually need 104,832 emojis per frame. This was too much!&lt;/p&gt;

&lt;p&gt;Luckily the answer was "simple", we are using 4 emojis per "emoji pixel", so we just sample 1 in every 4 pixels.&lt;/p&gt;

&lt;p&gt;Now we can't sample every 4th pixel, it isn't that simple. If we did that then we would lose too much information horizontally. &lt;/p&gt;

&lt;p&gt;So we actually sample a pixel every 2 pixels, every 2 rows.&lt;/p&gt;

&lt;p&gt;So we sample:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;- x1,y1
- x3,y1
- x5, y1
...
- x1, y3
- x3, y3
- x5, y3
...
etc.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That takes us down to just 6,552 pixels to sample, and with 4 emojis per pixel we are back at 23k characters per frame. That will do!&lt;/p&gt;

&lt;h3&gt;
  
  
  encoding the data
&lt;/h3&gt;

&lt;p&gt;The next step is taking that pixel data and converting it to emojis.&lt;/p&gt;

&lt;p&gt;Now one way I could have improved this was to take all the emojis and categorise them by colour (closest match to each RGB value).&lt;/p&gt;

&lt;p&gt;But that was a little bit too much work for this silly experiment.&lt;/p&gt;

&lt;p&gt;So instead what I did was pick just 5 emojis to work with: 🟥🟩🟦⬜⬛&lt;/p&gt;

&lt;p&gt;For each pixel we would grab each of the RGB values and then based on the strength, either show a coloured emoji, or a black or white one.&lt;/p&gt;

&lt;p&gt;The logic I went with in the end was as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;check the red value. &lt;/li&gt;
&lt;li&gt;is it greater than 127 (half of 255).&lt;/li&gt;
&lt;li&gt;if "yes", then show the red emoji.&lt;/li&gt;
&lt;li&gt;if "no", then we show either white or black.&lt;/li&gt;
&lt;li&gt;repeat for Green and Blue.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now you might be wondering, where do we decide whether to show black or white?&lt;/p&gt;

&lt;p&gt;We do that by looking at the combined values of R + G + B.&lt;/p&gt;

&lt;p&gt;If R + G + B &amp;gt; 500, show the white emoji, otherwise show the black emoji.&lt;/p&gt;

&lt;p&gt;This works surprisingly well, for such a simple and "binary" expression of colour.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it all together.
&lt;/h2&gt;

&lt;p&gt;So listen...before I show you this, bear in mind that this is a single use, throwaway bit of code OK.&lt;/p&gt;

&lt;p&gt;I don't want you doing a code review on this garbage.&lt;/p&gt;

&lt;p&gt;We clear? Good. 🤣&lt;/p&gt;

&lt;p&gt;There are a few things I haven't covered that this code does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;loads an image from an image input onto a canvas and grabs the pixel data.&lt;/li&gt;
&lt;li&gt;putting image data onto the second canvas (hint &lt;code&gt;Uint8ClampedArray&lt;/code&gt; is important for this...I got stuck on that for a while!) &lt;/li&gt;
&lt;li&gt;grabbing the image data itself (Another useful tip: image data from a Canvas is stored in a "flat array" in 4 byte chunks, so you have to jump 4 array items for each pixel. e.g. R1, G1, B1, A1 then R2, G2, B2, A2).&lt;/li&gt;
&lt;li&gt;builds an ugly &lt;code&gt;INSERT&lt;/code&gt; statement to insert our image data into a database table.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is an image you can download if you want to test it (as it ONLY works on this specific image size and removes black bars based on these images only.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qdx6yakcozl3pk94pld.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2qdx6yakcozl3pk94pld.jpg" alt="Rick Astley with black bars sample" width="256" height="144"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And here is a CodePen with the code I used to convert the frames...ugly but it works.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/GrahamTheDev/embed/yLrvggg?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  The last part, the database query
&lt;/h2&gt;

&lt;p&gt;Honestly, this part was super simple thanks to Supabase, there are probably only a couple of points worth covering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Storing and retrieving the data
&lt;/h3&gt;

&lt;p&gt;I decided to make this a little more fun and store each line of pixels as a row in the database.&lt;/p&gt;

&lt;p&gt;Which makes for a really fun visual:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4dm9ol90csh3h7yp72u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj4dm9ol90csh3h7yp72u.png" alt="several rows of a database with emojis as the column data" width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The 2 columns we needed to make sure this worked were the &lt;code&gt;frameid&lt;/code&gt; column (so we could query all rows for a frame) and the &lt;code&gt;rownum&lt;/code&gt; column, so that we can order the rows of the frame.&lt;/p&gt;

&lt;p&gt;Then it was as simple as adding a custom function in  the SQL editor that retrieved this information:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_frames&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frametext&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; 
&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
   &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;STRING_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"rowdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CHR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;video2&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It might look complicated, but if we break it down it isn't so bad!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_frames&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt; &lt;span class="nb"&gt;integer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This first part let's us define a reusable function.  This is important as we can then use this function as an API endpoint later!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frametext&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; 
&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here we define the return type. The part in brackets corresponds to the columns we are returning (if we had multiple columns we would set the name we want the column to be called and the type for each).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;STRING_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"rowdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CHR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;video2&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last part is our SQL query (and returning it as this is a function).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;STRING_AGG&lt;/code&gt; function is one you might not be familiar with. All it does is join columns and characters together, so we grab the &lt;code&gt;rowdata&lt;/code&gt; data and then combine that with &lt;code&gt;CHR(13)&lt;/code&gt; (a new line character) to build our output string.&lt;/p&gt;

&lt;p&gt;The important part to make this work though is the &lt;code&gt;GROUP BY&lt;/code&gt; statement. Without this the &lt;code&gt;STRING_AGG&lt;/code&gt; function won't work as it won't know what data it should aggregate together over multiple rows.&lt;/p&gt;

&lt;p&gt;And then we just have &lt;code&gt;ORDER BY&lt;/code&gt; - this just makes sure we get each row of data in order so the image makes sense!&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating our query and API endpoint
&lt;/h3&gt;

&lt;p&gt;The beauty of Supabase is that now we are just a step away from being able to use that function as an API endpoint. &lt;/p&gt;

&lt;p&gt;We just need an access policy.&lt;/p&gt;

&lt;p&gt;You can find this under "authentication" &amp;gt; "policies" in a project.&lt;/p&gt;

&lt;p&gt;Now, I am fairly new to Postgres, but luckily Supabase had a useful template all set up that let me grant Read permissions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcdoln4meqhglkdtbwp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxcdoln4meqhglkdtbwp2.png" alt="Supabase dashboard screenshot, enable read access for all users template option" width="800" height="179"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And with that, I can now call that function as an API endpoint!&lt;/p&gt;

&lt;p&gt;(if you aren't sure where to get that info, it is in "Database" &amp;gt; "Functions" and you will see a row similar to this: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ixqwjt9y986eo0g6s3s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ixqwjt9y986eo0g6s3s.png" alt="row of data with columns " width="800" height="37"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Calling our API endpoint
&lt;/h4&gt;

&lt;p&gt;Last thing we need, getting our data.&lt;/p&gt;

&lt;p&gt;For that we need our &lt;code&gt;SUPABASE_URL&lt;/code&gt; and our key (&lt;code&gt;SUPABASE_KEY&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Took me a little while as I was new to Supabase, but they are located under "Project Settings" &amp;gt; "API".&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3whusenqj7epb6koytpt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3whusenqj7epb6koytpt.png" alt="Supabase project URL and project API key on dashboard" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then all we need is the Supabase SDK (I got it here: &lt;a href="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.42.0/dist/umd/supabase.min.js" rel="noopener noreferrer"&gt;https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2.42.0/dist/umd/supabase.min.js&lt;/a&gt;) and to create a client with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SUPABASE_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we are ready to go!&lt;/p&gt;

&lt;p&gt;To call an API endpoint you use &lt;code&gt;.rpc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So my function looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_frames&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
       &lt;span class="c1"&gt;//do stuff&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// catch error&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;get_frames&lt;/code&gt; is the name of the function I created earlier and &lt;code&gt;{ frame: num }&lt;/code&gt; is the variable name and the data I want to pass in to the function.&lt;/p&gt;

&lt;p&gt;And with that, we are done! &lt;/p&gt;

&lt;p&gt;Video, encoded in emojis, stored in a DB, served via Supabase APIs!&lt;/p&gt;

&lt;h2&gt;
  
  
  Edit / note on performance
&lt;/h2&gt;

&lt;p&gt;As &lt;a class="mentioned-user" href="https://dev.to/mattlewandowski93"&gt;@mattlewandowski93&lt;/a&gt; pointed out, this is inefficient as I am requesting a frame at a time, which creates a lot of network requests.&lt;/p&gt;

&lt;p&gt;I wanted to test out Supabase's latency, so it was deliberate.&lt;/p&gt;

&lt;p&gt;We could change the function that grabs the frames to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;create&lt;/span&gt; &lt;span class="k"&gt;or&lt;/span&gt; &lt;span class="k"&gt;replace&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;get_all_frames&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; 
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="k"&gt;table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frametext&lt;/span&gt; &lt;span class="nb"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt; 
&lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
  &lt;span class="c1"&gt;--grab current board&lt;/span&gt;
   &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;QUERY&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;STRING_AGG&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;"rowdata"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;CHR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;video2&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;frameid&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="err"&gt;$$&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To return all frames at once, saving on network requests.&lt;/p&gt;

&lt;p&gt;Then we could render it a lot faster (at the expense of initial load time) as we have all the data at once.&lt;/p&gt;

&lt;p&gt;It was a great point that I hadn't explained, so I thought I would include it here to explain why I did it this way.&lt;/p&gt;

&lt;h2&gt;
  
  
  You might be asking, WHY?
&lt;/h2&gt;

&lt;p&gt;A valid question!&lt;/p&gt;

&lt;p&gt;I like to learn through silly things like this.&lt;/p&gt;

&lt;p&gt;I never really used Supabase and they asked me if I wanted to write an article for them to celebrate the &lt;a href="https://supabase.com/blog/ga-week-summary" rel="noopener noreferrer"&gt;Supabase launch week&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So I wanted to try them out. (in retrospect, doing something "normal" might have been a better idea so I didn't eat through my 5GB of free egress data, which would be very generous for most applications but not for this! haha).  &lt;/p&gt;

&lt;p&gt;I am sure when they asked me to write they were expecting a tutorial, or something useful, but we all know I don't do that! &lt;/p&gt;

&lt;p&gt;Additionally, I also wanted to learn a little more about the &lt;code&gt;&amp;lt;canvas&amp;gt;&lt;/code&gt; element and how to manipulate pixel data, as I have an even sillier idea using that (Doom 3...in the browser...in emojis - yes, I am serious! 🤣). &lt;/p&gt;

&lt;p&gt;Anyway, hopefully you learned a little bit too, but if not, I hope you at least enjoyed my silliness.&lt;/p&gt;

&lt;p&gt;Oh, and last thing: I rick rolled you, &lt;strong&gt;in emojis.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;That is a huge W for me and an even bigger L for you I reckon, so that is another reason why I did this! 🤣💗&lt;/p&gt;

&lt;p&gt;Have a great week everyone and let me know what you thought in the comments.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>postgres</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
