Skip to content

How to webify a puzzle

Josh Bodner edited this page Apr 3, 2024 · 46 revisions

Do you normally create your puzzles in Publisher and deliver as PDF? Are you scared about the idea of turning your static puzzle into an interactive webpage? Don't worry, it's a lot easier than you think! If you're new to HTML, start with the basic overview below. Yes, there's some external reading, but the better you understand how webpages work on a fundamental level, the easier it is to know how to put what's in your head onto the screen. Otherwise, skip down to some of the examples or resources available to make it easy to get past the basic structure and onto the specifics of your puzzle.

Contents

HTML Basics

Everything on a webpage is a box. Images are boxes. Text is a box. Even audio is a box, assuming it has playback controls visible somewhere on the page. All these boxes are nested inside each other, sometimes quite deeply. How big they are depends on the type of box, its contents, and any extra styling applied via CSS. We'll cover CSS later, since while it affects how the individual elements on a page look, you'll usually start with just HTML to create the structure and layout of your page.

Within your HTML code, the type of every box is called a "tag" or "element". Elements are denoted by , which means you'll need to do something special if you want to actually display an angle bracket on your page (those are called "escape characters", but you likely won't need to worry about them). The contents of a particular element starts with the , everything that it contains, and then a matching </element name> with a forward slash / preceding the tag's name. Remember: every must have a matching closing !

Element list

Because each element displays a specific type of information in a specific way, it helps to know what the different elements do. The main ones you'll use are:

HTML metadata

  • <html>: This single tag contains the entirety of your webpage.
  • <head>: Information about your page that's not contained on the page itself lives here. For example, the text that the browser puts in the tab for this page, links to CSS or Javascript files that your page uses, etc. Each page has only one head.
  • <title>: The text that the browser puts in the tab for this page. This tag must be inside the head tag.
  • <script>: Javascript code can be written directly in here instead of being linked from another file. This tag should be inside the head tag.
  • <style>: CSS rules can be written directly in here instead of being linked from another file. This tag should be inside the head tag.
  • <link>: When using separate CSS files, you link to them using a link tag. You don't do this for Javascript though. This element should be inside a head tag. This element doesn't need a closing .
  • <body>: All the visible contents of your page lives here. Each page has only one body.

Note that once you learn how to use these metadata elements, their use will generally be the same for every page. You'll see how to use them in the examples below, and you can just copy and paste them as a template for all new pages.

Basic layout

  • <div>: A basic box. On its own it's invisible, but style, contents, or both will fill it to make it as big as it needs to be, or as big as you specify. By default, a div will take up an entire row of space on your page, so if an outer box contains two divs, one will appear on the next line below the other. This behavior can be changed with CSS.
  • <span>: Another basic box. The main difference between this and a div is that a span will happily live inline with other inline elements like spans, so two spans will need to have line breaks manually put between them if you want them to live on separate lines.
  • <br>: A line break. Each one of these you put will add another line of vertical space to your page. This element doesn't need a closing .

Tables

  • <table>: A table is an easy way to set up a uniform series of boxes of a specific height and width, optionally with each cell's boundaries marked with a visible border. Tables contain specific elements for each row and column.
  • <tr>: A row within a table. Rows are defined first, so they'll be directly inside a table element in most cases.
  • <td>: A cell within a row. The number of cells in a row defines the number of columns in a table, and it's generally good practice to keep the number of cells per row consistent within a table.
  • <th>: A special "header" cell, usually used within the first row of a table when you want it styled differently than the rest of the rows.

Text

  • <p>: A paragraph of text, which usually has extra space after it. Text can also be put directly inside most other elements.
  • <h1>,<h2>,...<h6>: Headers, which are bigger than the regular page text. By default, there are 6 available levels of headers, with h1 being the biggest.
  • <ul>: A bulleted list. Each bullet point is specified by a <li> element directly inside the ul element, and nested lists can be created by putting another ul element inside a li element.
  • <ol>: A numbered list. The numbers increment automatically, so you don't need to specify them as part of the list items.
  • <a>: A link to another webpage, or another part of the current page. Any text between the tags is what's displayed on the page. The location of the page, or "URL", is specified as an attribute inside the . See more on attributes below.

