Character Vault
Any Concept / Any System
Compendium
Your System Come To Life
Roll20 for Android
Streamlined for your Tablet
Roll20 for iPad
Streamlined for your Tablet

Personal tools

CSS Wizardry

From Roll20 Wiki

Revision as of 17:34, 11 May 2017 by Brian (Talk | contribs)

Jump to: navigation, search

Contents

Styling Checkboxes and Radio Buttons

Live Demo

Checkboxes and radio buttons don't like getting changed much. Instead, it can be easier to hide the actual checkbox/radio and put a more cooperative element underneath.

<input type="checkbox"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
<input type="radio" name="attr_r"><span></span>
/* Hide actual radio/checkbox */
input[type="radio"],
input[type="checkbox"] {
    opacity: 0;
    width: 16px;
    height: 16px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -10px;
    cursor: pointer;
    z-index: 1;
}

/* Fake radio/checkbox */
input[type="radio"] + span::before,
input[type="checkbox"] + span::before {
    margin-right: 4px;
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
}

/* Fake radio */
input[type="radio"] + span::before {
    content: "";
    width: 12px;
    height: 12px;
    font-size: 24px;
    border-radius: 50%;
}

input[type="radio"]:checked + span::before {
    content: "•";
}

/* Fake checkbox */
input[type="checkbox"] + span::before {
    content: "";
    width: 14px;
    height: 14px;
    font-size: 12px;
    border-radius: 3px;
}

input[type="checkbox"]:checked + span::before {
    content: "✓";
}

The key here is the opacity: 0; set on the actual input, and then the width, height, and content set on the span::before immediately following the input. The checkbox/radio will be on top of the span: invisible, but still clickable. When the checkbox/radio is selected, then, the style on the span::before is changed.

Note: Because of the way this is set up, if you do not have a span element immediately following your checkbox/radio button, the checkbox/radio button will not be visible.

Alternative Checkboxes

Live Demo

You're not restricted to a box with a check on it if you want a binary state (on or off). When styling your checkbox (or radio button!) you can use just about anything.

/* Fake checkbox */
input[type="checkbox"] + span::before {
    margin-right: 4px;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    content: "▼";
    width: 14px;
    height: 14px;
    font-size: 12px;
}

input[type="checkbox"]:checked + span::before {
    content: "►";
}

Now, instead of an empty box, or a box with a checkmark, you've got a right-pointing arrow or a down-pointing arrow. You can also use an image instead of a string, such as content: url(http://i.imgur.com/90HuQPr.png);

Fill Radio Buttons to the Left

Live Demo

A number of games use a set of bubbles, filled in from left to right, to represent various traits. Radio buttons can only have one selected value, however, and if we used a set of checkboxes, it would be annoying to make the user click each and every one of them to set the character's attribute. Also, a set of checkboxes would make macros extremely ugly: @{Strength_1} * 1 + @{Strength_2} * 1 + ...

However, with the radio button styling, we can solve this problem and use a radio button anyway, and only have one value.

<input type="radio" name="attr_r" value="1" checked="checked"><span></span>
<input type="radio" name="attr_r" value="2"><span></span>
<input type="radio" name="attr_r" value="3"><span></span>
<input type="radio" name="attr_r" value="4"><span></span>
<input type="radio" name="attr_r" value="5"><span></span>
/* Hide actual radio */
input[type="radio"] {
    opacity: 0;
    width: 16px;
    height: 16px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -10px;
    cursor: pointer;
    z-index: 1;
}

/* Fake radio */
input[type="radio"] + span::before {
    margin-right: 4px;
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: center;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
    content: "•";
    width: 12px;
    height: 12px;
    font-size: 24px;
    border-radius: 50%;
}

/* Remove dot from all radios _after_ selected one */
input[type="radio"]:checked ~ input[type="radio"] + span::before {    
    content: "";
}

Here, all radio buttons are styled by default to appear as though they're checked. The radio buttons after the one that's actually checked then have the dot removed. The result is that the checked radio button and all of the ones to the left are "filled in," while the ones to the left are empty. You can invert this behavior (right of the checked radio are filled, checked and left of checked are empty) by swapping the two content lines.

To reverse this behavior (checked radio and right of checked radio are filled, left of checked radio are empty), swap the two content lines and change the last selector to this:

input[type="radio"]:checked ~ input[type="radio"] + span::before,
input[type="radio"]:checked + span::before

