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

436 lines
18 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>
</div>
<div class="toolbar">
<button class="btn-save" onclick="saveSheet()">✦ Save</button>
<button class="btn-load" onclick="loadSheet()">↑ Load</button>
<div class="toolbar-sep"></div>
<button class="btn-save btn-export" onclick="exportSheet()">↓ Export JSON</button>
<button class="btn-load btn-import" onclick="importSheet()">↑ Import JSON</button>
<input type="file" id="importFileInput" accept=".json,application/json" style="display:none"
onchange="handleImportFile(this)">
<span class="save-status" id="saveStatus">Saved!</span>
<div class="toolbar-sep"></div>
<button class="btn-theme" id="themeToggle" onclick="toggleTheme()">☀ Light</button>
</div>
</header>
<!-- ══════════════════════════════════════════════════
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>
<script src="fabula-ultima-sheet.js"></script>
</body>
</html>