init commit
commit
e4f6d674df
|
@ -0,0 +1,524 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width">
|
||||
<title>abenteuerspiel character keeper</title>
|
||||
<style type="text/css" media="screen">
|
||||
:root {
|
||||
--header-color: green;
|
||||
--link-color: rgb(244,80,83);
|
||||
}
|
||||
body {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 1rem;
|
||||
background-color: rgb(254,247,223);
|
||||
}
|
||||
main {
|
||||
display: flex;
|
||||
gap: 1em;
|
||||
}
|
||||
@media only screen and (max-width: 600px) {
|
||||
main {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 50%;
|
||||
}
|
||||
label {
|
||||
text-transform: capitalize;
|
||||
margin-top: 1em;
|
||||
}
|
||||
summary {
|
||||
font-size: 2rem;
|
||||
margin: 1rem 0;
|
||||
color: var(--header-color);
|
||||
}
|
||||
summary.small {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
.gather {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.gather label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
button {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
h1, h2, h3 {
|
||||
color: var(--header-color);
|
||||
}
|
||||
a {
|
||||
color: var(--link-color);
|
||||
}
|
||||
abbr[title] {
|
||||
position: relative;
|
||||
}
|
||||
abbr[title]:hover::after,
|
||||
abbr[title]:focus::after {
|
||||
cursor: help;
|
||||
content: attr(title);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -30px;
|
||||
max-width: 60ch;
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 1px 5px 0 rgba(0,0,0,0.4);
|
||||
padding: 3px 5px;
|
||||
background: wheat;
|
||||
}
|
||||
footer {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Abenteuerspiel!</h1>
|
||||
</header>
|
||||
<main>
|
||||
<details open>
|
||||
<summary>
|
||||
Character
|
||||
</summary>
|
||||
|
||||
<div class="characterOptions">
|
||||
<a href="#" class="generateCharacter">Generate character</a>
|
||||
</div>
|
||||
|
||||
<label for="name">name</label>
|
||||
<input type="text" name="name" id="name" value="" />
|
||||
|
||||
<label for="conditions">conditions</label>
|
||||
<textarea name="conditions" rows="8" cols="40"></textarea>
|
||||
|
||||
<label for="skills">skills</label>
|
||||
<textarea name="skills" rows="8" cols="40"></textarea>
|
||||
<a href="#" class="generateSkills">Generate skills</a>
|
||||
|
||||
<label for="equipment">equipment</label>
|
||||
<textarea name="equipment" rows="8" cols="40"></textarea>
|
||||
<a href="#" class="generateEquipment">Generate equipment</a>
|
||||
|
||||
<label for="rituals">rituals</label>
|
||||
<textarea name="rituals" rows="8" cols="40"></textarea>
|
||||
<div>
|
||||
<a href="#" class="generateRitual">Generate ritual</a>
|
||||
<label for="chaoticRitual">enable chaos?</label>
|
||||
<input type="checkbox" name="chaoticRitual" id="chaoticRitual">
|
||||
<sup><abbr tabindex="0" title="Pick a random verb-noun combination from the list of available rituals.">?</abbr></sup>
|
||||
</div>
|
||||
</details>
|
||||
<details open>
|
||||
<summary>Risky Action</summary>
|
||||
<div class="actionOptions">
|
||||
<a href="#" class="rest">🛌 Rest</a> |
|
||||
<a href="#" class="tempt">⚠️ Tempt Fate</a> |
|
||||
<a href="#" class="orakel">🔮 Orakel</a>
|
||||
</div>
|
||||
<div class="pool">
|
||||
<p>Ready Pool: <span class="readyPool"><span></p>
|
||||
<p>Exhausted Pool: <span class="exhaustedPool"><span></p>
|
||||
</div>
|
||||
<details open>
|
||||
<summary class="small">Gather your dice</summary>
|
||||
<div class="gather">
|
||||
<label for="devil"><span>devil's bargain<sup><abbr tabindex="0" title="Gain an extra die in exchange for some kind of hardship that happens regardless of the outcome.">?</abbr></sup></span>
|
||||
<input type="checkbox" name="devil" id="devil">
|
||||
</label>
|
||||
<label for="stressed">stressed
|
||||
<input class="gatherable" type="checkbox" name="stressed" id="stressed" checked disabled>
|
||||
</label>
|
||||
<label for="unconditioned">unconditioned
|
||||
<input class="gatherable selectable" type="checkbox" name="unconditioned" id="unconditioned">
|
||||
</label>
|
||||
<label for="skilled">skilled
|
||||
<input class="gatherable selectable" type="checkbox" name="skilled" id="skilled">
|
||||
</label>
|
||||
<label for="equipped">equipped
|
||||
<input class="gatherable selectable" type="checkbox" name="equipped" id="equipped">
|
||||
</label>
|
||||
<label for="supported">supported
|
||||
<input class="gatherable selectable" type="checkbox" name="supported" id="supported">
|
||||
</label>
|
||||
<label for="advantaged">advantaged
|
||||
<input class="gatherable selectable" type="checkbox" name="advantaged" id="advantaged">
|
||||
</label>
|
||||
</div>
|
||||
<button class="roll">Roll</button>
|
||||
</details>
|
||||
<div class="outcome">
|
||||
<h3>Outcome</h3>
|
||||
<p class="outcomeOutput">...</p>
|
||||
</div>
|
||||
</details>
|
||||
</main>
|
||||
<footer><small><a href="https://terriblybeautiful.itch.io/abenteuerspiel">Abenteuerspiel! is by Terribly Beautiful</a></small></footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script charset="utf-8">
|
||||
const random = (max) => Math.floor(Math.random() * max);
|
||||
const rituals = [
|
||||
[
|
||||
"become",
|
||||
"breathe",
|
||||
"charm",
|
||||
"compel",
|
||||
"conjure",
|
||||
"control",
|
||||
"create",
|
||||
"detect",
|
||||
"duplicate",
|
||||
"exploit",
|
||||
"follow",
|
||||
"generate",
|
||||
"hasten",
|
||||
"heal",
|
||||
"hold",
|
||||
"induce",
|
||||
"invoke",
|
||||
"levitate",
|
||||
"make",
|
||||
"manipulate",
|
||||
"neutralize",
|
||||
"observe",
|
||||
"open",
|
||||
"parse",
|
||||
"produce",
|
||||
"protect",
|
||||
"read",
|
||||
"reverse",
|
||||
"shoot",
|
||||
"slow",
|
||||
"spawn",
|
||||
"summon",
|
||||
"superior",
|
||||
"swap",
|
||||
"transform",
|
||||
"traverse",
|
||||
],
|
||||
[
|
||||
"beauty",
|
||||
"water",
|
||||
"creature",
|
||||
"animal",
|
||||
"wind",
|
||||
"plants",
|
||||
"dark",
|
||||
"magic",
|
||||
"self",
|
||||
"flaw",
|
||||
"thread",
|
||||
"illusion",
|
||||
"time",
|
||||
"entity",
|
||||
"person",
|
||||
"sleep",
|
||||
"light",
|
||||
"thing",
|
||||
"invisible",
|
||||
"earth",
|
||||
"ritual",
|
||||
"location",
|
||||
"lock",
|
||||
"symbol",
|
||||
"web",
|
||||
"area",
|
||||
"mind",
|
||||
"gravity",
|
||||
"lightning",
|
||||
"movement",
|
||||
"fire",
|
||||
"spirit",
|
||||
"senses",
|
||||
"bodies",
|
||||
"beast",
|
||||
"wall"
|
||||
]
|
||||
]
|
||||
const equipment = [
|
||||
'animal traps(3)',
|
||||
'bandages (3)',
|
||||
'bone dust (3)',
|
||||
'bow & arrows (12)',
|
||||
'caltrops (9)',
|
||||
'chalk (6)',
|
||||
'collapsible pole',
|
||||
'crowbar',
|
||||
'dagger',
|
||||
'dice (6)',
|
||||
'fire oil (3)',
|
||||
'flint & steel (3)',
|
||||
'fools gold (6)',
|
||||
'grappling hook',
|
||||
'holy symbol',
|
||||
'iron spikes (6)',
|
||||
'lantern & oil (3)',
|
||||
'light armor',
|
||||
'lock-picks (6)',
|
||||
'marbles (12)',
|
||||
'mule',
|
||||
'pen & parchment',
|
||||
'pot of honey (3)',
|
||||
'quicksilver',
|
||||
'ritual incense (3)',
|
||||
'rope (60 ft)',
|
||||
'shield',
|
||||
'skeleton key (1)',
|
||||
'sling & stones (12)',
|
||||
'spyglass',
|
||||
'steel mirror',
|
||||
'sword',
|
||||
'tent',
|
||||
'torches (12)',
|
||||
'travel rations (3)',
|
||||
'twine (300 ft)',
|
||||
]
|
||||
const skills = [
|
||||
'alchemy',
|
||||
'artifacts',
|
||||
'athletics',
|
||||
'bartering',
|
||||
'beasts',
|
||||
'craft',
|
||||
'deception',
|
||||
'destruction',
|
||||
'dexterity',
|
||||
'forensics',
|
||||
'improvisation',
|
||||
'intimidation',
|
||||
'lairs',
|
||||
'mimicry',
|
||||
'myths',
|
||||
'obfuscation',
|
||||
'performance',
|
||||
'persistance',
|
||||
'plants',
|
||||
'protection',
|
||||
'rituals',
|
||||
'secrets',
|
||||
'security',
|
||||
'speed',
|
||||
'spontaneity',
|
||||
'stealth',
|
||||
'strength',
|
||||
'surgery',
|
||||
'surprise',
|
||||
'symbols',
|
||||
'tactics',
|
||||
'tracking',
|
||||
'traps',
|
||||
'trickery',
|
||||
'vigilance',
|
||||
'weapons',
|
||||
]
|
||||
|
||||
|
||||
const generateRitual = (evt) => {
|
||||
evt?.preventDefault();
|
||||
const isRitualChaos = () => document.querySelector('input#chaoticRitual').checked
|
||||
const out = document.querySelector('textarea[name=rituals]')
|
||||
out.value = ''
|
||||
const result = Array.from({length: 1}).map(ritual => {
|
||||
const i = random(rituals[0].length)
|
||||
const j = (isRitualChaos()) ? random(rituals[0].length) : i
|
||||
return `${rituals[0][i]} ${rituals[1][j]}`
|
||||
}).forEach(ritual => {
|
||||
out.value += ritual + '\n'
|
||||
})
|
||||
}
|
||||
document.querySelector('.generateRitual').addEventListener('click', generateRitual)
|
||||
|
||||
|
||||
const generateEquipment = (evt) => {
|
||||
evt?.preventDefault();
|
||||
const out = document.querySelector('textarea[name=equipment]')
|
||||
out.value = ''
|
||||
const result = Array.from({length: 3}).map(eq => {
|
||||
const i = random(equipment.length)
|
||||
return `${equipment[i]}`
|
||||
}).forEach(eq => {
|
||||
out.value += eq + '\n'
|
||||
})
|
||||
}
|
||||
document.querySelector('.generateEquipment').addEventListener('click', generateEquipment)
|
||||
|
||||
|
||||
const generateSkills = (evt) => {
|
||||
evt?.preventDefault();
|
||||
const out = document.querySelector('textarea[name=skills]')
|
||||
out.value = ''
|
||||
const result = Array.from({length: 2}).map(skill => {
|
||||
const i = random(skills.length)
|
||||
return `${skills[i]}`
|
||||
}).forEach(skill => {
|
||||
out.value += skill + '\n'
|
||||
})
|
||||
}
|
||||
document.querySelector('.generateSkills').addEventListener('click', generateSkills)
|
||||
|
||||
|
||||
const generateCharacter = (evt) => {
|
||||
evt.preventDefault()
|
||||
generateRitual()
|
||||
generateSkills()
|
||||
generateEquipment()
|
||||
}
|
||||
document.querySelector('.generateCharacter').addEventListener('click', generateCharacter)
|
||||
|
||||
|
||||
let dice = {
|
||||
'ready': 6,
|
||||
'exhausted': 0,
|
||||
}
|
||||
|
||||
|
||||
const showDice = () => {
|
||||
const readyOut = document.querySelector('.readyPool')
|
||||
const exhaustedOut = document.querySelector('.exhaustedPool')
|
||||
readyOut.innerText = ''
|
||||
exhaustedOut.innerText = ''
|
||||
|
||||
for(let i = 0; i < dice.ready; i++) {
|
||||
readyOut.innerText += '🎲'
|
||||
}
|
||||
for(let i = 0; i < dice.exhausted; i++) {
|
||||
exhaustedOut.innerText += '🎲'
|
||||
}
|
||||
}
|
||||
showDice()
|
||||
|
||||
|
||||
const rollDice = (evt) => {
|
||||
const pool = Array.from({length: poolSize()}).map(x => random(6) + 1)
|
||||
const high = Math.max(...pool)
|
||||
const stress = pool[0]
|
||||
const isStressful = handleStress({
|
||||
ready: dice.ready,
|
||||
stress: stress,
|
||||
})
|
||||
handleRiskOutput({
|
||||
high,
|
||||
pool,
|
||||
stress: isStressful,
|
||||
})
|
||||
|
||||
// clear gatherable selectables
|
||||
Array.from(document.querySelectorAll('.gather .selectable'))
|
||||
.forEach(input => {
|
||||
input.checked = false;
|
||||
input.disabled = false;
|
||||
})
|
||||
// ... and the devils bargin
|
||||
document.querySelector('input#devil').checked = false
|
||||
|
||||
}
|
||||
const poolSize = () => Array.from(document.querySelectorAll('.gather input'))
|
||||
.filter(el => el.checked)
|
||||
.length
|
||||
const handleStress = ({ ready, stress }) => {
|
||||
const isStressful = stress < ready;
|
||||
if(isStressful) {
|
||||
dice.ready -= 1;
|
||||
dice.exhausted += 1;
|
||||
}
|
||||
showDice()
|
||||
return isStressful;
|
||||
}
|
||||
const handleRiskOutput = ({ high, pool, stress }) => {
|
||||
const out = document.querySelector('.outcomeOutput')
|
||||
out.innerText = ''
|
||||
out.innerText += 'Roll: '
|
||||
out.innerText += ' ' + pool.join(', ')
|
||||
out.innerText += '\nHighest: '
|
||||
out.innerText += ' ' + high + ' = '
|
||||
out.innerText += (high < 4)
|
||||
? ' Bad. Things get worse. Take a condition.'
|
||||
: (high < 6)
|
||||
? ' Mixed. Partial success, or success with complication'
|
||||
: ' Success!'
|
||||
if(stress) {
|
||||
out.innerText += '\nYou exhaust one of your Ready Dice.'
|
||||
out.innerHTML += '<sup><abbr tabindex="0" title="the first die in your pool is always your stress die. If its value is less than your number of ready dice, one of your ready dice is exhausted.">?</abbr></sup>'
|
||||
}
|
||||
}
|
||||
document.querySelector('button.roll').addEventListener('click', rollDice);
|
||||
|
||||
|
||||
|
||||
const checkPoolSize = (evt) => {
|
||||
const gathered = Array.from(document.querySelectorAll('.gather .gatherable'))
|
||||
.filter(item => item.checked)
|
||||
.length
|
||||
Array.from(document.querySelectorAll('.selectable:not(:checked)'))
|
||||
.forEach(box => {
|
||||
box.disabled = (gathered === dice.ready)
|
||||
})
|
||||
}
|
||||
Array.from(document.querySelectorAll('.gather .selectable'))
|
||||
.forEach(input => {
|
||||
input.addEventListener('change', checkPoolSize)
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const rest = (evt) => {
|
||||
evt.preventDefault()
|
||||
let recovered = 0;
|
||||
Array.from({length: dice.exhausted})
|
||||
.map(exhausted => random(6) + 1)
|
||||
.forEach(exhausted => {
|
||||
if (exhausted >= 4) {
|
||||
dice.ready += 1;
|
||||
dice.exhausted -= 1;
|
||||
recovered += 1
|
||||
}
|
||||
})
|
||||
handleRestOutput(recovered)
|
||||
showDice()
|
||||
}
|
||||
handleRestOutput = (recovered) => {
|
||||
const out = document.querySelector('.outcomeOutput')
|
||||
out.innerText = ''
|
||||
out.innerText += `You rested and recovered ${recovered} dice`
|
||||
}
|
||||
document.querySelector('.rest').addEventListener('click', rest);
|
||||
|
||||
|
||||
|
||||
const tempt = (evt) => {
|
||||
const fate = random(6) + 1
|
||||
const out = document.querySelector('.outcomeOutput')
|
||||
out.innerText = `${fate}: `
|
||||
out.innerText += `You have tempted fate, and made things incrementally ${fate < 4 ? 'worse' : 'better'}.`
|
||||
if (fate < dice.ready) {
|
||||
out.innerText += `You also suffer stress.`
|
||||
}
|
||||
handleStress({
|
||||
ready: dice.ready,
|
||||
stress: fate,
|
||||
})
|
||||
}
|
||||
document.querySelector('.tempt').addEventListener('click', tempt);
|
||||
|
||||
|
||||
|
||||
const orakel = (evt) => {
|
||||
const fortune = random(6) + 1
|
||||
const out = document.querySelector('.outcomeOutput')
|
||||
out.innerText = ''
|
||||
out.innerText += `${fortune < 4 ? '🚫' : '✅'} ${fortune}: The orakel says, signs point to ${fortune < 4 ? 'no' : 'yes'}.`
|
||||
}
|
||||
document.querySelector('.orakel').addEventListener('click', orakel);
|
||||
</script>
|
Loading…
Reference in New Issue