Multimedia

  • <img>: An image, either supplied by your webpage, or linked from somewhere else on the web. The size can be controlled with CSS. This element doesn't need a closing .
  • <audio>: Embeds audio on a page, typically from a supplied file like an MP3. Audio playback controls are usually shown, but can be customized.
  • <video>: Embeds video on a page, typically from a supplied file like an MP4. Video playback controls are usually shown, but can be customized.
  • <canvas>: A drawing area that's usually populated by running some Javascript code, either once or many times every second to create an animation. This can also be interactive: users can draw in it in different ways, even changing the initial image supplied by the page.
  • <iframe>: A box that embeds an entire separate webpage within yours. This can also be used just on specific pieces of website, for example to embed just a Youtube video or a Google map on your page.

Input

  • <button>: A clickable button that can have text displayed on it. Usually Javascript is used to run some code when a button is clicked, so buttons should only be used if you plan on having some sort of interactivity.
  • <input>: A generic way of getting user input, although the most common way is with a text box. Browsers often provide standardized controls for many specific types of input, like calendars for picking dates, checkboxes, buttons just like the button element above, file pickers for uploading files, and more. Usually, the data provided by the user is then operated on by Javascript code that's triggered by different user interactions like clicking the mouse or typing on the keyboard. The type of control is specified as an attribute inside the . This element doesn't need a closing .
  • <select>: A dropdown list of items, where each item is specified by an <option> element inside the select element.

Page example

Let's look at an example webpage to see how the browser lays out all its boxes: image This page has some text at the top, a box with space inside and outside of it, and then the puzzle itself, which is a few lines of text and a grid of pictures.

First, to look at the page's code, hit F12 or go to the ... menu and look for a Developer Tools option (it may be hidden under a sub-menu). In the window or panel that pops up, look at the Elements tab: image

Just by following the text on the page, you can see what elements were used to build it. For example, the text at the top with the puzzle name and author is built out of divs and an h1 tag. The grid of images is a table... but where is the image itself coming from? If you expand the style tag, you'll see that it's set completely with CSS! The Developer Tools should also have an option turned on by default to highlight the boxes on the page when you hover your mouse over an element in the Developer Tools window.

This is the box for all of the text on top of the border: image You'll see different colors have been highlighted on the page. Blue is the size of the content itself. Orange is an extra margin that's been applied outside the top of this box via CSS. You can also specify padding, which the browser shows in green, between the edge of the box and the content inside it.

This is the box for the puzzle content inside the border: image The border itself is just created on that div with CSS; any box can have its border made visible. The padding inside the box that's limiting the width of the text is shown in green, but you'll see that the white space outside of the box isn't affected by this div. Instead, it's specified by one of the outer boxes.

Attributes

Clearly, there's a lot more that goes into defining a page than just what's between the tags. You can already see a lot of the tags have attributes inside the . As mentioned in the list of tags, some elements require certain attributes in order to be useful. For example, all of the link tags that are bringing in CSS files have the file location specified by an "href" attribute. The script tag, on the other hand, uses a "src" attribute for the exact same thing. The list of possible attributes for each tag can be found online in documentation.

Additionally, all elements support a set of "global" attributes. The one seen here is the "class" attribute. On its own, class does nothing. It's primary purpose is to provide a way for CSS to style multiple elements the same way with a single rule. Each class is essentially a group. Other useful global attributes include the "id" attribute, which needs to be unique for every element (unlike class), the "style" attribute, which lets you write CSS rules directly in your HTML tag that apply only to that element and all of its contents, and the "title" attribute, which lets you specify popup text when the mouse is hovered over that element.

CSS Overview

Once you get your basic page layout set, you'll want to spend a lot of time making it look pretty. Because some CSS rules can affect the layout of the elements, you might also need to go back and adjust the HTML too. But what exactly is a CSS rule? Basically, CSS provides many different ways to specify which elements you want to apply which style values to.

At the most basic level are all of the different ways you can affect a style value. These are called CSS "properties". For example, there's a property for color, and for height, and for margins (like mentioned above!). A particular group of properties that are all applied together to an element or set of elements is called a "rule", and the elements that a rule applies to are specified via a "selector".

