Several of the grid-items on the character sheet are very similar, but the most complex of these is Traits so we’ll look closely at that one.

I won’t talk about what traits are, that’s more for the game rules (you can think of them as being a lot like fate’s aspects, though). Here we’ll discuss how they are recorded and what you need from the character sheet.
You have a number of traits equal to your Lifepaths, typically 5-10. You can use as many Traits in an adventure as you have lifepaths, but in each scene, you can use each Trait once. So, at the end of each scene, a scene button is clicked, and your trait checkboxes are cleared, but the total traits used are not.
The logic to make this work was quite tricky. The meat of the work is in sheet workers, but we need to see the HTML. The most visible part is the repeating section. Here you can add or remove traits, change their order, or whatever.
It’s a details element inside a fieldset. The ↓ span is a down arrow, which gives players an easy place t click to open the details element an enter a larger description.
<details>
<summary>
<span class="header center">↓</span>
<input type="text" name="attr_thing" value="" placeholder="THING">
<input type="checkbox" name="attr_check" value="1" class="side-check">
</summary>
<textarea name="attr_description" placeholder="DESCRIPTION"></textarea>
</details>
</fieldset>
Code language: HTML, XML (xml)
Many of the CSS classes are not necessarily needed, but it’s handy tp get in the habit of creating them in the case they are needed.
We also have a title and containing div of course.
<div class="traits grid-box">
<button type="action" name="act_show_xp" class="show-xp title">
<h2>Traits (<span name="attr_trait_bought">0</span> / <span name="attr_trait_limit"></span>)</h2>
</button>
<input type="hidden" name="attr_traits_running_total_max" class="" value="5" />
<input type="hidden" name="attr_traits_running_total" class="" value="0" />
Code language: HTML, XML (xml)
We have spans that show the current total and the maximum (see experience for their calculations), and there are also two hidden inputs – these or for the maximum number of traits and the used checkboxes. We don’t have to agonise over short names – the player never sees it.
Finally we need a set of per-session trait checkboxes – these show players how many have been used and how many remain. Notice there is a_show counterpart for each checkbox – these are used in CSS to make sure boxes above the characters total lifepaths are hidden.
<div class="trait">
<input type="hidden" name="attr_trait0_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait0" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait1_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait1" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait2_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait2" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait3_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait3" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait4_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait4" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait5_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait5" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait6_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait6" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait7_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait7" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait8_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait8" class="no-click trait" value="1" />
<input type="hidden" name="attr_trait9_show" class="no-click trait-show" value="1" />
<input type="checkbox" name="attr_trait9" class="no-click trait" value="1" />
</div>
Code language: HTML, XML (xml)
We don’t need mch CSS to make this work. We need to hide the unused trait buttons:
.trait-show:not([value="1"]) + .trait {
display:none;
}
Code language: CSS (css)
The + here means it checks only the very next html element – this is very handy as a way to avid needing many declarations.
We also need to make sure those checkboxes line up okay.
.traits div.trait {
display: grid;
grid-template-columns: repeat(10, auto);
margin-bottom: 5px;
justify-content: center;
border: 1pt solid black;
border-radius: 5pt;
}
.traits div.trait input {
margin: 1pt;
}
Code language: CSS (css)
justify-content
is doing the centering. The border stuff might be changed later, to make this section stand out more and maybe be less jarring.
To make the areas line up properly, we use a simple calc
function o the central text input:
.charsheet .container .traits summary input[type=text],
.charsheet .container .gear summary input[type=text],
.charsheet .container .extras summary input[type=text] {
width: calc(100% - 30px);
}
Code language: CSS (css)
Calc allows you to handle changes in sheet size, by, well, performing a calculation.
We do a similar thing with the normally hidden textareas, to make sure they fit their columns:
.charsheet textarea {
width: calc(100% - 10px);
height: 35px;
}
Code language: CSS (css)
This illustrates hw fiddle character sheets (and any webpage) can be – fist you make the HTML to get its structure, then you finetune the layout with lots and lots of very particular CSS.
Finally, we get on to the JavaScript sheet workers, which do the bulk of the work.
First, when the number of traits change, we need to change the number of visible checkboxes. We already have code to find the number of traits, so we just need to watch for changes.
// when #traits changes, the number you can spend each adventure changes
// the used traits is probably always 0 at this point, but just in case, we only change it if #traits goes down.
on('sheet:opened change:trait_limit', () => {
getAttrs(['trait_limit'], v => {
const traits = int(v.trait_limit);
const output = {};
output.traits_running_total_max = traits;
seq(10).forEach(i => {
output[`trait${i}_show`] = (i < traits) ? 1 : 0;
if (i >= traits) {
output[`trait${i}`] = 0;
}
});
setAttrs(output);
});
});
Code language: JavaScript (javascript)
See how we reuse the custom function seq; we now there rare up to 10 traits, so we check each to see if it should be hidden.
Then we want to look at the checkboxes on each trait. If it is checked, the total number of trtaits must be updated, and it cannot exceed the current maximum number of traits.
// each time we click a check button, the current used traits changes.
// also if used traits equals or exceeds max, all trait boxes are checked.
// for convenience you keep a running total from previous scenes.
// add number of checks to running total.
on('change:repeating_trait:check change:traits_running_total', () => {
getSectionIDs(`repeating_trait`, ids => {
const checks = ids.reduce((all, one) => [...all, `repeating_trait_${one}_check`], [])
getAttrs([...checks, 'traits_running_total', 'traits_running_total_max'], v => {
const total = int(v.traits_running_total);
const max = int(v.traits_running_total_max);
const output = {};
const sum = Math.min(max, checks.reduce((all, one) => all + int(v[one]), 0) + total);
seq(10).forEach(i => {
output[`trait${i}`] = i < sum ? 1 : 0;
});
if( sum >= max) {
checks.forEach(row => {
output[row] = 1;
});
}
log({output})
setAttrs(output);
});
});
});
Code language: JavaScript (javascript)
Anywhere on the sheet we create two buttons, one to end a scene, and one to end an adventure (or chapter). They each do different things, but are vitally important.
The scene button is for things which are per-scene. Right now, this is traits. When you click it, all checkboxes are cleared, but the total used is added, through a traits_running_total
attribute.
// when click scene button, current checks are counted and added to the hidden scene attribute, then all checks are greyed out.
on('clicked:scene', () => {
getSectionIDs('repeating_trait', ids => {
const checks = ids.reduce((all, one) => [...all, `repeating_trait_${one}_check`], [])
getAttrs(['traits_running_total', 'traits_running_total_max', ...checks], v => {
const output = {};
const scene = int(v.traits_running_total);
const max = int(v.traits_running_total_max);
const sum = Math.min(max, checks.reduce((all, one) => all + int(v[one]), 0));
output.traits_running_total = Math.min(max, scene + sum);
ids.forEach(i => {
output[`repeating_trait_${i}_check`] = scene >= (max -1) ? 1 : 0;
});
setAttrs(output);
});
});
});
Code language: JavaScript (javascript)
The chapetr button hasthe job of clearing everything used in a per chapter rate. Right now that is mostly the checkboxes in every repeating section. We lso need to clear those trait running total calculations, add the advance_enabled
hidden attribute (see experience, for what that’s for).
// when chapter button is clicked, all used items are cleareed, everything is reset
on('clicked:chapter', () => {
const output = {};
output.traits_running_total = 0;
seq(10).forEach(i => {
output[`trait${i}`] = 0;
});
output.advance_enabled = 1;
getSectionIDs('repeating_trait', traits => {
traits.forEach(i => output[`repeating_trait_${i}_check`] = 0);
getSectionIDs('repeating_things', things => {
things.forEach(i => output[`repeating_things_${i}_check`] = 0);
getSectionIDs('repeating_extras', extras => {
extras.forEach(i => output[`repeating_extras_${i}_check`] = 0);
setAttrs(output);
});
});
});
});
Code language: JavaScript (javascript)
So, with that done, and the other repeating sections essentially being simpler versions of this, we have much of the character sheet done. We have 3 main sections left to do: harm, sidebar, and the special section devoted to different iterations of Turbopulp.
We’ll start with Harm, which also covers how conflicts work.