Putting the C in CSS Crimes
Speaker Introduction and Cohost CSS Crimes: Hijacking Existing Keyframes
Lyra Rubain introduces herself as the creator of CSS Clicker and an x86 CPU emulator written entirely in CSS, tracing her CSS journey back to the social media platform cohost. She explains that while cohost permitted inline styles but not style blocks, users discovered creative workarounds — including hijacking Tailwind's built-in spin keyframes to produce animations — establishing the mischievous spirit of 'CSS crimes' that runs throughout the talk.
Building CSS-Only Games and Interactive Art with Details and Summary Elements
Lyra demonstrates how the HTML details and summary elements act as clickable state triggers, enabling fully interactive CSS-only experiences without JavaScript. She walks through a point-and-click adventure game built from chained details elements with absolutely positioned images, and then shows a pixel art program that cycles colors by exploiting the spatial layout shift that opening nested details elements produces.
2D Games, Advanced Cohost Techniques, and the Case for CSS as a Real Language
Lyra scales the pixel-shifting technique into a directional 2D top-down adventure game and surveys further cohost tricks: element resizing for 3D positioning, CSS counters for live score display, animation-based randomness, and SVG data URIs for embedding style blocks. She then pivots from cohost's inline-style constraints to full CSS crimes via the checkbox hack, and frames the philosophical question — 'Is CSS a real programming language?' — that motivates the rest of the talk.
The CPU Hack: Enabling Iterative Loops in CSS Custom Properties
Lyra introduces the foundational technique that makes a CSS CPU possible: the 'CPU hack' discovered by JN Ory in 2023, which enables repeated iteration of CSS custom properties — something CSS normally prevents. She explains how container style queries can gate animations so that a triangle of variables feeds into each other with a math operation applied each clock cycle, effectively counting upward and demonstrating the minimal loop primitive needed for computation.
Building Memory, Registers, and a Minimal Instruction Set
With the clock mechanism established, Lyra expands the CPU by adding multiple memory variables, a read function that maps integer addresses to stored values, and a write mechanism that conditionally updates the targeted variable. She introduces an instruction pointer that cycles through program memory, implements the first real instructions — MOV and basic arithmetic — and demonstrates hand-compiling a short program into machine code that loads and executes inside CSS.
Adding Control Flow: Flags, Conditional Jumps, and Loops
Lyra adds branching and looping to the CSS CPU by implementing a compare instruction (secretly subtraction) that sets zero and sign flags, and a family of conditional jump instructions (JL, JLE, JE, JGE, JG) that read those flags to redirect the instruction pointer. She walks through translating a ternary expression and a for loop into assembly and shows the CPU successfully executing a three-iteration loop.
Implementing I/O: ASCII Output and On-Screen Keyboard Input
Lyra adds interactive input and output to the CSS CPU. For output, she builds an ASCII integer-to-string lookup table and appends characters to a CSS content-list property displayed via a pseudo-element. For input, she constructs an on-screen keyboard that writes key values into an input register using hover and active pseudo-selectors, then demonstrates a live echo program that waits in a polling loop and prints each keystroke to the screen.
Scaling to x86: Bitwise Operations, the Stack, and the Peculiar Register Layout
Lyra begins adapting the custom CSS CPU to match the real x86 architecture, starting with bitwise AND, OR, XOR, NOT, and shift operations implemented by decomposing values into individual bits and reassembling. She covers the stack — essential for function calls and return addresses — and then walks through x86's famously odd register layout, including the AX/BX/CX/DX split into H and L halves stored in the counterintuitive ACDB encoding order that even Intel's own documentation tends to gloss over.
Decoding Variable-Length x86 Instructions and the ModRM Byte
Lyra tackles x86's most complex feature: variable-length instructions that range from one to five bytes depending on a secondary ModRM byte that encodes operand types, addressing modes, and sometimes even the instruction identity itself. She explains how 'group instructions' sharing the same opcode byte (such as 0x80) require decoding ModRM before the operation can even be identified, and describes implementing the complete ModRM lookup table in CSS to handle every addressing case.
Compiling Real C Code to x86 and Running It in CSS
With the full x86 emulator complete, Lyra demonstrates the payoff: compiling an actual C program — featuring string output, a Pascal's triangle function with nested loops, conditionals, and direct register usage — using a 16-bit GCC toolchain. She shows the compilation, hex dump, and decimal output steps, copies the binary values into CSS memory, and runs the program live in the browser, proving that C code can execute inside a stylesheet.
Q&A: Doom, Speed, Browser Compatibility, and the Origins of the Project
Lyra fields audience questions covering whether the emulator can run Doom (theoretically yes, but she would rather reimplement it in CSS directly), the silliest CSS thing she has built (a DNS-triggered comic-sans injection), how the project started (needing a fresh talk topic after the checkbox hack was deemed too familiar by the organizer), the build time (roughly a week of focused work), why it is Chrome-only (reliance on CSS features not yet available elsewhere), and the emulator's clock speed — approximately 5 Hz, versus the original Intel 8086's 5 MHz.
Without further ado, I would like you to put your hands together for our next speaker, putting the crimes into CSS. It's Lyra Rubain. Best of luck, Lyra.
So, hi everyone. I'm Lyra and I will be giving you a talk about C, which stands for C, the programming language. So who am I? I'm Lyra. You can find stuff on my website, lyra.horse. The slides for this talk are there already up if you need them to like I don't know.
Anyways, what do I do? I started off my CSS journey on this website called cohost. And then I learned some CSS crime stuff from there and took it on with me. So next I made this game called CSS Clicker. Who here has played it? Awesome. Which is a CSS only game.
What else do I do? I blog about CSS. I break browsers. This one was, I think Google messed up on that one. But my latest creation is x 86 CSS, Which is a full on x 86 emulator written in CSS.
And that is That is what this talk is going to be about. But since you all just came from lunch, I think we should start off with something simpler. So let's go back to where I began, the cohost stuff. Some of you maybe have already seen those slides, but they're awesome.
So what is cohost? Cohost is this, what's this, social media website where among other things, you could use HTML in your posts. Now the thing is, you could not use style blocks. But you could use inline styles. And this by itself allows you to do a lot of stuff.
People started using it to make fun little posts, recreations of stuff, and just like funny little things. And these are very basic. Just some fonts and colors and stuff. But people started figuring out some cool tricks. Let's take a look at this one here. This post has animation in it.
How do you do animation in CSS? Well of course you just use keyframes and then you can have an animation. But we can't use a style block. Oh no. How do we have the animation? Well if you poke around on the website, maybe you can find some other keyframes already there. There's the Tailwinds spin keyframes which they use for like a loading thing. But we can use that.
We if we put it in our thing, it starts spinning. And of course, if we add a transform to our element, we can control what it does. So that's a fun way to have animations. Let's take a look at another CSS crime. So this one is cohost 22, I believe.
It's this desktop thing where you can click on stuff, and you get windows opening and stuff happens. Pretty neat. How does that work? Well it uses the details element which you probably are familiar with. You click on it, it opens up and you can start styling it. You make it look like a window, like an icon, position things absolutely. And there you go.
You have an interactive desktop thing. Pretty fun. Okay. But what else can you do with this technique? Well you can make a point and click adventure game like this one from Rykarn. This game has a point and click adventure game that has items, different endings, a lot of stuff.
And the way that works is it just has a chain of details and summary elements like so. So this is a game I made where you can see I'm just opening up the details elements and it plays out the story. But what we can do of course with styles is we can add images. We can position them all absolutely on top of each other. And now when you click on stuff, it opens up the next scene. You can then have the summary elements cover the things you want to be able to click on.
And just like that, you have a full on game. You can see we go ahead and go in the door, pick a door, we lose. We can go in the door, pick a door, we win. Pretty cool. Okay. Let's take a look at another CSS crime from cohost. This one is an art program. You can click on the pixels, and you can have different colors.
Pretty cool, especially like inside of a social media post. And the way this works, if you look at the pixel, is that you can just keep clicking on the pixel and it keeps cycling through colors. How can we do that? Well it uses the same details and summary element technique from before. But it does it in a very very specific way.
It takes the summary elements and it places them in this very specific pattern. If we now take that pattern, we put it inside of a div, and then we have another div that just show a single pixel to the user. What we can do is we can have the summary elements have details of specific sizes in there.
And once you start clicking on them and opening the details, it starts shifting the entire thing around. So if we click on here, everything moves to the right. We get the next color. We keep clicking. Get to the ends. We're back on the first color. And now of course, by clicking on them, we are closing them. So that's how you can have a color changing pixel.
And I guess the only thing to note here is that you need absolute positioning for the summary elements. And then you can just use container percentages to do that. Okay. Let's take a look at another CSS crime. This one is a two d top down adventure game made by Corncicle. Pretty cool. How can we make a game like that?
Well we already have a thing from our pixel thing for moving stuff around. So we can keep clicking in a spot to move to the right. But there is no button to move to the left. But what we can do is instead of using a positive percentage, we can use a negative one. And this shifts around the way the elements are placed so that if we overlay a d pad, going to the right means that there will be a button available on the left.
And this can be done vertically as well, of course. Now this is not a game. This is just moving around. So let's make it into a game. I made this little map here where we need to get the key to open the lock. And the way we can implement this is we just make a copy of the map and we turn it into elements like so.
Now that the elements are on top of the buttons, we can click them. We can't go down because the fence is in the way. Go to the right. We can't go through the lock. Go to the right. And then when we pick up the key, which in reality is just closing a details element, you can see the lock disappears and we can go through the gate.
And then we win. Yeah. If you want the source code it's on Gist. Okay. So that's some Gohost CSS crimes. I'm quickly going to go over some other features we used on Gohost back when that was still around. So we have resizing of elements of course, which you probably already know about. But you can use this for all sorts of things.
For example I used it to control the three d position of yourself in a room to make a three d game where you can move around by just moving your mouse. Yeah. Okay. So another technique is counters. So what you can do is you can use the size of the container to place numbers that move based on the size of the container. So what this means is we can have a number output for how big div is.
So if we resize this now, you can see the number count updates based on the size. And what you can do with that is you can use this, for example, to have a high score. So this is an Ozoclone I made for cohost. And every time you click on one of the circles, it opens up a div that adds one pixel to the container.
And thus it counts up the score. And I learned about this technique from this post which was made before mods was a thing in CSS. So the way this was done back then was like that. Okay. What else? We have randomness.
So randomness is just you animate something and when the user clicks on it, whatever they click on is what gets picked. And I use this in blackjack for example to pick random cards. And last we have SVG backgrounds, where because you can just use a data URI, you can include stuff like style blocks in the SVG. So you can have animations, and you can have media queries.
So for example, you can have a div where if you resize it, it starts playing an animation. Okay. So that's it for the cohost stuff. You can find more stuff there. There is the yellow afterlife's list of cohost CSS crimes. I would recommend taking a look at it. And there is also the CSS Puzzle Box by Blackl, which is really cool. This was cohost stuff.
Cohost was unfortunately shut down, which is quite sad. But anyways, let's move on to CSS crimes that are not limited to just inline styles. Because all of this was just inline styles. So if with Coco CSS Crimes, you had the details element where if you click on it, it does something.
What you have for full on CSS Crimes is the checkbox. You can click on it and it does something. You don't seem to be amazed by that. Wait, I got the email. Let's see. Okay, so it seemed the CCC audience was fouled by the concept of checkbox techniques.
But the CSSD audience is well introduced at CSSD twenty sixteen. You already know about this stuff? Okay, I'm gonna just skip through the slides. Okay, so logic gates. You can do logic gates with checkboxes. You had a session about this last year?
Okay. I guess this means I will have to teach you about something that hopefully none of you know about, which is x, the x 86 architecture. I I have this slide only because I really enjoyed those images.
But okay. What's x 86? X 86 is the is a CPU architecture introduced back in 1978 with the Intel 8,086. And it's the architecture that powers all modern computers. Well, except for Macs and some new Windows computers.
But what this architecture is, is something that pretty much all modern software runs in. So next question. Is C a real programming language? Raise your hand if you think C is a real programming language. Okay, I see a few hands.
Yeah, probably. Is CSS a real programming language? Well we know C is. And C can compile into x86 assembly. So if we can run x86 assembly in CSS. Okay. I'm going to give you all a little warning here.
What follows is going to be Chrome only unfortunately. But yeah. What you're about to see, I don't really blame browser vendors for not implementing all of that properly. But Okay, we start off with the CPU hack. The CPU hack is something JN Ory discovered back in 2023.
And it allows the repeated iteration of CSS variables. So if you wanted to implement something like that in CSS, that's easy. But the moment you want to have a loop, something like that, you can't do it. Even if you try to work around it by having one variable feed into each other, it doesn't work. And this is something the CPU hack addresses and allows us to do. And with this post, Chainori put out a really good example of it being in use, which is this Game of Life clone where it can run infinitely.
Which is something that's not really possible unless you can repeatedly iterate upon variable values. So I'm going to show you how I implemented the CPU hack. It's a little bit different from the original one from Chain Ori. But it's like kind of the same concept. So we start off with some key frames to run a clock.
So you can see it goes from zero to three. And what we can do with that is we can now use container style queries. For example, we can change the background color of the text. Now one thing container style queries allow us to do is also control animations. Usually you can't have one animation control another one.
But with style queries that is possible. And what you can do with that is you can have another variable. So here I am setting the clock two to the variable clock, like the origin clock variable. But because I am pausing this animation most of the time and only running it while the clock is one, you can see that the clock two only updates while the clock is one.
And what we can do with that is we can have this triangle of variables where they sort of all feed into each other, while at the same time evaluating a math expression on them. And this means that we can start counting up numbers. It's really useful. To better explain what's going on there, we start off with the variable a being the value zero.
Then the clock will copy the variable a into variable b. That will go from variable b into variable c. And then it will go back into variable a, but this time with the math operation applied. So a is number one now. We repeat that. B becomes one. C becomes one. And then A is two.
And as we keep repeating that, we are running a math operation every clock cycle. So we have a CPU. Unfortunately, our CPU has just one instruction and one register.
Let's fix that. We'll start off with the register. So let's add some more memory to our CPU. Now it doesn't really matter whether it's the registers or the memory in the context of CSS. But how we do that is pretty simple. Since we already have one variable, we can just add more.
But now the next problem is we have memory but we have no way of accessing it. And ideally we would want to have a function to read out memory addresses, and also some way of writing them. And the way we can do that, let's start off with the reading, is we are going to have a function where you give it an integer. And it gives back the according memory address value.
And the way that works is we just have an if statement with every single memory variable in there. For writing, what we do is we have variables where we define what variable we're writing and what the value is going to be. And then every single variable is going to have an if case where it checks whether we are writing into that specific variable.
So this is memory addressing. We can now read and write memory. So what next? Well we want to now be able to read a program from the memory. And for that we're going to need an instruction pointer. So what an instruction pointer is, is it's this special register that points at the memory.
And whatever it's pointing at is the instruction we're currently executing. So what we do from here is we make it read out the memory to find out the instruction. Then we make it cycle through the program, like so.
And that's our instruction pointer. It's reading the instructions. We can use that to apply styles. So here I am applying transparency when it's 10 and an outline when it's 20. And it's working. Though we don't have any real instructions yet. So let's add some. We are going to start off with the MOV instruction.
What this does is it moves a value into a register or a value from a register into a register. And just so it's not confusing, the assembly notation there, the destination is before the source. So that's why it's written as like mov reg one thirteen. But yeah, that's the first instruction.
And then we are going to add some math instructions, just so we can do some math stuff. And that's enough instructions for now. So let's put them all in a nice little table for us. And let's write the program with these instructions. The program I'm going to run is this program here. But we can't translate into assembly directly.
First we need to deconstruct it into parts, like so. Then we can take each line and turn that into the representative assembly. And finally we can take the assembly and turn that into machine code. So what we're doing here is the registers are just going to be negative values. And the instructions are gonna turn into the numbers in our table.
And this is actually what your compiler does. But we are doing it by hand. Now the way you're going to load this program into CSS is by taking these numbers and throwing them into the initial values of the memory. And now we have our sample program loaded. So let's actually execute this program.
To execute this program we need to make a few adjustments. So first off, of course we need to make it read the arguments as well. So our instruction length is going to be three. We are going to read the arguments from memory. And also we want to make sure we can get the value of the memory address in case it's a register.
So we are going to add more variables for that. And now for the MOV instructions, for the math instructions, we need to save the value somewhere. So we are going to put the first argument as the destination. And then the data we are writing is going to be a function. And this function is going to contain all of the instructions.
So we are going to have the mov instruction. We can add the other ones from this table. And now we can implement every single instruction as a CSS function. So to implement these, the mov instruction is just getting the value of the second argument. The rest of them is just basic CSS math stuff. And that's really all we need to run the program.
So let's try running it. You can see it's stepping through. And it's done. You can see your results there. So we finally have a CPU with no logic.
So let's add logic. What we need for logic? First of all, we want to be able to jump around in the code. So we are going to add a jump instruction. Then we are going to add a compare instruction, which secretly is just subtraction. And it sets some flags. It doesn't save its result. It saves flags whether the result is zero or negative.
And then we're going to have jump instructions that can use the result from the comp instruction to make a jump if the condition is true. There's a lot of different jump instructions we can implement. You can see there's like jump less, jump less equal, chump equal, chump greater, chump greater equal, whatever. X86 has more of them, don't worry.
I mean, some of those are duplicates. Anyways, the way this work is that we check the flags. For example, if the zero flag is set, which means we subtracted a number from itself, it means it's equal. And the same for the other ones. So you're using these flags to make these operations happen. So okay. We have these new instructions.
Let's make a program with them. The first example I'm going to show is a program where we have a ternary f. So we can deconstruct it and make it into assembly like so. You can see we have the compare instruction. And right after we have the jump less equal instruction, which is the conditional part. We can also use this to make loops.
So this is a four loop example. You can see we are going through this loop three times. So in the assembly, we are doing a compare at the end. And if the compare is less, then we are jumping back and running the code again. So let's implement it. To implement this stuff, what we need to do is first we need to, of course, add the flags.
Then we need to make sure we can change the instruction pointer around. So we are going to make it writable just like any other register. And then we are going to want to make sure that the right destination is something we can change too. So we are going to have a new function for that. This new function is going to default to the first argument.
But the rest of the functions in there are the special cases. And what the special cases do is, for example, the compare instruction only writes into the flags. The jump instruction writes into the instruction pointer. And the JL instruction only writes into the instruction pointer if the flags are set as required.
Otherwise, it just writes it nowhere. And that's all we need, so let's run it. You can see it gets to the end, goes back, goes back one more time, and then it's done. We have our result. But well we have a CPU and it can do stuff but there's no IO. There's no way for us to interact with our program. So how do we have IO?
Let's start with the output. We want to be able to print ASCII text out from our program. And the way we are going to do it is if we write into a special out register, it's going to print that specific character on the screen. And to do that we are going to need a CSS function that converts integers into ASCII strings.
What would an example program look like with that register? Well if we want to print out the string, we're just going to take every single character and we're going to move it into the out register. And the characters are just going to be numbers. Okay. Let's try running that. So to run that, what we need is we first need to of course add the register.
Or this is not actually a register. This is a property for the output text. And what's special about it is it uses the content list type, which is the same type that the content property uses. Which means we can keep putting strings after it to make it longer. Then we will have the output set to itself, unless it is being written to.
In which case it's going to be itself, plus the extra character we are writing. And now last oh, we also need to, of course, show the text on the screen with a pseudo element. But now last, we need to implement this function here that converts integers into strings. And the way that's going to work is we're just going to have a table like that.
It just has the string for every single case. But now if we run our program, you can see it starts printing out text. Okay. So that's output.
Let's do input two. So for input, we are going to want to have an on screen keyboard where if we press a button, we detect it using the hover and active pseudo selectors. And then we write the output into an input register, or the input into the input register. What we can do then is we can have a piece of code that that constantly checks whether we have input anything.
And if we have, it jumps out of the checking loop and does something. And last, we're going to have a register to hide the keyboard when it's not in use, just to be nice. So what would an example program look for this? Well let's say we want to make it so that if you press a letter, it's just going to print it out.
The way we would implement that is we have a loop that keeps waiting for the input. And once the input appears, it copies the input into the output, then clears the input, and then jumps back into the beginning. The way we are going to implement that is first we are of course going to need the keyboard. You get the CSS for free if you're cool.
Then we are going to need extra registers. And then we are going to make it so that the keyboard is not shown unless the register is set. And last, we need to make it so that every single key, when you hold it down, is writing into that input register. So we're just going to have every single case in the code like that. But Okay, let's try it out.
So I'm running the program. You can see it's showing the keyboard. You can see it. We press a letter. It types it out. Let's try it again. Let's press another letter. And there we go. So this is input and output.
And now do we finally have a CPU? Yes. We do have a CPU that can do everything we want. But it's not x86. So this is going to be a bit painful.
I'm going to try to just run through this now. I'm not going to be showing in-depth examples because x86 is kind of out there. So first I have this bonus slide. I don't know where to put it. But did you know that even if you don't have a custom property, you can throw it through a function that's typed to be able to use style queries on it, even if you use cloak and stuff like that.
I don't know. I just think it's a cool trick. Maybe you'll find it useful. But anyways, first for x86, we need bitwise operations, which we are going to implement like this. Hopefully you can see the text. No? Okay. I'm going to show it with four bits because it's a bit easier to see. So essentially we're just going to take the bits of every value.
We're running math operations with them. And then we're putting it all back together. And the same way we can implement the OR instruction, the XOR instruction, and the not instruction. Though it turns out for not, there's an even better way to implement it. Then for logical shifts, we can do this stuff.
I don't know why, but CSS and everything in the browser kept breaking down with that left shift. So I changed it to that. And then it was fine. But yeah, that's the bitwise operations. I think they are quite nice compared to everything else. So then there's the stack, which I'm not going to go too in-depth with. But essentially the stack allows you to put values into it and pop them out.
And it's used, for example, to do function calls. So when you call a function, it saves the previous instruction pointer address. And then when you return from the function, it knows where to go back to. So it's pretty useful to have that in a CPU natively. But Okay, let's take a at the registers of x86. So the registers in x86 are ax, bx, cx, and tx. Those are the main registers.
Pretty easy to remember right. Well it turns out those x registers split themselves into h and l registers for some reason. And when you thought that that like a b c d order was nice, it's actually a c d b.
Which is which is so absurd that the developers of debugger tools pretend it's not a thing. And even Wikipedia and Intels own manuals pretend it's not a thing. But behind everything, the register encoding is in that order, which is what we're going to have to deal with when we are writing an x86 emulator. Okay.
So those are the main registers. We also have the instruction pointer. That makes sense. We have the stack pointer and some other pointers. I guess that's nice. The We segment registers, which are there to address more than 64 kilobytes of RAM. Luckily, we don't really need to deal with that.
We don't want programs that big in our CSS anyways. And then we come to the flags. So of course, we have the zero and sign flag that we're already familiar with. Then we have the carry flag and the overflow flag, which are pretty much the same thing. I think the difference is that one is signed and the other one is not. And if you thought that was not enough carry flags, there's also the half carry flag when you like to have a carry.
Then we have the parity flag for whether the amount of bits is even or odd. We have the trap and interrupt flags, which are actually kind of useful. We have the text direction flag. And then we have no more flags. They left the rest of them empty. They ran out of ideas, I think. But those are the registers.
So we have to implement all of that. Okay, so what else does x86 have off there? Well, it's a 16 bit architecture. But our memory is eight bit. So remember how we were writing memory addresses like so? Well actually we have to split them up into two and then split the value in half and write half the value in one memory and the other half in the other memory.
Okay but now we come to the worst part which is that x 86 instructions are all like, okay so our instructions that every instruction was three bytes long. Makes sense. X 86, how long are they? Well, they are all different. These instructions are one byte long.
These are two bytes long. These are three bytes long. And then we have a couple that are five bytes long. So that's already a bit annoying. But we can just put all of them in a table. Right? Right? No, because all of those instructions, we don't even know how long they are.
So the reason for that is the ModernM byte. So what is ModernM? Well let's take an instruction that uses ModernM. This is the subtract instruction byte hex 28. Let's take the modern table from Wikipedia. Let's take this another table. And let's try to decode an instruction. We have some bytes there.
We don't know how long this instruction is. The first byte tells us it's the sub instruction. Okay. But then the next byte, the modern byte, we must first decode. And based on that, we take the mod field and the rm field to find out that the first operand where it is writing to is the register b p plus register s I added together. And that's the memory address.
And of course the middle one is the second operand. Okay. But at least we know that this instruction is two bytes long. So that's Okay. But let's take another instruction. This instruction, it's of course the sub instruction. But when we take it apart into the model rem thing and we do the lookup, we get this SI plus TISP 16 thing.
What is the TISP 16? That's not one of the registers. Well it's the next two values. So what this means is once we finish decoding it, this instruction is four bytes long. And to figure that out, we needed to read the second byte and do a lookup into this model rm table. And this table, I mean it makes sense for the most part.
It has like a pattern going except for just like one thing there, one cell. To implement that in CSS, I just had to cover every single case. But yeah. Now where it gets even worse, there are x86 group instructions. I'm going to show you a list of instructions.
So these are various different instructions. But you'll notice that they are all byte 80. What's up with that? Well it turns out that which instruction it is is dependent on that modern m byte. So we first have to decode the modern m to even figure out what the instruction is. But I'm I'm sure they had good intentions.
I'm sure they were running out of space to put instructions in. I guess that's why they added the instruction byte 82, which is exactly the same as byte 80. And it is also why they left half of like, well not half, but they left a lot of the instructions empty. Okay. But we can implement it in CSS.
We just implement all of the instructions And for some of them, we have to do the modern m checks. But it's good. Now we truly, finally have a CPU. And we can run C code in it.
So I wrote this little C program here. You can see it's doing a lot of stuff. We are using our hardware registers here for a function. We have some strings that we're printing out. And we have a function call to the Pascal's triangle, which does a lot of programmer y stuff. It has for loops. It has arithmetic and conditionals and all that.
So Okay. Let's run the C code. How do we do that? Well, we put the C code in a file. We compile it with GCC. This is a special version of GCC that's meant for 16 bit architectures. But otherwise it's kind of the same. And now we have our program file, the binary file.
We can take a look at its hex dump. This is our program. We can even use obg dump to find out what the instructions are. And actually what we want to use is this program called od that will print it out as decimal values. But this is our program. We can just take those numbers from there and copy it into our CSS memory. And now we will finally have an x86 C program that we are going to run inside CSS.
So let's give it a shot. Thank you. I'm just going to keep it running in the background.
If you want to get the slides, those are on my website. If you want to visit my blog, I'm going to do a write up about x86 CSS at some point. It's on my site. If you want to try x 86 CSS for yourself, it's also on the site, you can find everything there, my contact information, so on. But yeah, thank you.
Wow. Wow. Lyra, please join me on the chair of Inquisition. Help yourself to a flower if you want. Okay. Iris said, but can it run Doom?
So this specific emulator, no. I think theoretically you might be able to make it work. But honestly I would just reimplement it in CSS directly. That's a bit more efficient.
There you are Iris. And she continued, I love everything you do. What's the silliest CSS thing you've ever made?
Silliest CSS thing. It has to be some sort of a weird injection attack or hack I've done. I used to have a domain where if you went on shady site to do a DNS lookup on it, it would make the entire page comic sense.
CJ asked, how did you come up with this idea and why?
Well it kind of, I've always wanted to do something like this. But I really got started with it once I wanted to give a talk here. And PPK told me I already know about the checkbox hack, so I had to quickly figure something else out to talk about.
So PPK is the reason why. So blame him. Jesper, or presumably Jesper, said how long from idea to implementation did it take to build the x 86 CPU?
Well, I have been looking at this project on and off for a while. But I think most of the work was just a bit over a week.
Really? I'm asking, why is it Chrome only?
It uses some CSS features that are only available in Chrome. And I'm not sure if the variable key frame thing would work in other browsers too. But first we would need to get the extra features.
Fair enough. Well, Apple rep and Firefox reps, make sure it's in WebKit and Gecko ASAP, please, Vadim. Maybe Looking at
I can have a feature request. I think it would be cool if the Chrome DevTools could debug the CSS without crashing.
Asking for the moon there, honestly. Can I go And Alexis asked, how fast can the CSS x 86 CPU run?
I think it could be optimized. But as it runs currently, the the main demo version of it is running at about five hertz. Now for comparison, the original eighty eighty six ran at five megahertz. So that was a million times faster. But I feel like in a way, this just comes to show how powerful computers are.
Because you can make it do stuff. And you can have menus. You can have it do maths. And when you have a computer with gigahertz, multiple gigahertz, multiple cores, and it takes a few seconds to do something, it really makes you think like, oh maybe maybe we could make something a bit faster. And
did you send the browser or spec bugs for the bitwise problems that you encountered?
No. Half the time, I wasn't sure if it was a problem with me or a problem with the browser. Two:
It's really hard when the DevTools keeps crashing. Who's from Google here? Okay, you want to talk to that person over there. Can you fix it tomorrow? Yeah. Tomorrow. Folks, give it up for Lyra Rebane.
People
- Blackle
- Chainori
- Corncicle
- PPK
- Rykarn
- Yellow Afterlife
Technologies & Tools
- C
- Chrome DevTools
- container style queries
- CSS counters
- CSS keyframes
- details element
- GCC
- Gecko
- objdump
- TailwindCSS
- WebKit
- x86 assembly
Standards & Specs
- ASCII
- ModRM byte
- SVG
- x86
Concepts & Methods
- checkbox hack
- CSS CPU hack
- Pascal's Triangle
Organisations & Products
- cohost
- Intel 8086
Works
- CSS Puzzle Box
- Game of Life
CSS was never meant to be a programming language — which hasn’t stopped
people from turning it into one. Lyra Rebane shows off interactive art and
games built entirely in CSS, including a CSS x86 CPU capable of running C
code.