In CSS, a rule is enclosed in {curly brackets}, and the selector is placed directly before the brackets. For example, let's say we want to make all p elements red and bold. We'd write a rule like this:

p {
    color: red;
    font-weight: bold;
}

Reading that in English, you'd say that this rule applies two properties to all p elements: it sets their color property to the value red, and it sets the font weight to bold. You can see that the value you want to set for a particular property is specified after a colon : and each property:value pair within a rule is separated by a semicolon ; and although it's formatted nicely here with line breaks and white space, the white space actually doesn't matter.

Where things start to get complicated is that you can write as many rules as you want that apply to the SAME element or group of elements! CSS decides which ones to use based on which ones come later in the code, as well as which ones are the most specific. That means you can actually overwrite one rule with another, or have part of one rule applied AND part of another. For example, let's say that in addition to our red rule above, we want any paragraph that has a class attribute of "green" to be green instead of red. To do that, we'd write a second rule that looks like this:

p.green {
    color: green;
}

Because this rule is more specific (it applies to only SOME p elements instead of all of them), it will override the red color for every p that also has a "green" class. However, all p elements will still ALSO have their text be bold, because our new rule says nothing about changing the font weight, and so our previous rule for all p elements still applies that particular property.

Selectors

Most of the time, you'll likely end up using classes to do your styling; rarely are there styles that you want to apply to every single element of a particular type. CSS uses a dot . before a class name to denote that it's a class instead of an element. So in our example ".green" is the selector that tells CSS to apply that rule to a class named "green". But wait, we actually wrote "p.green"! That's totally valid; selectors can be combined in many different ways. The way we wrote our rule means "first select all p elements, then select the ones with a "green" class, and then apply these rules to them". If we had written the selector as just ".green", then that rule would apply to ALL elements with the green class, not just p elements.

Another useful selector is the id selector. CSS uses a # hashtag to denote that it's an id instead of an element. As mentioned before, every element's id must be unique, so an id is a good way to style just a single element. You can also select something that's only specifically nested inside something else by using a descendant selector. This selector is very tricky because it's just a blank space! It's the one place in CSS where white space DOES MATTER, so be careful with your spaces. For example, if we had written "p .green" with a space before the dot, what we would actually be telling CSS is to look for an element with a green class that's INSIDE a p element, instead of looking for a p element that HAS a green class.

One useful property of descendant selection is that it will find something at any level inside another element; it doesn't have to just be an immediate child. For example, looking at our puzzle above, if we were to write a rule for ".content-div .puzzle-entry", we would successfully select our .puzzle-entry div even though there are multiple other divs in between it and its ancestor .content-div. If you only want to look for an immediate child, you can use the > angle bracket. By doing that, a rule for ".content-div>.puzzle-entry" would fail to find anything since .puzzle-entry is not a direct child of the .content-div.

CSS also lets you write one rule that applies to multiple selectors by separating the selectors with , commas. For example, if you wanted to write a rule that makes both p and span elements red, you'd write it like:

p,
span {
    color: red;
}

Another important thing to remember is inheritance. When you apply a rule to something, that rule will also apply to everything nested inside that element. That means that if you were to want to write a rule to make everything in the .content-div green in our example puzzle above, all you'd have to do is write a rule that says:

.content-div {
    color: green;
}

You wouldn't have to worry about making any other rules for any other specific elements or classes because the entire puzzle is contained inside that .content-div. This can also lead to frustration when you DON'T want certain properties to be inherited though, so make sure you lay out your elements very carefully so you'll have an easy time selecting them when it comes time to style them.

Because these rules could be coming from different files, style tags, or style attributes, it can sometimes be difficult to know where exactly an element is getting all of its different styles from. Luckily, the Developer Tools have a section to help with that. When you first opened it, you might have noticed another section of the panel with a bunch of CSS rules. If you click on an element in the HTML tree, that panel will tell you all the rules that are being applied to that element, in the order they're applied (which is helpful for seeing which rules are overwriting others): image