Note: If no radio button is selected, all of them will appear filled in (or all will appear empty if you've reversed/inverted the CSS). Therefore, it is wise to include checked="checked" on one of the radio buttons. That said, you may desire a "zero" value for the trait in question. I recommend having an extra radio button with value="0" checked="checked" and without the span element immediately following it. This will give you an initial value of 0, your radio button group will appear as intended, and the "zero" value will not show up to the user.

Note: All radio buttons which are siblings will be affected by the selection of one of the radios. It is therefore recommended that you wrap the button group in some element, such as span or div.

Circular Layouts

Live Demo

Some character sheets have rather interesting layouts. Mage: the Ascension, for example, has a pair of traits called Quintessence and Paradox that are both mapped onto a wheel of checkboxes.

<div>
    <div>
        <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle sheet-left-1" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel27 sheet-mid-three-eighth sheet-left-2" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel45 sheet-mid-quarter sheet-left-3" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel63 sheet-mid-eighth sheet-left-4" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel81 sheet-top sheet-left-5" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel99 sheet-top sheet-left-6" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel117 sheet-mid-eighth sheet-left-7" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel135 sheet-mid-quarter sheet-left-8" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel153 sheet-mid-three-eighth sheet-left-9" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel171 sheet-middle sheet-left-10" value="1"><span></span>
    </div>
    <div>
        <input type="checkbox" class="sheet-wheel sheet-wheel171 sheet-middle-2 sheet-left-1" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel153 sheet-mid-five-eighth sheet-left-2" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel135 sheet-mid-three-quarter sheet-left-3" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel117 sheet-mid-seven-eighth sheet-left-4" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel99 sheet-bottom sheet-left-5" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel81 sheet-bottom sheet-left-6" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel63 sheet-mid-seven-eighth sheet-left-7" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel45 sheet-mid-three-quarter sheet-left-8" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel27 sheet-mid-five-eighth sheet-left-9" value="1"><span></span>
        <input type="checkbox" class="sheet-wheel sheet-wheel9 sheet-middle-2 sheet-left-10" value="1"><span></span>
    </div>
    <div class="sheet-marker">•</div>
</div>
/* Hide actual checkbox */
input[type="checkbox"] {
    position: absolute;
    opacity: 0;
    width: 15px;
    height: 15px;
    cursor: pointer;
    z-index: 1;
    margin-top: 6px;
}

/* Fake checkbox */
input[type="checkbox"] + span::before {
    border: solid 1px #a8a8a8;
    line-height: 14px;
    text-align: middle;
    display: inline-block;
    vertical-align: middle;
    box-shadow: 0 0 2px #ccc;
    background: #f6f6f6;
    background: radial-gradient(#f6f6f6, #dfdfdf);
    position: relative;
    content: "";
    width: 14px;
    height: 14px;
    font-size: 12px;
    border-radius: 3px;
}

/* Styles unique to fake checkbox (checked) */
input[type="checkbox"]:checked + span::before {
    content: "✓";
    color: #a00;
    box-shadow: 0 0 2px transparent;
}

/* Position checkboxes vertically in circle */
input.sheet-top  { margin-top: 5px; }
input.sheet-top + span::before { top: 0px; }
input.sheet-mid-eighth { margin-top: 12px; }
input.sheet-mid-eighth + span::before  { top: 7px; }
input.sheet-mid-quarter { margin-top: 27px; }
input.sheet-mid-quarter + span::before  { top: 22px; }
input.sheet-mid-three-eighth { margin-top: 45px; }
input.sheet-mid-three-eighth + span::before  { top: 40px; }
input.sheet-middle { margin-top: 67px; }
input.sheet-middle + span::before  { top: 62px; }
input.sheet-middle-2 { margin-top: 73px; }
input.sheet-middle-2 + span::before  { top: 68px; }
input.sheet-mid-five-eighth { margin-top: 95px; }
input.sheet-mid-five-eighth + span::before  { top: 90px; }
input.sheet-mid-three-quarter { margin-top: 113px; }
input.sheet-mid-three-quarter + span::before  { top: 108px; }
input.sheet-mid-seven-eighth { margin-top: 127px; }
input.sheet-mid-seven-eighth + span::before  { top: 122px; }
input.sheet-bottom { margin-top: 135px; }
input.sheet-bottom + span,
input.sheet-bottom + span::before  { top: 130px; }

/* Position checkboxes horizontally in circle */
input.sheet-left-1 { margin-left: 14px; }
input.sheet-left-1 + span::before  { left: 14px; }
input.sheet-left-2 { margin-left: 1px; }
input.sheet-left-2 + span::before  { left: 1px; }
input.sheet-left-3 { margin-left: -4px; }
input.sheet-left-3 + span::before  { left: -4px; }
input.sheet-left-4 { margin-left: -5px; }
input.sheet-left-4 + span::before  { left: -5px; }
input.sheet-left-5 { margin-left: -2px; }
input.sheet-left-5 + span::before  { left: -2px; }
input.sheet-left-6 { margin-left: 1px; }
input.sheet-left-6 + span::before  { left: 1px; }
input.sheet-left-7 { margin-left: 3px; }
input.sheet-left-7 + span::before  { left: 3px; }
input.sheet-left-8 { margin-left: 2px; }
input.sheet-left-8 + span::before  { left: 2px; }
input.sheet-left-9 { margin-left: -4px; }
input.sheet-left-9 + span::before  { left: -4px; }
input.sheet-left-10 { margin-left: -16px; }
input.sheet-left-10 + span::before  { left: -16px; }

/* Rotate checkboxes */
input.sheet-wheel9,
input.sheet-wheel9 + span::before { transform: rotate(9deg); }
input.sheet-wheel27,
input.sheet-wheel27 + span::before { transform: rotate(27deg); }
input.sheet-wheel45,
input.sheet-wheel45 + span::before { transform: rotate(45deg); }
input.sheet-wheel63,
input.sheet-wheel63 + span::before { transform: rotate(63deg); }
input.sheet-wheel81,
input.sheet-wheel81 + span::before { transform: rotate(81deg); }
input.sheet-wheel99,
input.sheet-wheel99 + span::before { transform: rotate(99deg); }
input.sheet-wheel117,
input.sheet-wheel117 + span::before { transform: rotate(117deg); }
input.sheet-wheel135,
input.sheet-wheel135 + span::before { transform: rotate(135deg); }
input.sheet-wheel153,
input.sheet-wheel153 + span::before { transform: rotate(153deg); }
input.sheet-wheel171,
input.sheet-wheel171 + span::before { transform: rotate(171deg); }

div.sheet-marker {
    margin: 36px 0px 0px 5px;
    font-size: 20px;
}

Styling Select Dropdowns

Live Demo

<select> elements are notoriously difficult to apply most styles to. However, using :hover pseudo-selectors and radio buttons, you can create something approximating a dropdown with whatever style you like.

<div class="sheet-container">
    <div class="sheet-child">
        <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d4" value="1" checked="true" />
        <label>d4</label>
        <input type="radio" name="attr_radio" class="sheet-select-radio sheet-d8" value="2" />
        <label>d8</label>
        <div class="sheet-d4"></div>
        <div class="sheet-d8"></div>
    </div>
</div>
.sheet-container {
    width: 280px;
}

.sheet-container,
.sheet-child {
    display: inline-block;
}

.sheet-child {
    vertical-align: middle;
    width: 35px;
    height: 35px;
}

.sheet-child input,
.sheet-child input + label {
    display: none;
    z-index: 1;
}

.sheet-child:hover {
    background: gray;
    position:absolute;
    width: 100px;
    height: auto;
    z-index: 1;
    padding: 5px;
}

.sheet-child:hover > div.sheet-d4 {
    display: none;
}

.sheet-child:hover input,
.sheet-child:hover input + label {
    display: inline;
}

.sheet-child:hover input + label {
   margin-right: 50%
}

.sheet-child:hover label {
    display: inline-block;
}

div.sheet-d4 {
    background-position: -411px -1px;
    width: 35px;
    height: 35px;
    background-image: url("https://i.imgur.com/zkgyBOi.png");
    background-repeat: no-repeat;
    color: transparent;
    display: none;
}

div.sheet-d8 {
    background-position: -703px -1px;
    width: 35px;
    height: 35px;
    background-image: url("https://i.imgur.com/zkgyBOi.png");
    background-repeat: no-repeat;
    color: transparent;
    display: none;
}

.sheet-child:not(:hover) input.sheet-select-radio.sheet-d4:checked ~ div.sheet-d4,
.sheet-child:not(:hover) input.sheet-select-radio.sheet-d8:checked ~ div.sheet-d8 {
    display: block;
}

Styling Repeating Sections

It's possible to style your repeating sections in a variety of ways. However, you can't just write your CSS as though the <fieldset> that's in your HTML source is what the user is viewing. After writing the code for your repeating section, here is how it will look when rendered to the user:

<fieldset class="repeating_my-repeating-section" style="display: none;">
    <!-- my-repeating-section HTML -->
</fieldset>
<div class="repcontainer" data-groupname="repeating_my-repeating-section">
    <div class="repitem">
        <div class="itemcontrol">
            <button class="btn btn-danger pictos repcontrol_del">#</button>
            <a class="btn repcontrol_move">≡</a>
        </div>
        <!-- my-repeating-section HTML -->
    </div>
    <!-- there will be a div.repitem for each item the user has actually added to the sheet -->
</div>
<div class="repcontrol" data-groupname="repeating_my-repeating-section">
    <button class="btn repcontrol_edit">Modify</button>
    <button class="btn repcontrol_add">+Add</button>
</div>

When you click the Modify button, the Add button is is set to display: none and the text of the Modify button is changed to "Done". When you click Done, the Add button is set to display: inline-block and the text of the Done button is changed to "Modify". While modifying repitems, the repcontainer gains the class "editmode".

Armed with this knowledge, you can do numerous things to alter how your repeating sections are displayed on the final character sheet. For example, you can have multiple repeating items per row:

.repcontainer[data-groupname="repeating_example"] > .repitem {
    display: inline-block;
}

Note: You do not prefix the rep* classes with sheet-!

Remember to use the [data-groupname="repeating_..."] attribute selector if you want to only apply the style to a single repeating section. Of course, if you want the style to affect all of your repeating sections, that's not needed.

What Can't You Do?

You cannot:

  • Change the "display" property of the original <fieldset>.
  • Change the text of the Add, Modify/Done, Delete, or Move buttons.
    • However, you could set their opacity to 0 and display something in their place, much like styling checkboxes and radios, as well as add ::before or ::after pseudo-elements to them.
  • Change the "display" property of the Add button after the user has pressed Modify once.

Counting Items

Live Demo

CSS can let you count things. This can be applied in multiple contexts for character sheets, but one option is to count how many repeating items are in your repeating section:

<fieldset class="repeating_my-repeating-section">
    <span class="sheet-counter"></span>
    <input type="text" name="attr_example">
</fieldset>
.sheet-repeating-fields {
  counter-reset: sheet-rep-items;
}

.sheet-counter::before {
  counter-increment: sheet-rep-items;
  content: counter(sheet-rep-items) '. ';
}

Hide Areas

Live Demo

You can hide areas on the character sheet based on the state of a checkbox. Instead of the adjacent sibling selector (+) used by custom checkboxes, you should use the sibling selector (~).

<div>
    <input type="checkbox" class="sheet-arrow">
    <h4>Stuff and Things</h4>
    <div class="sheet-body">
        <input type="text" placeholder="Name" name="attr_name"><br>
        <input type="text" placeholder="Description" name="attr_description">
        <input type="number" min="0" max="20" value="0" name="attr_level"><br>
        <textarea placeholder="Fulltext" name="attr_bio"></textarea>
    </div>
</div>
<div>
    <input type="checkbox" class="sheet-arrow">
    <h4>More Stuff</h4>
    <div class="sheet-body">
        <input type="text" placeholder="Name" name="attr_alternative-name"><br>
        <input type="text" placeholder="Description" name="attr_alternative-description">
        <input type="number" min="0" max="20" value="0" name="attr_alternative-level"><br>
        <textarea placeholder="Fulltext" name="attr_alternative-bio"></textarea>
    </div>
</div>
<div>
    <input type="checkbox" class="sheet-arrow">
    <h4>Other Things</h4>
    <div class="sheet-body">
        <input type="text" placeholder="Name" name="attr_class-name><br>
        <input type="text" placeholder="Description" name="attr_class-description">
        <input type="number" min="0" max="20" value="0" name="attr_class-level"><br>
        <textarea placeholder="Fulltext" name="class-bio"></textarea>
    </div>
</div>
input.sheet-arrow {
    float: left;
}

input.sheet-arrow:checked ~ div.sheet-body {
    display: none;
}

Whenever one of the checkboxes in this example is checked, the following div becomes hidden.

Swap Visible Areas

Live Demo

You can apply the hide areas logic to multiple elements based on the same checkbox, and get swappable behavior:

<div>
  <input type="checkbox" class="sheet-block-switch">
  <div class="sheet-block-a">
    Lorem ipsum dolor sit amet
  </div>
  <div class="sheet-block-b">
    consectetur adipiscing elit
  </div>
</div>
.sheet-block-a,
.sheet-block-switch:checked ~ .sheet-block-b {
  display: block;
}

.sheet-block-b,
.sheet-block-switch:checked ~ .sheet-block-a {
  display: none;
}

Tabs

Live demo

A tabbed layout is essentially an extension of hidden areas, using radio inputs instead of checkbox inputs.

<input type="radio" name="attr_tab" class="sheet-tab sheet-tab1" value="1" checked="checked"><span title="First Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab2" value="2"><span title="Second Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab3" value="3"><span title="Third Tab"></span>
<input type="radio" name="attr_tab" class="sheet-tab sheet-tab4" value="4"><span title="Fourth Tab"></span>

<div class="sheet-tab-content sheet-tab1">
    <h1>Tab 1</h1>
    Lorem ipsum dolor sit amet
</div>

<div class="sheet-tab-content sheet-tab2">
    <h1>Tab the Second</h1>
    consectetur adipisicing elit
</div>

<div class="sheet-tab-content sheet-tab3">
    <h1>3rd Tab</h1>
    sed do eiusmod tempor incididunt ut labore et dolore magna aliqua
</div>

<div class="sheet-tab-content sheet-tab4">
    <h1>Fourth Tab</h1>
    Ut enim ad minim veniam
</div>
div.sheet-tab-content { display: none; }

input.sheet-tab1:checked ~ div.sheet-tab1,
input.sheet-tab2:checked ~ div.sheet-tab2,
input.sheet-tab3:checked ~ div.sheet-tab3,
input.sheet-tab4:checked ~ div.sheet-tab4 { display: block; }

input.sheet-tab {
    width: 150px;
    height: 20px;
    position: relative;
    top: 5px;
    left: 6px;
    margin: -1.5px;
    cursor: pointer;
    z-index: 1;
    opacity: 0;
}

input.sheet-tab + span::before {
    content: attr(title);
    border: solid 1px #a8a8a8;
    border-bottom-color: black;
    text-align: center;
    display: inline-block;
    background: #fff;
    background: linear-gradient(to top, #c8c8c8, #fff, #c8c8c8);
    width: 150px;
    height: 20px;
    font-size: 18px;
    position: absolute;
    top: 12px;
    left: 13px;
}

input.sheet-tab:checked + span::before {
    background: #dcdcdc;
    background: linear-gradient(to top, #fcfcfc, #dcdcdc, #fcfcfc);
    border-bottom-color: #fff;
}

input.sheet-tab:not(:first-child) + span::before { border-left: none; }

input.sheet-tab2 + span::before {
    background: #fee;
    background: linear-gradient(to top, #f8c8c8, #fee, #f8c8c8);
    left: 163px;
}

input.sheet-tab2:checked + span::before {
    background: #dcdcdc;
    background: linear-gradient(to top, #fcecec, #f8c8c8, #fcecec);
    border-bottom-color: #fcecec;
}

input.sheet-tab3 + span::before { left: 313px; }

input.sheet-tab4 + span::before { left: 463px; }

div.sheet-tab-content {
    border: 1px solid #a8a8a8;
    border-top-color: #000;
    margin: 2px 0 0 5px;
    padding: 5px;
}

div.sheet-tab2 { background-color: #fcecec; }

The key to take away from this is that we have a set of radio buttons which are siblings to the divs that contain each tab's content. Then, we hide all of the tabs' content and use the sibling selector along with the :checked property to show the tab content associated with that particular radio button.

The rest of this example shows off means to make your tabs look pretty, such as using content: attr(title) to set the text of the tab, giving them colors and borders, and even making certain tabs different from the others in some fashion.

Hexagons

Live demo

<div class="sheet-hex sheet-hex-3" style="background-color: #444; width: 100px; height: 57px">		
    <div class="sheet-inner">
        <h4>Stat</h4>
        <input type="number" style="width: 50%">
    </div>		
    <div class="sheet-corner-1"></div>
    <div class="sheet-corner-2"></div>		
</div>
.sheet-hex {
    width: 100px;
    height: 57px;
    background-color: #ccc;
    background-repeat: no-repeat;
    background-position: 50% 50%;			
    background-size: auto 173px;							
    position: relative;
    float: left;
    margin: 25px 5px;
    text-align: center;
    zoom: 1;
}

.sheet-hex.sheet-hex-gap {
    margin-left: 86px;
}

.sheet-hex a {
    display: block;
    width: 100%;
    height: 100%;
    text-indent: -9999em;
    position: absolute;
    top: 0;
    left: 0;
}

.sheet-hex .sheet-corner-1,
.sheet-hex .sheet-corner-2 {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: inherit;								
    z-index: -2;						
    overflow: hidden;		
    backface-visibility: hidden;			
}

.sheet-hex .sheet-corner-1 {
    z-index:-1;
    transform: rotate(60deg);
}

.sheet-hex .sheet-corner-2 {
    transform: rotate(-60deg);
}

.sheet-hex .sheet-corner-1::before,
.sheet-hex .sheet-corner-2::before {
    width: 173px;
    height: 173px;
    content: '';
    position: absolute;
    background: inherit;
    top: 0;
    left: 0;
    z-index: 1;
    background: inherit;
    background-repeat: no-repeat;
    backface-visibility: hidden;				  
}

.sheet-hex .sheet-corner-1::before {
    transform: rotate(-60deg) translate(-87px, 0px);	
    transform-origin: 0 0;
}

.sheet-hex .sheet-corner-2::before {
    transform: rotate(60deg) translate(-48px, -11px);	
    bottom: 0;
}

/* Custom styles*/
.sheet-hex .sheet-inner { color: #eee; }

.sheet-hex h4 {
    font-family: 'Josefin Sans', sans-serif;		
    margin: 0;			
}

.sheet-hex hr {
    border: 0;
    border-top: 1px solid #eee;
    width: 60%;
    margin: 15px auto;
}

.sheet-hex p {
    font-size: 16px;
    font-family: 'Kotta One', serif;
    width: 80%;
    margin: 0 auto;
}

.sheet-hex.sheet-hex-1 { background: #74cddb; }
.sheet-hex.sheet-hex-2 { background: #f5c53c; }
.sheet-hex.sheet-hex-3 { background: #80b971; }

Note: Make sure the hexagon's width is 57% of the hexagon's height.

Cycling Button

Live Demo

You can't make a button that rotates through a list, changing a displayed value. However, you can fake it!

The trick lies in layering the radio buttons on top of one another, and changing the z-index based on which input is checked.

  • First, set all of the inputs to some baseline z-index.
  • Set the first radio input of the group to a z-index higher than the rest.
  • For each input, when it is checked, set the z-index of the next input to something higher than the first input. You don't need to do anything for when the last input is selected, as they'll all be back at their default z-index (which means the first one is on top).

When the first input is selected, only the second one will be visible, so you can't click on any other value. When the second is clicked, the third will become the only one you can click on, and so on. The user can also navigate back and forth using arrow keys, or a combination of the tab key (or shift+tab) and the space bar.

<div class="sheet-damage-box">
    <input type="radio" class="sheet-damage-box sheet-no-damage" name="attr_damage-box" value="0" checked>
    <input type="radio" class="sheet-damage-box sheet-bashing-damage" name="attr_damage-box" value="1">
    <input type="radio" class="sheet-damage-box sheet-lethal-damage" name="attr_damage-box" value="2">
    <input type="radio" class="sheet-damage-box sheet-aggravated-damage" name="attr_damage-box" value="3">

    <span class="sheet-damage-box sheet-no-damage">☐ (no damage)</span>
    <span class="sheet-damage-box sheet-bashing-damage">&nbsp;/&nbsp; (bashing damage)</span>
    <span class="sheet-damage-box sheet-lethal-damage">☓ (lethal damage)</span>
    <span class="sheet-damage-box sheet-aggravated-damage">✱ (aggravated damage)</span>
</div>
div.sheet-damage-box {
    width: 195px;
    height: 30px;
    position: relative;
}

input.sheet-damage-box {
    width: 30px;
    height: 30px;
    position: absolute;
    z-index: 1;
}

span.sheet-damage-box {
    margin: 10px 0 0 40px;
    display: none;
}

input.sheet-no-damage { z-index: 2; }

input.sheet-no-damage:checked + input.sheet-bashing-damage,
input.sheet-bashing-damage:checked + input.sheet-lethal-damage,
input.sheet-lethal-damage:checked + input.sheet-aggravated-damage { z-index: 3; }

input.sheet-no-damage:checked ~ span.sheet-no-damage,
input.sheet-bashing-damage:checked ~ span.sheet-bashing-damage,
input.sheet-lethal-damage:checked ~ span.sheet-lethal-damage,
input.sheet-aggravated-damage:checked ~ span.sheet-aggravated-damage { display: inline-block; }

As you can see, this uses the show/hide areas technique to display a span with some text for each radio input. You could also display an image, an input field, an entire section of the charactersheet, whatever you like.

One fancy option would be to combine this with the technique to style your radio buttons. Hide the radios with opacity: 0, and display an image in the same location as the radio button (make sure the image is at a lower z-index than the invisible buttons!) so that the user is apparently clicking on the displayed image to change it.

Icon Fonts

An icon font is a font which has pictures instead of letters. You can specify one of the icon fonts below with the font-family property. For example, something like:

<p>A Gem: <span style="font-family: 'Pictos Three'">a</span></p>

Would produce:

A Gem: a

Pictos

Character Icon Character Icon Character Icon Character Icon
 !  !  :  : S S l l
" "  ;  ; T T m m
# # < < U U n n
$ $ = = V V o o
 %  % > > W W p p
& &  ?  ? X X q q
' ' @ @ Y Y r r
( ( A A Z Z s s
) ) B B [ [ t t
* * C C \ \ u u
+ + D D ] ] v v
, , E E ^ ^ w w
- - F F _ _ x x
. . G G ` ` y y
/ / H H a a z z
0 0 I I b b { {
1 1 J J c c | |
2 2 K K d d } }
3 3 L L e e ~ ~
4 4 M M f f
5 5 N N g g
6 6 O O h h
7 7 P P i i
8 8 Q Q j j
9 9 R R k k

Pictos Custom

Character Icon
[ [
a a
e e
i i
o o
p p
q q
r r
t t
u u
w w
y y

Pictos Three

Character Icon
a a
b b
c c
d d
e e
f f
g g
h h
i i
j j
k k
l l

dicefontd4

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O
P P

dicefontd6

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q
R R

dicefontd8

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G
H H

dicefontd10

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q s s
R R t t
S S
T T

dicefontd12

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K
L L

dicefontd20

Character Icon Character Icon
0 0 a a
@ @ b b
A A c c
B B d d
C C e e
D D f f
E E g g
F F h h
G G i i
H H j j
I I k k
J J l l
K K m m
L L n n
M M o o
N N p p
O O q q
P P r r
Q Q s s
R R t t
S S
T T

dicefontd30

Character Icon Character Icon Character Icon
0 0 L L g g
1 1 M M h h
2 2 N N i i
3 3 O O j j
4 4 P P k k
5 5 Q Q l l
6 6 R R m m
7 7 S S n n
8 8 T T o o
@ @ U U p p
A A V V q q
B B W W r r
C C X X s s
D D Y Y t t
E E Z Z u u
F F a a v v
G G b b w w
H H c c x x
I I d d y y
J J e e z z
K K f f

fontello

Character Icon
&#xe800;
&#xe801;
&#xe802;
&#xe803;
&#xe804;
&#xe805;
&#xe806;
&#xe807;
&#xe808;
&#xe809;
&#xe80a;
&#xe80b;
&#xe80c;
&#xe80d;
&#xe80e;
&#xe80f;
&#xe810;
&#xe811;
&#xe812;
&#xe813;
&#xf008;

Styling Roll Buttons

Want a roll button that doesn't have a d20 image in it? Simple!

button[type=roll].sheet-blank-roll-button::before { content: ''; }

The d20 is a single character with the dicefontd20 font-family in the button's ::before pseudo-element. Setting the content to an empty string removes it. If you want to put something other than a d20 in its place, you'll have to change the font-family as well.

Replicating the JavaScript Math Library

Note: With the advent of Sheet Worker Scripts, the value of these tricks is diminished, as you can use the Math library directly with a sheet worker. However, they are kept here for legacy purposes, and because they still work.

It is possible to replicate most of the functionality of JavaScript's Math library with autocalc fields. While this isn't technically CSS Wizardry, it is included in this article for ease of discovery. Functions with an asterisk (*) produce approximate results; you can increase their accuracy by adding iterations to the computation.

Some functions below may reference other functions below.

Constants

The following are constants in JavaScript, so there's no reason to not have them as constants if you happen to need them.

E

E is Euler's constant, the base for the natural logarithm. The exact value is Sum[1 / n!, {n, 0, Infinity}]. Math.log(Math.E) is 1.

<input type="hidden" name="attr_cE" value="2.718281828459045">

LN2

LN2 is the value of Math.log(2).

<input type="hidden" name="attr_cLN2" value="0.6931471805599453">

LN10

LN10 is the value of Math.log(10).

<input type="hidden" name="attr_cLN10" value="2.302585092994046">

LOG2E

LOG2E is the base-2 logarithm of E. In other words, the value of Math.log2(Math.E), or 1 / Math.log(2).

<input type="hidden" name="attr_cLOG2E" value="1.4426950408889634">

LOG10E

LOG10E is the base-10 logarithm of E. In other words, the value of Math.log10(Math.E), or 1 / Math.log(10).

<input type="hidden" name="attr_cLOG10E" value="0.4342944819032518">

PI

PI is the radio between the circumference and diameter of a circle.

<input type="hidden" name="attr_cPI" value="3.141592653589793">

SQRT1_2

SQRT1_2 is the square root of 1/2 (0.5).

<input type="hidden" name="attr_cSQRT1_2" value="0.7071067811865476">

SQRT2

SQRT2 is the square root of 2.

<input type="hidden" name="attr_cSQRT2" value="1.4142135623730951">

Trivially Represented

The following functions you can use in autocalc fields easily, either because they're simple or because they're directly available.

abs(x)

If x < 0, the result is -1 * x. Otherwise, the result is x.

<input type="hidden" name="attr_abs" value="abs(@{x})" disabled>

cbrt(x)

Cube root: cbrt(x)^3 = x.

<input type="hidden" name="attr_cbrt" value="(@{x}**(1/3))" disabled>

ceil(x)

Rounds towards positive infinity.

<input type="hidden" name="attr_ceil" value="ceil(@{x})" disabled>

exp(x)

E^x.

<input type="hidden" name="attr_exp" value="(@{cE}**@{x})" disabled>

floor(x)

Rounds towards negative infinity.

<input type="hidden" name="attr_floor" value="floor(@{x})" disabled>

hypot(x, y, z, ...)

Hypotenuse: sqrt(x^2 + y^2 + z^2 + ...).

<!-- include as many values as you need -->
<input type="hidden" name="attr_hypot" value="((@{x}**2 + @{y}**2 + @{z}**2)**0.5)" disabled>

max(x, y, z, ...)

If x > y, the result is x, while if x < y, the result is y.

<input type="hidden" name="attr_max_xy" value="(((@{x} + @{y}) + abs(@{x} - @{y})) / 2)" disabled>
<input type="hidden" name="attr_max_xyz" value="(((@{max_xy} + @{z}) + abs(@{max_xy} - @{z})) / 2)" disabled>
<input type="hidden" name="attr_max_xyzw" value="(((@{max_xyz} + @{w}) + abs(@{max_xyz} - @{w})) / 2)" disabled>

min(x, y, z, ...)

If x > y, the result is y, while if x < y, the result is x.

<input type="hidden" name="attr_min_xy" value="(((@{x} + @{y}) - abs(@{x} - @{y})) / 2)" disabled>
<input type="hidden" name="attr_min_xyz" value="(((@{min_xy} + @{z}) - abs(@{min_xy} - @{z})) / 2)" disabled>
<input type="hidden" name="attr_min_xyzw" value="(((@{min_xyz} + @{w}) - abs(@{min_xyz} - @{w})) / 2)" disabled>

pow(x, y)

x^y, this is x multiplied by itself y times; fractional values for y are permissible.

<input type="number" name="attr_pow" value="(@{x}**@{y})" disabled>

round(x)

Round towards the nearest integer. If the fractional part is 0.5, it will round towards positive infinity.

<input type="hidden" name="attr_round" value="round(@{x})" disabled>

sign(x)

If x < 0, the result is -1, while if x > 0, the result is 1 and if x = 0, the result is 0. Unfortunately, because of the nature of the calculation, we cannot correctly calculate the final possibility. This works for all values of x other than 0, however.

<input type="hidden" name="attr_sign" value="(@{x} / abs(@{x}))" disabled>

sqrt(x)

Square root: sqrt(x)^2 = x.

<input type="hidden" name="attr_sqrt" value="(@{x}**0.5)" disabled>

trunc(x)

Round towards 0.

<input type="hidden" name="attr_trunc" value="(@{sign} * floor(abs(@{x})))" disabled>

Trigonometric Functions

Trigonometric functions deal with angles and the relations between the sides of a triangle.

*acos(x)

Inverse function of cos; acos(cos(x)) = x, and if abs(x) <= 1, cos(acos(x)) = x.

<input type="hidden" name="attr_acos" value="(@{cPI} / 2 - @{asin})" disabled>

*acosh(x)

Hyperbolic arccosine.

Note: See log(x) for the definition of @{log_2x}

<input type="hidden" name="attr_acosh" value="(@{log_2x} - (1 / (4 * @{x}**2) + 3 / (32 * @{x}**4) + 15 / (288 * @{x}**6) + 105 / (384 * @{x}**8)))" disabled>

*asin(x)

Inverse function of sin; asin(sin(x)) = x, and if abs(x) <= 1, sin(asin(x)) = x.

<input type="hidden" name="attr_asin" value="(@{x} + @{x}**3 / 6 + (3 * @{x}**5) / 40 + (15 * @{x}**7) / 336)" disabled>

*asinh(x)

Hyperbolic arcsine.

<input type="hidden" name="attr_asinh" value="(@{x} - @{x}**3 / 6 + 3 * @{x}**5 / 40 - 15 * @{x}**7 / 336)" disabled>

*atan(x)

Inverse function of tan; atan(tan(x)) = x = tan(atan(x)).

<input type="hidden" name="attr_atan" value="(@{x} - @{x}**3 / 3 + @{x}**5 / 5 - @{x}**7 / 7)" disabled>

*atanh(x)

Hyperbolic arctangent.

<input type="hidden" name="attr_atan" value="(@{x} + @{x}**3 / 3 + @{x}**5 / 5 + @{x}**7 / 7)" disabled>

*atan2(y, x)

The same as atan(y / x), although the actual Math library function gracefully handles x = 0.

<input type="hidden" name="attr_atan2" value="((@{y} / @{x}) - (@{y} / @{x})**3 / 3 + (@{y} / @{x})**5 / 5 - (@{y} / @{x})**7 / 7)" disabled>

*cos(x)

The ratio of the adjacent side of a triganle from the given angle (on a right triangle) to the hypotenuse.

<input type="hidden" name="attr_cos" value="(1 - @{x}**2 / 2 + @{x}**4 / 24 - @{x}**6 / 720 + @{x}**8 / 40320)" disabled>

*cosh(x)

Hyperbolic cosine.

<input type="hidden" name="attr_cosh" value="(1 + @{x}**2 / 2 + @{x}**4 / 24 + @{x}**6 / 720 + @{x}**8 / 40320)" disabled>

*sin(x)

The ratio of the opposite side of a triangle from the given angle (on a right triangle) to the hypotenuse.

<input type="hidden" name="attr_sin" value="(@{x} - @{x}**3 / 6 + @{x}**5 / 120 - @{x}**7 / 5040)" disabled>

*sinh(x)

Hyperbolic sine.

<input type="hidden" name="attr_sinh" value="(@{x} + @{x}**3 / 6 + @{x}**5 / 120 + @{x}**7 / 5040)" disabled>

*tan(x)

The ratio of the opposite side of a triangle from the given angle (on a right triangle) to the adjacent side.

<input type="hidden" name="attr_tan" value="(@{sin} / @{cos})" disabled>

*tanh(x)

Hyperbolic tangent.

<input type="hidden" name="attr_tanh" value="(@{sinh} / @{cosh})" disabled>

Other Transcendental Functions

Transcendental functions cannot be expressed with a finite number of algebraic operations. The trigonometric functions are transcendental as well.

*log(x)

Natural logarithm (log base E) of x.

<input type="hidden" name="attr_log" value="(@{x} - @{x}**2 / 2 + @{x}**3 / 3 - @{x}**4 / 4 + @{x}**5 / 5 - @{x}**6 / 6 + @{x}**7 / 7)" disabled>
<!-- used for acosh, above -->
<input type="hidden" name="attr_log_2x" value="(@{cLN2} + @{log})" disabled>

*log1p(x)

log(1 + x)

<input type="hidden" name="attr_log1p" value="((1 + @{x}) - (1 + @{x})**2 / 2 + (1 + @{x})**3 / 3 - (1 + @{x})**4 / 4 + (1 + @{x})**5 / 5 - (1 + @{x})**6 / 6 + (1 + @{x})**7 / 7)" disabled>

*log10(x)

Log base 10 of x.

<input type="hidden" name="attr_log10" value="(@{log} / @{cLN10})" disabled>

*log2(x)

Log base 2 of x.

<input type="hidden" name="attr_log2" value="(@{log} / @{cLN2})" disabled>

Other Functions

The following functions don't really have a mathematical categorization.

*clz32(x)

The number of leading zero bits in a 32-bit number. For example, the number 64 is represented in binary as 00000000000000000000000001000000, so clz32(64) = 25.

<input type="hidden" name="attr_clz32" value="(32 - ceil(@{log1p} / @{cLN2}))" disabled>

Impossible to Implement

Some functions cannot be implemented with autocalc fields.

fround(x)

Rounds a number to the nearest 32-bit representable floating point number.

imul(x, y)

Perform 32-bit multiplication. This is functionally equivalent to trunc(x * y), except when large numbers get involved.

random()

Generate a random number in the range [0, 1). While this can't be done with autocalc fields, you can roll dice!