Files
fabula-ultima-html/fabula-ultima-sheet.html

691 lines
24 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fabula Ultima — Character Sheet</title>
<link
href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;600;700&family=Crimson+Text:ital,wght@0,400;0,600;1,400&family=Inconsolata:wght@400;600&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="fabula-ultima-sheet.css" />
<script>
(function () {
var t =
localStorage.getItem("fabulaUltimaTheme") ||
(window.matchMedia("(prefers-color-scheme: light)").matches
? "light"
: "dark");
document.documentElement.dataset.theme = t;
})();
</script>
</head>
<body>
<header>
<div class="logo">Fabula Ultima</div>
<div class="tabs">
<button class="tab active" onclick="switchTab('main')">
Character
</button>
<button class="tab" onclick="switchTab('classes')">Classes</button>
<button class="tab" onclick="switchTab('spells')">
Arcana & Spells
</button>
<button class="tab" onclick="switchTab('manage')">Manage</button>
</div>
<div class="toolbar">
<span class="save-status" id="saveStatus">Saved!</span>
<button class="btn-theme" id="themeToggle" onclick="toggleTheme()">
☀ Light
</button>
</div>
</header>
<div id="urlBanner" class="url-banner" style="display: none">
<span class="url-banner-icon"></span>
Viewing a shared character — auto-save is disabled. Use
<strong>Manage → Save to Browser</strong> to keep any changes.
</div>
<!-- ══════════════════════════════════════════════════
PAGE 1: MAIN CHARACTER
══════════════════════════════════════════════════ -->
<div class="page active" id="page-main">
<!-- Row 1: Identity + Level -->
<div class="grid-2" style="margin-bottom: 20px">
<!-- Identity -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Identity & Traits
</div>
<div class="field-row">
<div class="field" style="flex: 2">
<label>Name</label>
<input type="text" id="charName" placeholder="Character name…" />
</div>
<div class="field">
<label>Pronouns</label>
<input type="text" id="charPronouns" placeholder="they/them" />
</div>
</div>
<div class="field">
<label>Identity</label>
<input type="text" id="charIdentity" placeholder="Who are you?" />
</div>
<div class="field-row">
<div class="field">
<label>Theme</label>
<input type="text" id="charTheme" placeholder="Your theme…" />
</div>
<div class="field">
<label>Origin</label>
<input type="text" id="charOrigin" placeholder="Where from?" />
</div>
</div>
<div class="field">
<label>Traits (comma-separated)</label>
<textarea
id="charTraits"
placeholder="Brave, Reckless, Loyal to a fault…"
style="min-height: 55px"
></textarea>
</div>
</div>
<!-- Level + XP + Defenses -->
<div style="display: flex; flex-direction: column; gap: 16px">
<div class="section">
<div class="section-title">
<span class="icon"></span> Level & Experience
</div>
<div class="grid-2" style="gap: 14px">
<div class="level-display">
<span class="level-num" id="levelDisplay">1</span>
<span class="level-text">Character Level</span>
<button
class="add-btn"
style="margin-top: 10px; width: 100%; justify-content: center"
onclick="adjustLevel(1)"
>
+ Level Up
</button>
<button
class="add-btn"
style="
margin-top: 4px;
width: 100%;
justify-content: center;
border-color: var(--border-bright);
color: var(--text-dim);
"
onclick="adjustLevel(-1)"
>
Level Down
</button>
</div>
<div>
<div class="field">
<label>Experience Points (XP)</label>
<input
type="number"
id="xpCurrent"
value="0"
min="0"
oninput="updateXPBar()"
/>
</div>
<div class="xp-bar-wrap">
<div class="xp-bar" id="xpBar" style="width: 0%"></div>
</div>
<div class="xp-label">
<span id="xpVal">0 XP</span>
<span>10 XP = Level</span>
</div>
<div class="field" style="margin-top: 12px">
<label>Zenit (currency)</label>
<input type="number" id="zenit" value="0" min="0" />
</div>
</div>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="icon"></span> Defenses
</div>
<div class="def-row">
<div class="def-block">
<label>Initiative Mod</label>
<input type="number" id="initMod" value="0" />
</div>
<div class="def-block">
<label>Defense</label>
<input type="number" id="defense" value="0" />
</div>
<div class="def-block">
<label>Magic Defense</label>
<input type="number" id="magDef" value="0" />
</div>
</div>
</div>
</div>
</div>
<!-- Row 2: Attributes + Status + HP/MP/IP -->
<div class="grid-3" style="margin-bottom: 20px">
<!-- Attributes -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Attributes
</div>
<div class="attr-grid">
<div class="attr-block">
<div class="attr-name">Dexterity</div>
<div class="attr-inputs">
<div style="flex: 1">
<label style="font-size: 0.48rem">Base</label>
<input
type="number"
id="dex-base"
value="6"
min="6"
max="12"
oninput="recalcStats()"
/>
</div>
<div class="attr-sep"></div>
<div style="flex: 1">
<label style="font-size: 0.48rem">Current</label>
<input
type="number"
id="dex-cur"
value="6"
min="6"
max="12"
/>
</div>
</div>
</div>
<div class="attr-block">
<div class="attr-name">Insight</div>
<div class="attr-inputs">
<div style="flex: 1">
<label style="font-size: 0.48rem">Base</label>
<input
type="number"
id="ins-base"
value="6"
min="6"
max="12"
oninput="recalcStats()"
/>
</div>
<div class="attr-sep"></div>
<div style="flex: 1">
<label style="font-size: 0.48rem">Current</label>
<input
type="number"
id="ins-cur"
value="6"
min="6"
max="12"
/>
</div>
</div>
</div>
<div class="attr-block">
<div class="attr-name">Might</div>
<div class="attr-inputs">
<div style="flex: 1">
<label style="font-size: 0.48rem">Base</label>
<input
type="number"
id="mig-base"
value="6"
min="6"
max="12"
oninput="recalcStats()"
/>
</div>
<div class="attr-sep"></div>
<div style="flex: 1">
<label style="font-size: 0.48rem">Current</label>
<input
type="number"
id="mig-cur"
value="6"
min="6"
max="12"
/>
</div>
</div>
</div>
<div class="attr-block">
<div class="attr-name">Willpower</div>
<div class="attr-inputs">
<div style="flex: 1">
<label style="font-size: 0.48rem">Base</label>
<input
type="number"
id="wlp-base"
value="6"
min="6"
max="12"
oninput="recalcStats()"
/>
</div>
<div class="attr-sep"></div>
<div style="flex: 1">
<label style="font-size: 0.48rem">Current</label>
<input
type="number"
id="wlp-cur"
value="6"
min="6"
max="12"
/>
</div>
</div>
</div>
</div>
</div>
<!-- Status Effects -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Status Effects
</div>
<div class="status-grid" id="statusGrid">
<!-- generated by JS -->
</div>
</div>
<!-- HP / MP / IP -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Hit, Mind & Inventory Points
</div>
<div class="vital-block">
<div class="vital-label hp">HP</div>
<div style="flex: 1">
<div class="vital-formula">MIG×5 + Level + Other</div>
<div class="vital-inputs">
<input
type="number"
id="hpMax"
placeholder="Max"
oninput="updateCrisis()"
/>
<div class="vital-sep">/</div>
<input
type="number"
id="hpCur"
placeholder="Cur"
oninput="updateCrisis()"
/>
<div
class="crisis-badge"
id="crisisBadge"
style="display: none"
>
CRISIS
</div>
</div>
</div>
<button
class="add-btn"
style="padding: 4px 8px"
onclick="calcHP()"
title="Auto-calculate from Might"
>
Calc
</button>
</div>
<div class="vital-block">
<div class="vital-label mp">MP</div>
<div style="flex: 1">
<div class="vital-formula">WLP×5 + Level + Other</div>
<div class="vital-inputs">
<input type="number" id="mpMax" placeholder="Max" />
<div class="vital-sep">/</div>
<input type="number" id="mpCur" placeholder="Cur" />
</div>
</div>
<button
class="add-btn"
style="padding: 4px 8px"
onclick="calcMP()"
title="Auto-calculate from Willpower"
>
Calc
</button>
</div>
<div class="vital-block">
<div class="vital-label ip">IP</div>
<div style="flex: 1">
<div class="vital-formula">6 + Other</div>
<div class="vital-inputs">
<input type="number" id="ipMax" value="6" placeholder="Max" />
<div class="vital-sep">/</div>
<input type="number" id="ipCur" value="6" placeholder="Cur" />
</div>
</div>
</div>
</div>
</div>
<!-- Row 3: Fabula Points + Bonds -->
<div class="grid-2" style="margin-bottom: 20px">
<!-- Fabula Points -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Fabula Points
</div>
<div
style="
display: flex;
align-items: center;
gap: 14px;
flex-wrap: wrap;
"
>
<div>
<label>Current FP</label>
<input
type="number"
id="fpCount"
value="0"
min="0"
max="20"
style="
width: 70px;
text-align: center;
font-size: 1.4rem;
font-family: var(--font-mono);
"
oninput="renderFP()"
/>
</div>
<div class="fp-pips" id="fpPips"></div>
</div>
<div class="fp-rules">
<div class="fp-rule">
<strong>+1 FP</strong> if you have none at start of session.
</div>
<div class="fp-rule">
<strong>+1 FP</strong> when a Villain makes an entrance.
</div>
<div class="fp-rule">
<strong>+1 FP</strong> when you fumble a Check.
</div>
<div class="fp-rule">
<strong>+2 FP</strong> if you surrender at zero HP.
</div>
<div class="fp-rule" style="margin-top: 6px">
<strong>Spend 1 FP</strong> to invoke a trait: reroll one or both
dice.
</div>
<div class="fp-rule">
<strong>Spend 1 FP</strong> to invoke a bond: add its strength to
the result.
</div>
<div class="fp-rule">
<strong>Spend 1 FP</strong> to alter the story.
</div>
</div>
</div>
<!-- Bonds -->
<div class="section">
<div class="section-title"><span class="icon"></span> Bonds</div>
<div id="bondsContainer"></div>
</div>
</div>
<!-- Row 4: Equipment + Backpack -->
<div class="grid-2" style="margin-bottom: 20px">
<!-- Equipment -->
<div class="section">
<div class="section-title"><span class="icon"></span> Equipment</div>
<div class="martial-row" id="martialRow">
<div class="martial-item" onclick="toggleMartial(this)">
<div class="martial-box"></div>
<span>Martial Armor</span>
</div>
<div class="martial-item" onclick="toggleMartial(this)">
<div class="martial-box"></div>
<span>Martial Shields</span>
</div>
<div class="martial-item" onclick="toggleMartial(this)">
<div class="martial-box"></div>
<span>Martial Melee</span>
</div>
<div class="martial-item" onclick="toggleMartial(this)">
<div class="martial-box"></div>
<span>Martial Ranged</span>
</div>
</div>
<div style="margin-top: 14px">
<div class="equip-row">
<div class="equip-slot">Accessory</div>
<div class="equip-fields">
<input type="text" id="acc-name" placeholder="Item name" />
<input
type="text"
id="acc-desc"
placeholder="Description / effect"
/>
</div>
</div>
<div class="equip-row">
<div class="equip-slot">Armor</div>
<div class="equip-fields">
<input type="text" id="arm-name" placeholder="Item name" />
<input
type="text"
id="arm-desc"
placeholder="Defense bonus / effect"
/>
</div>
</div>
<div class="equip-row">
<div class="equip-slot">Main Hand</div>
<div class="equip-fields">
<input type="text" id="mh-name" placeholder="Weapon name" />
<input type="text" id="mh-desc" placeholder="Damage / effect" />
</div>
</div>
<div class="equip-row">
<div class="equip-slot">Off-Hand</div>
<div class="equip-fields">
<input type="text" id="oh-name" placeholder="Weapon / shield" />
<input type="text" id="oh-desc" placeholder="Damage / effect" />
</div>
</div>
</div>
</div>
<!-- Backpack & Notes -->
<div class="section">
<div class="section-title">
<span class="icon"></span> Backpack & Notes
</div>
<textarea
id="backpack"
placeholder="Items, notes, lore…"
style="min-height: 200px"
></textarea>
</div>
</div>
</div>
<!-- ══════════════════════════════════════════════════
PAGE 2: CLASSES
══════════════════════════════════════════════════ -->
<div class="page" id="page-classes">
<div class="grid-2" style="margin-bottom: 20px">
<div class="section">
<div class="section-title">
<span class="icon"></span> Primary Classes (up to 3 levels each)
</div>
<div id="primaryClassContainer"></div>
<button class="add-btn" onclick="addPrimaryClass()">
+ Add Primary Class
</button>
</div>
<div class="section">
<div class="section-title">
<span class="icon"></span> Other Classes (max 3 non-mastered)
</div>
<div id="otherClassContainer"></div>
<button class="add-btn" onclick="addOtherClass()">+ Add Class</button>
</div>
</div>
<div class="section">
<div class="section-title">
<span class="icon"></span> Heroic Skills
</div>
<textarea
id="heroicSkills"
placeholder="Record your heroic skill abilities here…"
style="min-height: 100px"
></textarea>
</div>
</div>
<!-- ══════════════════════════════════════════════════
PAGE 3: ARCANA & SPELLS
══════════════════════════════════════════════════ -->
<div class="page" id="page-spells">
<div class="section" style="margin-bottom: 20px">
<div class="section-title">
<span class="icon"></span> Arcana & Spells
</div>
<table class="spells-table" id="spellsTable">
<thead>
<tr>
<th class="spell-name-col">Name / Notes</th>
<th class="spell-mp-col">MP Cost</th>
<th class="spell-targets-col">Targets</th>
<th class="spell-dur-col">Duration</th>
<th class="spell-del-col"></th>
</tr>
</thead>
<tbody id="spellsBody"></tbody>
</table>
<button class="add-btn" onclick="addSpell()" style="margin-top: 10px">
+ Add Spell / Arcana
</button>
</div>
<div class="section">
<div class="section-title"><span class="icon"></span> Rituals</div>
<div class="disciplines-row" id="disciplinesRow">
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Arcanism</span>
</div>
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Chimerism</span>
</div>
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Elementalism</span>
</div>
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Entropism</span>
</div>
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Ritualism</span>
</div>
<div class="disc-item" onclick="toggleDisc(this)">
<div class="disc-box"></div>
<span>Spiritism</span>
</div>
</div>
<textarea
id="ritualsNotes"
placeholder="Record ritual details, components, and notes here…"
style="min-height: 120px"
></textarea>
</div>
</div>
<!-- ══════════════════════════════════════════════════
PAGE 4: MANAGE
══════════════════════════════════════════════════ -->
<div class="page" id="page-manage">
<div class="manage-grid">
<div class="section">
<div class="section-title">
<span class="icon"></span> Local Save
</div>
<p class="manage-desc">
Save your character sheet to your browser's local storage, or load a
previously saved sheet.
</p>
<div class="manage-btn-row">
<button class="btn-save btn-lg" onclick="saveSheet()">
✦ Save to Browser
</button>
<button class="btn-load btn-lg" onclick="loadSheet()">
↑ Load from Browser
</button>
</div>
</div>
<div class="section">
<div class="section-title"><span class="icon"></span> JSON File</div>
<p class="manage-desc">
Export your character to a JSON file for backup or sharing, or
import from a previously exported file.
</p>
<div class="manage-btn-row">
<button class="btn-save btn-export btn-lg" onclick="exportSheet()">
↓ Export JSON
</button>
<button class="btn-load btn-import btn-lg" onclick="importSheet()">
↑ Import JSON
</button>
<input
type="file"
id="importFileInput"
accept=".json,application/json"
style="display: none"
onchange="handleImportFile(this)"
/>
</div>
</div>
<div class="section col-span-2">
<div class="section-title">
<span class="icon"></span> Share via URL
</div>
<p class="manage-desc">
Encode your character's current state into a shareable link. Anyone
who opens the link will see your character — auto-save is disabled
for viewers.
</p>
<div class="manage-btn-row">
<button class="btn-save btn-export btn-lg" onclick="copyShareURL()">
⎘ Copy URL
</button>
<span class="save-status" id="copyStatus">Copied!</span>
</div>
</div>
</div>
</div>
<script src="fabula-ultima-sheet.js"></script>
</body>
</html>