Here, you can see that the p elements are getting their font type from a rule applied to the body element, their size and color from a rule on the .content-div class (with that rule overwriting the size property from the body rule), and their spacing from a rule applied specifically on p elements. If you click on the "Computed" section of that panel instead of "Styles", it will show a handy summary of ONLY the properties that are being used on that element, as well as the size (measured in pixels) that it ended up being after all that styling was applied (note the shading colors used in that diagram are the same ones shown above on the page):
image

Properties and Values

Because you're going to be setting values for every property you use, you should familiarize yourself with the different units CSS uses and the range of values they accept. The first one we've already seen is color. There are actually multiple ways to specify color, the first being color names like we used here. A more precise way is to specify their individual components, which in this case are red, green, and blue, in that order. Each component has 255 possible values, and you can use the rgb() function directly with those values.

For example, pure red is rgb(255, 0, 0) because it has no blue or green components. If you know how to count in hexadecimal, you can also use hex notation to specify red as #ff0000. Note that the preceding # must have no space between it and the value components. Here, you can see that hex ff is equal to 255 in decimal. There are plenty of online color pickers that will let you just pick a color visually and give you the numbers you need to recreate it.

Another type of value is a "keyword". Many properties have a specific set of words that are the only values they accept, and these values can be found in online documentation. In our font-weight example, besides bold, you could have also used bolder, normal, and lighter, as well as numeric values. The numbers have no unit here, but other properties do take numbers with units. The most common unit is length, since many properties set the size of something.

The smallest unit of length measurement is the pixel, abbreviated px in CSS. It is also the smallest visible unit of your screen, so it's easy to take screenshots, zoom in, and then count the individual pixels. CSS has many other types of length units, some of which are relative to other measurements, but those can be very tricky to use properly. The most common one you'll use is percentage, which uses the standard % percent sign. It does what you probably think it does: it takes up whatever percent of the available space it has. For example, if you want a div to take up only half the width available to it, you'd set its width to 50%.

Many properties actually set multiple things in a single statement because it's less to write. For example, the "border" property takes three values separated by spaces to set the color, width, and line style, in any order. You don't have to use this shorthand though; you can set individual properties as well. For example, "border: 1px dotted #ff0000;" is equivalent to:

{
    border-color: red;
    border-width: 1px;
    border-style: dotted;
}

Here are the properties you're most likely to need for basic puzzles:

Content

  • color: Sets the color of the content in a box, which is usually the color of its text.
  • height: Sets the height of the box to something specific or relative, depending on the unit. Note that the exact height of the box may be different than what you set if the box's layout style causes it to interact with parent boxes in a specific way (for example, its parent may have a max-height set) or its content is bigger than the size you set (for example, your font is 20px tall but you only want the box to be 10px tall). You can also set min-height and max-height to make your boxes less dependent on content size.
  • width: Similar to height, sets the width of a box, but with some caveats. Some layout styles (see more in the next section), for example, will cause boxes to only be as wide as their contents, no matter how big you set their width. This will likely be a big cause of consternation, and one easy way to set consistent widths is to use tables. Conversely, if you want flexible widths, CSS layout styles like Grid or Flex may be what you're looking for.
  • visibility: Makes a box invisible, but leaves it on the page so the layout doesn't change. This can be useful if you want to uncover something hidden while keeping the everything on the page from moving around.
  • opacity: Useful for making something partially see-through. Opacity is the opposite of transparency.
  • background-image: Lets you show an image underneath the content of its box, just like in the example puzzle above, which shows an image underneath the table cells.
  • background-color: Sets the background color of a box to something besides white.
  • cursor: Sets the mouse pointer when it's hovered over an element. This is useful to hint to users whether something is interactable (for example, making it a hand with the "pointer" value when hovering over something that's clickable like a button or a link, or the typing I beam with the "text" value when hovering over something that users can type into).

Boxes

  • border: A shorthand property to set all 4 borders of a box to a specific color, width, and style of line.
    • Individual borders can be set with their own properties: border-top, border-right, border-bottom, and border-left, all of which are also shorthand properties to set that border's color, width, and style.
    • If you want to set a single property for a single border, you can use its property as needed, for example border-top-color or border-left-width.
    • You can also set just a single property for all borders by using the appropriate border-color, border-width, or border-style property.
    • border-radius: Rounds the corners of your boxes. This is a shorthand property for each individual corner.
    • border-collapse: Tells the individual cells in a table whether they should both show their borders side-by-side, or collapse them into a single border. For example, in a table with 2 columns, if you separate the borders and set them to 1px width, you'll actually see a 2px line down the middle, with 1px belonging to the left cell's right border, and the other belonging to the right cell's left border, assuming no separation between the cells.
    • border-spacing: Tells the individual cells in a table to put extra space between them, effectively setting a margin between them.
  • margin: Sets extra space outside of a box between it and the other contents of its parent. This is a shorthand property; the margin on each of the 4 sides can be individually set.
  • padding: Sets extra space inside of a box between its border and its contents. This is a shorthand property; the padding on each of the 4 sides can be individually set.

Text

  • font-size: Sets the font size, usually to something specific in pixels.
  • font-style: This is how you can make text italic.
  • font-weight: This is how you can make text bold.
  • text-decoration: This is how you can make text underlined or strikethrough. Like borders, this is a shorthand property that lets you set the line position, thickness, color, and style.
  • font-family: Sets the font for text, either to something specific by settings its name, like Arial or Times New Roman, or to a more general category like "sans-serif" (fonts without tails) or "monospace" (every character takes up the same number of pixels in width). Note that when setting a font to something specific, there are only a few "web-safe" fonts that are guaranteed to be available to users and not need to be supplied with your website. If you don't use the safe fonts, the browser may fall back to something else.
  • text-align: This is how you can left or right-align text.
  • vertical-align: This is how you can place text at the top, bottom, or middle of its containing box, although not all elements respond to this property. If you find your element doesn't respond to this property, you may need to change which layout style its parent is using. For example, using Flex or Grid layout lets you instead use the align-items property for vertical alignment.
  • text-indent: An alternative way to indent text if you don't want to mess with padding or margins.
  • text-orientation: This is how you can make vertical text.
  • text-transform: This is useful for displaying user input a certain way, for example if you want all text that users type to be uppercase, regardless of whether or not they actually typed an uppercase letter.
  • text-wrap: This is how you can turn off text wrapping, for example if you want text to intentionally overflow its containing box or to be clipped.
    • To get those effects, you will likely need to set other properties as well. For example, you'll likely need to set the white-space property to "nowrap", the text-overflow property to the desired value, and possibly also the word-break property to "keep-all" if you have incredibly long strings of text without any spaces that you want to overflow.

Advanced Layout

There are also some powerful properties that affect the HTML layout. The first is position, which has a few specific uses. For example, if you want something on your page to be in an exact position you can set it to "absolute" or "fixed", and everything else on the page won't interact with it at all.

Likely more useful is the display property. This is how you enable more advanced layouts. The first tricky thing though is that this property affects all of its contents slightly different than how it affects how its parent displays it, because it actually does both! You'll likely find yourself using this property mostly with divs, possibly with two nested divs so that the outer one controls just how it interacts with its parents, and the inner one controls just the layout of its children.

Let's go back to our example. If you look at the table, you'll see a couple of divs that have no other content than just the table: image

Why did we do that? Well, that's how we got the table centered. You see that the outer div actually has a class called "center"; the only thing it does is set the text-align to "center", which means all of its children should be centered. However, on its own, a table just doesn't want to center itself like that. If we set the inner div with the class "wrapper" to its default display type of "block", you'll see that it takes up all the width available on the line, which is what we expect of a default div based on what we said above: image

So instead, our wrapper actually has an "inline" value (in this case, "inline-block" specifically). This makes the content take up only its actual width, which you can see by the blue highlighting in the first image being only the width of the table, whereas in the second "block" image, the highlighting also includes all the space to the right of the table. Thus, we come to our first rule of thumb: use the display value "block" when you want something to be on its own line, and "inline" if not.

That's how you control how a box interacts with its parent. What about if you want a box to control its children? That's where we get to the other display values. There are two VERY powerful display layouts called Grid and Flex. Grid is more intuitive, so let's look at it first. Basically, Grid lets you set up a series of columns and/or rows that can be spaced independently of their content:
image

In this puzzle, there are 4 equally sized columns, and instead of setting them to a concrete value like you would with a table, they are instead just set to take up 25% of the container's available space. That means they'll automatically change size as the window size changes! Grid and Flex really shine when your content isn't all of equal size (which if it were, it would be easy to just use a table). For example, if the window is narrower, the text will automatically expand onto further lines, whereas with a table, it would likely overflow.

Flex, short for "flexbox", is useful when your item size is flexible, meaning it could add extra padding or lines of text and still look fine. Flex layouts can grow or shrink all their children when the container size changes, and they can also wrap the children onto a new line if you enable wrapping and set a minimum shrink size. This means that flex layouts are most useful in one-dimensional situations, like across a single line.

Looking at another puzzle, you can see that this row of text input is set up as a flexbox: image

If we mess with this page a little and force this container to be smaller, we can see that the boxes will automatically wrap onto subsequent lines since wrapping has been enabled for this flexbox: image

Finally, one last trick to remember is that setting display to "none" will completely remove an item from a page, including the space it took up! This is different than the "visibility" property mentioned above, which blanks out an item, but maintains its space in the layout.

Javascript

At this point, you likely know enough for many basic puzzle layouts, but if you're still curious about Javascript, here's enough to get you started. The crucial thing to know about Javascript is that any code you see in a page is generally only run once, right when the page loads. That can be useful for setting up the page if you want to do something fancy like build your UI programmatically instead of manually writing HTML, but it's not that helpful for interactive pages that respond when a user does something.

To do that, you're going to want to rely on "events". Javascript has many built-in events, most of which fire in response to a specific user interaction. For example, there are events that fire when the page finishes loading, when the user clicks or scrolls with the mouse, when the user types, and even when the user prints! To make use of these events, your code will register a "listener", which is a block of code that automatically runs in response to an event firing.

To make this more useful, you will usually have specific items on your page listen for specific events. For example, if you want to do something when a user clicks a button, you would want that button specifically to listen for when it's clicked, as opposed to having the page just listen for all clicks. The following code is how you would do that:

document.addEventListener("DOMContentLoaded", populatePage);
function populatePage () {
    var myButton = document.getElementById("myButtonHasThisUniqueId");
    myButton.addEventListener("click", doButtonStuff);
    function doButtonStuff (event) {
        // Do whatever you want here when the button is clicked
    }
}

Reading through this line by line, here's what this does:

  1. The first line is run as soon as this code is loaded as part of loading the page. When this code runs, the rest of the page may not have loaded yet, so what this line is doing is registering a listener for one of the events that fires when the page is finished loading. We have to do it this way because if the page isn't done loading, then the button we want to add a listener to may not even exist yet!
  2. The rest of the code is the function that runs in response to the page load event firing, so it's NOT run at the same time as the first line. It is, however, also available to run any other time this function is called. A "function" is Javascript's equivalent to a CSS rule: it's only useful if something else makes use of it.
  3. The third line is finding our button on the page. This is Javascript's standard way of reaching into HTML: it doesn't have any automatic mapping, so any time we want to use a piece of HTML inside of Javascript, we have to search for it using a function like getElementById. We're storing the reference to our button inside a "variable" called "myButton", so if we want to do multiple things with our button, we can just use that variable next time instead of using that search function every time.
  4. This is the line that's doing the important thing: it's adding an event listener for the "click" event to our button! You'll notice that it's using the exact same function that we used to listen for our page to finish loading. In fact, this is how you can add listeners for any event to any object.
  5. This is the function that runs when our button is clicked, and you may notice one crucial difference between it and the function that runs when the page loads: there's now a variable called "event" in between the parentheses after our button event's name. That event variable contains all the information about the event that fired, for example which mouse button was used. You may not need that info depending on what you want to do, but most events will supply it if you give them a variable to put it in.

If you're still confused, don't worry; you will likely be able to do a lot without touching Javascript (or at least writing new Javascript) thanks to the tools we have available to you. To learn more about them, keep reading the rest of this page. Additionally, if you want to read official documentation on Javascript or anything else covered so far, Mozilla has great tutorials with a lot of live examples:

PuzzleJS

past event libraries examples (picture this 2023, yahtzee 2023, tic-tac-toe 2023, scene it 2023) other wiki page for repo structure

Appendix

Clone this wiki locally