Adds import support from our other project.
This commit is contained in:
57
rulesets/open5e/entities/armor.json
Normal file
57
rulesets/open5e/entities/armor.json
Normal file
@@ -0,0 +1,57 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "armor",
|
||||
"name": "Armor",
|
||||
"description": "Armor entity extending base item schema with AC base, dex cap, strength requirement, and stealth penalties.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name", "armor_category", "ac_base"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "item.json#/schema"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"armor_category": {
|
||||
"type": "string",
|
||||
"enum": ["light", "medium", "heavy"],
|
||||
"description": "Classification of armor determining weight class and dexterity application."
|
||||
},
|
||||
"ac_base": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Base Armor Class provided by this armor (e.g. 11 for Leather, 14 for Breastplate, 18 for Plate)."
|
||||
},
|
||||
"ac_modifier": {
|
||||
"type": "integer",
|
||||
"description": "Armor Class modifier over the base 10 (e.g. +1 for Leather, +4 for Breastplate, +8 for Plate)."
|
||||
},
|
||||
"dex_cap": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Maximum dexterity modifier bonus allowed. null = uncapped (light armor), 2 = medium armor cap (+2), 0 = heavy armor (no dex bonus)."
|
||||
},
|
||||
"strength_requirement": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Minimum Strength score needed to wear without speed penalty. null or 0 if none."
|
||||
},
|
||||
"stealth_disadvantage": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether wearing this armor imposes disadvantage on Dexterity (Stealth) checks."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_values": {
|
||||
"weight": 10,
|
||||
"quantity": 1,
|
||||
"equipped": false,
|
||||
"rarity": "common",
|
||||
"stealth_disadvantage": false,
|
||||
"strength_requirement": null
|
||||
}
|
||||
}
|
||||
375
rulesets/open5e/entities/character.json
Normal file
375
rulesets/open5e/entities/character.json
Normal file
@@ -0,0 +1,375 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "character",
|
||||
"name": "Player Character",
|
||||
"description": "5e Player Character entity with ability scores, proficiencies, hit points, and inventory.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"default_template": "character_sheet",
|
||||
"allowed_templates": [
|
||||
"character_sheet",
|
||||
"character_sheet_alt",
|
||||
"spellcasting_sheet",
|
||||
"stat_block",
|
||||
"character_card",
|
||||
"character_list_item"
|
||||
],
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["level", "abilities", "hit_points"],
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"maximum": 20,
|
||||
"description": "Total character level."
|
||||
},
|
||||
"class": {
|
||||
"type": "string",
|
||||
"description": "Primary class of the character."
|
||||
},
|
||||
"race": {
|
||||
"type": "string",
|
||||
"description": "Character race / species."
|
||||
},
|
||||
"background": {
|
||||
"type": "string",
|
||||
"description": "Character background."
|
||||
},
|
||||
"alignment": {
|
||||
"type": "string",
|
||||
"description": "Ethical and moral alignment."
|
||||
},
|
||||
"experience_points": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"armor_class": {
|
||||
"type": ["integer", "object"],
|
||||
"description": "Armor class value or extended property with shield/dex/item modifiers."
|
||||
},
|
||||
"equipped_armor": {
|
||||
"type": ["object", "null"],
|
||||
"description": "Currently equipped body armor item snapshot or reference. null if unarmored.",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"armor_category": {
|
||||
"type": "string",
|
||||
"enum": ["light", "medium", "heavy"]
|
||||
},
|
||||
"ac_base": { "type": "integer" },
|
||||
"dex_cap": { "type": ["integer", "null"] },
|
||||
"stealth_disadvantage": { "type": "boolean" },
|
||||
"strength_requirement": { "type": ["integer", "null"] }
|
||||
}
|
||||
},
|
||||
"equipped_shield": {
|
||||
"type": ["object", "null"],
|
||||
"description": "Currently equipped shield item snapshot or reference. null if no shield is equipped.",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"name": { "type": "string" },
|
||||
"ac_bonus": { "type": "integer", "default": 2 },
|
||||
"stealth_disadvantage": { "type": "boolean" },
|
||||
"strength_requirement": { "type": ["integer", "null"] }
|
||||
}
|
||||
},
|
||||
"speed": {
|
||||
"type": "integer",
|
||||
"description": "Base walking speed in feet."
|
||||
},
|
||||
"hit_points": {
|
||||
"type": "object",
|
||||
"required": ["current", "max"],
|
||||
"properties": {
|
||||
"current": { "type": "integer" },
|
||||
"max": { "type": "integer" },
|
||||
"temp": { "type": "integer", "default": 0 }
|
||||
}
|
||||
},
|
||||
"abilities": {
|
||||
"type": "object",
|
||||
"description": "Core 5e ability scores.",
|
||||
"required": [
|
||||
"strength",
|
||||
"dexterity",
|
||||
"constitution",
|
||||
"intelligence",
|
||||
"wisdom",
|
||||
"charisma"
|
||||
],
|
||||
"properties": {
|
||||
"strength": { "type": ["integer", "object"] },
|
||||
"dexterity": { "type": ["integer", "object"] },
|
||||
"constitution": { "type": ["integer", "object"] },
|
||||
"intelligence": { "type": ["integer", "object"] },
|
||||
"wisdom": { "type": ["integer", "object"] },
|
||||
"charisma": { "type": ["integer", "object"] }
|
||||
}
|
||||
},
|
||||
"player_name": {
|
||||
"type": "string",
|
||||
"description": "Name of the player controlling this character."
|
||||
},
|
||||
"subclass": {
|
||||
"type": "string",
|
||||
"description": "Character archetype or subclass (e.g. Hunter, Champion, Evoker)."
|
||||
},
|
||||
"inspiration": {
|
||||
"type": ["boolean", "integer"],
|
||||
"default": false,
|
||||
"description": "Heroic inspiration state or count."
|
||||
},
|
||||
"initiative": {
|
||||
"type": ["integer", "object"],
|
||||
"description": "Initiative modifier (typically dexterity modifier plus bonuses)."
|
||||
},
|
||||
"hit_dice": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": { "type": ["integer", "string"] },
|
||||
"current": { "type": ["integer", "string"] },
|
||||
"dice": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"death_saves": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"successes": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 3,
|
||||
"default": 0
|
||||
},
|
||||
"failures": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 3,
|
||||
"default": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"saving_throws": {
|
||||
"type": ["array", "object"],
|
||||
"description": "Saving throw proficiencies and bonuses."
|
||||
},
|
||||
"skills": {
|
||||
"type": ["array", "object"],
|
||||
"description": "Skill proficiencies and bonuses across the 18 standard 5e skills."
|
||||
},
|
||||
"passive_perception": {
|
||||
"type": ["integer", "object"],
|
||||
"description": "Passive Wisdom (Perception) score: typically 10 + perception bonus."
|
||||
},
|
||||
"other_proficiencies_and_languages": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"armor": { "type": "array", "items": { "type": "string" } },
|
||||
"weapons": { "type": "array", "items": { "type": "string" } },
|
||||
"tools": { "type": "array", "items": { "type": "string" } },
|
||||
"languages": { "type": "array", "items": { "type": "string" } }
|
||||
}
|
||||
},
|
||||
"attacks": {
|
||||
"type": "array",
|
||||
"description": "Weapons and spell attacks for the Attacks & Spellcasting section.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"atk_bonus": { "type": ["string", "integer"] },
|
||||
"damage": { "type": "string" },
|
||||
"damage_type": { "type": "string" },
|
||||
"range": { "type": "string" },
|
||||
"notes": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"currency": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"cp": { "type": "integer", "default": 0 },
|
||||
"sp": { "type": "integer", "default": 0 },
|
||||
"ep": { "type": "integer", "default": 0 },
|
||||
"gp": { "type": "integer", "default": 0 },
|
||||
"pp": { "type": "integer", "default": 0 }
|
||||
}
|
||||
},
|
||||
"personality_traits": { "type": ["string", "array"] },
|
||||
"ideals": { "type": ["string", "array"] },
|
||||
"bonds": { "type": ["string", "array"] },
|
||||
"flaws": { "type": ["string", "array"] },
|
||||
"features_and_traits": {
|
||||
"type": "array",
|
||||
"description": "Class, racial, and background features.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"source": { "type": "string" },
|
||||
"desc": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"character_details": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"age": { "type": ["integer", "string"] },
|
||||
"height": { "type": "string" },
|
||||
"weight": { "type": "string" },
|
||||
"eyes": { "type": "string" },
|
||||
"skin": { "type": "string" },
|
||||
"hair": { "type": "string" },
|
||||
"appearance": { "type": "string" },
|
||||
"backstory": { "type": "string" },
|
||||
"allies_and_organizations": { "type": ["string", "array"] },
|
||||
"treasure": { "type": ["string", "array"] }
|
||||
}
|
||||
},
|
||||
"spellcasting": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"class": { "type": "string" },
|
||||
"ability": { "type": "string" },
|
||||
"save_dc": { "type": ["integer", "object"] },
|
||||
"attack_bonus": { "type": ["integer", "object"] },
|
||||
"cantrips": {
|
||||
"type": "array",
|
||||
"items": { "type": ["string", "object"] }
|
||||
},
|
||||
"slots": {
|
||||
"type": "object",
|
||||
"description": "Spell slot counts per spell level 1-9.",
|
||||
"additionalProperties": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": { "type": "integer" },
|
||||
"expended": { "type": "integer", "default": 0 }
|
||||
}
|
||||
}
|
||||
},
|
||||
"spells": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": { "type": "string" },
|
||||
"level": { "type": "integer", "minimum": 0, "maximum": 9 },
|
||||
"prepared": { "type": "boolean", "default": false },
|
||||
"desc": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"inventory": {
|
||||
"type": "array",
|
||||
"description": "List of carried items.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["id", "name"],
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"entity_ref": {
|
||||
"type": "string",
|
||||
"description": "Optional relative path or identifier of the base item entity."
|
||||
},
|
||||
"name": { "type": "string" },
|
||||
"quantity": { "type": "integer", "default": 1 },
|
||||
"weight": { "type": "number", "default": 0 },
|
||||
"equipped": { "type": "boolean", "default": false },
|
||||
"ac_base": { "type": "integer" },
|
||||
"dex_cap": { "type": ["integer", "null"] },
|
||||
"ac_bonus": { "type": "integer" },
|
||||
"grants_modifiers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"target": { "type": "string" },
|
||||
"value": { "type": ["number", "string"] },
|
||||
"type": { "type": "string" },
|
||||
"source": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"calculated_properties": {
|
||||
"armor_class": {
|
||||
"expr": "10 + (equipped_armor ? (equipped_armor.ac_modifier !== undefined ? equipped_armor.ac_modifier : equipped_armor.ac_base - 10) : 0) + (equipped_armor && equipped_armor.dex_cap !== null ? (equipped_armor.dex_cap === 0 ? min(0, abilities.dexterity.modifier) : min(equipped_armor.dex_cap, abilities.dexterity.modifier)) : abilities.dexterity.modifier) + (equipped_shield ? equipped_shield.ac_bonus : 0) + sum(filter(armor_class.modifiers, m => m.active && m.type !== 'armor' && m.type !== 'ability'))",
|
||||
"type": "integer",
|
||||
"dependencies": [
|
||||
"equipped_armor",
|
||||
"equipped_shield",
|
||||
"abilities.dexterity.modifier",
|
||||
"armor_class.modifiers"
|
||||
],
|
||||
"description": "5e Armor Class calculation: base 10 + armor modifier (e.g. +1 for leather, +8 for plate) + dexterity modifier (capped by armor; heavy armor caps positive dex to 0 but allows negative penalties) + shield bonus + active external modifiers."
|
||||
},
|
||||
"proficiency_bonus": {
|
||||
"expr": "floor((level - 1) / 4) + 2",
|
||||
"type": "integer",
|
||||
"dependencies": ["level"],
|
||||
"description": "Standard 5e proficiency bonus calculation."
|
||||
},
|
||||
"ability_modifiers.strength": {
|
||||
"expr": "floor((abilities.strength - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.strength"]
|
||||
},
|
||||
"ability_modifiers.dexterity": {
|
||||
"expr": "floor((abilities.dexterity - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.dexterity"]
|
||||
},
|
||||
"ability_modifiers.constitution": {
|
||||
"expr": "floor((abilities.constitution - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.constitution"]
|
||||
},
|
||||
"ability_modifiers.intelligence": {
|
||||
"expr": "floor((abilities.intelligence - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.intelligence"]
|
||||
},
|
||||
"ability_modifiers.wisdom": {
|
||||
"expr": "floor((abilities.wisdom - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.wisdom"]
|
||||
},
|
||||
"ability_modifiers.charisma": {
|
||||
"expr": "floor((abilities.charisma - 10) / 2)",
|
||||
"type": "integer",
|
||||
"dependencies": ["abilities.charisma"]
|
||||
}
|
||||
},
|
||||
"default_values": {
|
||||
"level": 1,
|
||||
"equipped_armor": null,
|
||||
"equipped_shield": null,
|
||||
"abilities": {
|
||||
"strength": 10,
|
||||
"dexterity": 10,
|
||||
"constitution": 10,
|
||||
"intelligence": 10,
|
||||
"wisdom": 10,
|
||||
"charisma": 10
|
||||
},
|
||||
"hit_points": {
|
||||
"current": 10,
|
||||
"max": 10,
|
||||
"temp": 0
|
||||
},
|
||||
"speed": 30,
|
||||
"inventory": []
|
||||
}
|
||||
}
|
||||
100
rulesets/open5e/entities/item.json
Normal file
100
rulesets/open5e/entities/item.json
Normal file
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "item",
|
||||
"name": "Item / Gear",
|
||||
"description": "Base entity schema for generic items, equipment, and gear in 5e.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name"],
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the item."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "A description of the item."
|
||||
},
|
||||
"cost": {
|
||||
"description": "The market value or cost of the item.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"quantity": { "type": "number", "minimum": 0 },
|
||||
"unit": {
|
||||
"type": "string",
|
||||
"enum": ["cp", "sp", "ep", "gp", "pp"],
|
||||
"default": "gp"
|
||||
}
|
||||
}
|
||||
},
|
||||
"weight": {
|
||||
"type": "number",
|
||||
"minimum": 0,
|
||||
"description": "Weight in pounds (lbs)."
|
||||
},
|
||||
"rarity": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"common",
|
||||
"uncommon",
|
||||
"rare",
|
||||
"very rare",
|
||||
"legendary",
|
||||
"artifact"
|
||||
],
|
||||
"default": "common"
|
||||
},
|
||||
"attunement": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether this item requires attunement."
|
||||
},
|
||||
"equipped": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether this item is currently equipped or worn."
|
||||
},
|
||||
"quantity": {
|
||||
"type": "integer",
|
||||
"minimum": 1,
|
||||
"default": 1,
|
||||
"description": "Number of copies carried in inventory."
|
||||
},
|
||||
"grants_modifiers": {
|
||||
"type": "array",
|
||||
"description": "Modifiers applied to the character when this item is equipped.",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["target", "value"],
|
||||
"properties": {
|
||||
"target": {
|
||||
"type": "string",
|
||||
"description": "The property path modified on the wearer (e.g. 'armor_class', 'saving_throws.all')."
|
||||
},
|
||||
"value": {
|
||||
"type": ["number", "string"],
|
||||
"description": "Modifier bonus value or expression."
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"description": "Modifier type (e.g. 'bonus', 'base', 'override', 'advantage')."
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Name or origin of the bonus."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"default_values": {
|
||||
"weight": 0,
|
||||
"quantity": 1,
|
||||
"equipped": false,
|
||||
"rarity": "common",
|
||||
"attunement": false
|
||||
}
|
||||
}
|
||||
45
rulesets/open5e/entities/shield.json
Normal file
45
rulesets/open5e/entities/shield.json
Normal file
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "shield",
|
||||
"name": "Shield",
|
||||
"description": "Shield entity extending base item schema with AC bonus and strength/stealth properties.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": ["name", "ac_bonus"],
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "item.json#/schema"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ac_bonus": {
|
||||
"type": "integer",
|
||||
"default": 2,
|
||||
"description": "Armor Class bonus granted by wielding this shield (standard 5e shield is +2)."
|
||||
},
|
||||
"strength_requirement": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Minimum Strength score required to use without penalty."
|
||||
},
|
||||
"stealth_disadvantage": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "Whether wielding this shield imposes disadvantage on Dexterity (Stealth) checks."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_values": {
|
||||
"ac_bonus": 2,
|
||||
"weight": 6,
|
||||
"quantity": 1,
|
||||
"equipped": false,
|
||||
"rarity": "common",
|
||||
"stealth_disadvantage": false,
|
||||
"strength_requirement": null
|
||||
}
|
||||
}
|
||||
92
rulesets/open5e/entities/spell.json
Normal file
92
rulesets/open5e/entities/spell.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "spell",
|
||||
"name": "Spell",
|
||||
"description": "5e Spell entity definition declaring level, school, casting time, range, components, duration, and spell description.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"level",
|
||||
"school",
|
||||
"casting_time",
|
||||
"range",
|
||||
"duration",
|
||||
"description"
|
||||
],
|
||||
"properties": {
|
||||
"level": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 9,
|
||||
"description": "Spell level from 0 (cantrip) up to 9."
|
||||
},
|
||||
"school": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"abjuration",
|
||||
"conjuration",
|
||||
"divination",
|
||||
"enchantment",
|
||||
"evocation",
|
||||
"illusion",
|
||||
"necromancy",
|
||||
"transmutation"
|
||||
]
|
||||
},
|
||||
"casting_time": {
|
||||
"type": "string",
|
||||
"description": "e.g. 1 action, 1 bonus action, 1 reaction, 10 minutes."
|
||||
},
|
||||
"range": {
|
||||
"type": "string",
|
||||
"description": "e.g. Self, Touch, 30 feet, 60 feet, 120 feet."
|
||||
},
|
||||
"components": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"verbal": { "type": "boolean", "default": true },
|
||||
"somatic": { "type": "boolean", "default": true },
|
||||
"material": { "type": "boolean", "default": false },
|
||||
"material_specified": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"duration": {
|
||||
"type": "string",
|
||||
"description": "e.g. Instantaneous, 1 round, 1 hour, Concentration up to 1 minute."
|
||||
},
|
||||
"concentration": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"ritual": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "Rules text of the spell effect."
|
||||
},
|
||||
"higher_levels": {
|
||||
"type": "string",
|
||||
"description": "Rules text for casting at higher slot levels."
|
||||
}
|
||||
}
|
||||
},
|
||||
"default_values": {
|
||||
"level": 0,
|
||||
"school": "evocation",
|
||||
"casting_time": "1 action",
|
||||
"range": "60 feet",
|
||||
"components": {
|
||||
"verbal": true,
|
||||
"somatic": true,
|
||||
"material": false
|
||||
},
|
||||
"duration": "Instantaneous",
|
||||
"concentration": false,
|
||||
"ritual": false,
|
||||
"description": ""
|
||||
}
|
||||
}
|
||||
82
rulesets/open5e/entities/weapon.json
Normal file
82
rulesets/open5e/entities/weapon.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity-definition.json",
|
||||
"id": "weapon",
|
||||
"name": "Weapon",
|
||||
"description": "5e Weapon entity definition inheriting from base item, specifying damage dice, damage type, and weapon properties.",
|
||||
"ruleset_id": "open5e",
|
||||
"version": "1.0.0",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "item.json#/schema"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["damage_dice", "damage_type"],
|
||||
"properties": {
|
||||
"weapon_category": {
|
||||
"type": "string",
|
||||
"enum": ["simple", "martial"]
|
||||
},
|
||||
"weapon_range": {
|
||||
"type": "string",
|
||||
"enum": ["melee", "ranged"],
|
||||
"default": "melee"
|
||||
},
|
||||
"damage_dice": {
|
||||
"type": "string",
|
||||
"description": "Damage formula e.g. 1d6, 1d8, 2d6."
|
||||
},
|
||||
"damage_type": {
|
||||
"type": "string",
|
||||
"enum": ["bludgeoning", "piercing", "slashing"]
|
||||
},
|
||||
"properties": {
|
||||
"type": "array",
|
||||
"description": "Standard 5e weapon properties.",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ammunition",
|
||||
"finesse",
|
||||
"heavy",
|
||||
"light",
|
||||
"loading",
|
||||
"range",
|
||||
"reach",
|
||||
"special",
|
||||
"thrown",
|
||||
"two-handed",
|
||||
"versatile"
|
||||
]
|
||||
}
|
||||
},
|
||||
"range_normal": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Standard effective range in feet."
|
||||
},
|
||||
"range_long": {
|
||||
"type": ["integer", "null"],
|
||||
"description": "Long range with disadvantage in feet."
|
||||
},
|
||||
"versatile_dice": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Two-handed damage dice when used versatile (e.g. 1d10 for a longsword)."
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"default_values": {
|
||||
"cost": {
|
||||
"quantity": 10,
|
||||
"unit": "gp"
|
||||
},
|
||||
"weight": 2.0,
|
||||
"weapon_category": "simple",
|
||||
"weapon_range": "melee",
|
||||
"damage_dice": "1d6",
|
||||
"damage_type": "slashing",
|
||||
"properties": []
|
||||
}
|
||||
}
|
||||
51
rulesets/open5e/examples/character/acolyte.json
Normal file
51
rulesets/open5e/examples/character/acolyte.json
Normal file
@@ -0,0 +1,51 @@
|
||||
{
|
||||
"$schema": "../../../../schema/entity.json",
|
||||
"uuid": "f6a83d72-b94e-4e5c-a5c2-f39b6b52d202",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "character",
|
||||
"display_name": "Acolyte",
|
||||
"description": "Medium humanoid (any race), any alignment.",
|
||||
"version": "1.0.0",
|
||||
"template_id": "stat_block",
|
||||
"tags": ["npc", "humanoid", "spellcaster", "cr-1/4"],
|
||||
"properties": {
|
||||
"size": "medium",
|
||||
"type": "Humanoid",
|
||||
"subtype": "any race",
|
||||
"alignment": "any alignment",
|
||||
"armor_class": 10,
|
||||
"hit_points": {
|
||||
"current": 9,
|
||||
"max": 9,
|
||||
"formula": "2d8 + 0",
|
||||
"hit_dice": "2d8"
|
||||
},
|
||||
"speed": 30,
|
||||
"speed_desc": "30 ft.",
|
||||
"challenge_rating": "1/4 (50 XP)",
|
||||
"cr": "1/4",
|
||||
"abilities": {
|
||||
"strength": 10,
|
||||
"dexterity": 10,
|
||||
"constitution": 10,
|
||||
"intelligence": 10,
|
||||
"wisdom": 14,
|
||||
"charisma": 11
|
||||
},
|
||||
"skills": ["medicine +4", "religion +2"],
|
||||
"senses": "passive Perception 12",
|
||||
"languages": ["any one language (usually Common)"],
|
||||
"traits": [
|
||||
{
|
||||
"name": "Spellcasting",
|
||||
"desc": "The acolyte is a 1st-level spellcaster. Its spellcasting ability is Wisdom (spell save DC 12, +4 to hit with spell attacks). The acolyte has following cleric spells prepared:\n\n* Cantrips (at will): _light, sacred flame, thaumaturgy_\n* 1st level (3 slots): _bless, cure wounds, sanctuary_"
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Club",
|
||||
"desc": "_Melee Weapon Attack:_ +2 to hit, reach 5 ft., one target. _Hit:_ 2 (1d4) bludgeoning damage."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
99
rulesets/open5e/examples/character/adult_black_dragon.json
Normal file
99
rulesets/open5e/examples/character/adult_black_dragon.json
Normal file
@@ -0,0 +1,99 @@
|
||||
{
|
||||
"$schema": "../../../../schema/entity.json",
|
||||
"uuid": "b8a91c34-d07f-4f8a-92c1-d41a8a91e404",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "character",
|
||||
"display_name": "Adult Black Dragon",
|
||||
"description": "Huge dragon, chaotic evil.",
|
||||
"version": "1.0.0",
|
||||
"template_id": "stat_block",
|
||||
"tags": ["monster", "dragon", "legendary", "cr-14"],
|
||||
"properties": {
|
||||
"size": "huge",
|
||||
"type": "Dragon",
|
||||
"alignment": "chaotic evil",
|
||||
"armor_class": 19,
|
||||
"armor_description": "natural armor",
|
||||
"hit_points": {
|
||||
"current": 196,
|
||||
"max": 196,
|
||||
"formula": "17d12 + 85",
|
||||
"hit_dice": "17d12"
|
||||
},
|
||||
"speed": 40,
|
||||
"speed_desc": "40 ft., fly 80 ft., swim 40 ft.",
|
||||
"challenge_rating": "14 (11,500 XP)",
|
||||
"cr": "14",
|
||||
"abilities": {
|
||||
"strength": 23,
|
||||
"dexterity": 14,
|
||||
"constitution": 21,
|
||||
"intelligence": 14,
|
||||
"wisdom": 13,
|
||||
"charisma": 17
|
||||
},
|
||||
"saving_throws": [
|
||||
"Dexterity +7",
|
||||
"Constitution +10",
|
||||
"Wisdom +6",
|
||||
"Charisma +8"
|
||||
],
|
||||
"skills": ["perception +16", "stealth +11"],
|
||||
"damage_immunities": ["acid"],
|
||||
"senses": "darkvision 120 ft., blindsight 60 ft., passive Perception 11",
|
||||
"languages": ["Common", "Draconic"],
|
||||
"traits": [
|
||||
{
|
||||
"name": "Amphibious",
|
||||
"desc": "The dragon can breathe air and water."
|
||||
},
|
||||
{
|
||||
"name": "Legendary Resistance (3/Day)",
|
||||
"desc": "If the dragon fails a saving throw, it can choose to succeed instead."
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Multiattack",
|
||||
"desc": "The dragon can use its Frightful Presence. It then makes three attacks: one with its bite and two with its claws."
|
||||
},
|
||||
{
|
||||
"name": "Bite",
|
||||
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 10 ft., one target. _Hit:_ 17 (2d10 + 6) piercing damage plus 4 (1d8) acid damage."
|
||||
},
|
||||
{
|
||||
"name": "Claw",
|
||||
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 5 ft., one target. _Hit:_ 13 (2d6 + 6) slashing damage."
|
||||
},
|
||||
{
|
||||
"name": "Tail",
|
||||
"desc": "_Melee Weapon Attack:_ +11 to hit, reach 15 ft., one target. _Hit:_ 15 (2d8 + 6) bludgeoning damage."
|
||||
},
|
||||
{
|
||||
"name": "Frightful Presence",
|
||||
"desc": "Each creature of the dragon's choice that is within 120 feet of the dragon and aware of it must succeed on a DC 16 Wisdom saving throw or become frightened for 1 minute. A creature can repeat the saving throw at the end of each of its turns, ending the effect on itself on a success. If a creature's saving throw is successful or the effect ends for it, the creature is immune to the dragon's Frightful Presence for the next 24 hours."
|
||||
},
|
||||
{
|
||||
"name": "Acid Breath (Recharge 5-6)",
|
||||
"desc": "The dragon exhales acid in a 60-foot line that is 5 feet wide. Each creature in that line must make a DC 18 Dexterity saving throw, taking 54 (12d8) acid damage on a failed save, or half as much damage on a successful one."
|
||||
}
|
||||
],
|
||||
"legendary_actions": {
|
||||
"description": "The dragon can take 3 legendary actions, choosing from the options below. Only one legendary action option can be used at a time and only at the end of another creature's turn. The dragon regains spent legendary actions at the start of its turn.",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Detect",
|
||||
"desc": "The dragon makes a Wisdom (Perception) check."
|
||||
},
|
||||
{
|
||||
"name": "Tail Attack",
|
||||
"desc": "The dragon makes a tail attack."
|
||||
},
|
||||
{
|
||||
"name": "Wing Attack (Costs 2 Actions)",
|
||||
"desc": "The dragon beats its wings. Each creature within 10 ft. of the dragon must succeed on a DC 19 Dexterity saving throw or take 13 (2d6 + 6) bludgeoning damage and be knocked prone. The dragon can then fly up to half its flying speed."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
261
rulesets/open5e/examples/character/arannis.json
Normal file
261
rulesets/open5e/examples/character/arannis.json
Normal file
@@ -0,0 +1,261 @@
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "character",
|
||||
"display_name": "Arannis",
|
||||
"description": "Elven Ranger exploring ancient ruins.",
|
||||
"version": "1.0.0",
|
||||
"created_at": "2026-09-06T14:00:00Z",
|
||||
"updated_at": "2026-09-06T18:00:00Z",
|
||||
"tags": ["pc", "ranger", "elf"],
|
||||
"properties": {
|
||||
"player_name": "Tom",
|
||||
"level": 3,
|
||||
"class": "Ranger",
|
||||
"subclass": "Hunter",
|
||||
"race": "Elf",
|
||||
"size": "medium",
|
||||
"type": "Humanoid",
|
||||
"subtype": "Elf",
|
||||
"background": "Outlander",
|
||||
"alignment": "Chaotic Good",
|
||||
"experience_points": 900,
|
||||
"inspiration": true,
|
||||
"proficiency_bonus": 2,
|
||||
"initiative": 3,
|
||||
"speed": 35,
|
||||
"speed_desc": "35 ft.",
|
||||
"hit_points": {
|
||||
"current": 24,
|
||||
"max": 24,
|
||||
"formula": "3d10 + 6",
|
||||
"temp": 0
|
||||
},
|
||||
"hit_dice": {
|
||||
"total": 3,
|
||||
"current": 3,
|
||||
"dice": "1d10"
|
||||
},
|
||||
"death_saves": {
|
||||
"successes": 0,
|
||||
"failures": 0
|
||||
},
|
||||
"saving_throws": ["Strength +3", "Dexterity +5"],
|
||||
"advantages": ["wis"],
|
||||
"skills": ["Athletics +3", "Perception +5", "Stealth +5", "Survival +5"],
|
||||
"passive_perception": 15,
|
||||
"senses": "darkvision 60 ft., passive Perception 15",
|
||||
"languages": ["Common", "Elvish", "Sylvan"],
|
||||
"other_proficiencies_and_languages": {
|
||||
"armor": ["Light armor", "Medium armor", "Shields"],
|
||||
"weapons": ["Simple weapons", "Martial weapons"],
|
||||
"tools": ["Herbalism kit", "Flute"],
|
||||
"languages": ["Common", "Elvish", "Sylvan"]
|
||||
},
|
||||
"attacks": [
|
||||
{
|
||||
"name": "Longbow",
|
||||
"atk_bonus": "+7",
|
||||
"damage": "1d8 + 3",
|
||||
"damage_type": "Piercing",
|
||||
"range": "150/600 ft."
|
||||
},
|
||||
{
|
||||
"name": "Shortsword",
|
||||
"atk_bonus": "+5",
|
||||
"damage": "1d6 + 3",
|
||||
"damage_type": "Piercing",
|
||||
"range": "5 ft."
|
||||
}
|
||||
],
|
||||
"currency": {
|
||||
"cp": 15,
|
||||
"sp": 40,
|
||||
"ep": 0,
|
||||
"gp": 25,
|
||||
"pp": 0
|
||||
},
|
||||
"personality_traits": "I feel far more comfortable among the ancient trees and wild beasts than in bustling cities.",
|
||||
"ideals": "Nature: The natural world is more important than the constructs of civilization.",
|
||||
"bonds": "An ancient ruined shrine in the northern forest holds a secret I have sworn to protect.",
|
||||
"flaws": "I am slow to trust others and prefer solitude even in times of danger.",
|
||||
"features_and_traits": [
|
||||
{
|
||||
"name": "Favored Enemy (Undead)",
|
||||
"source": "Ranger 1",
|
||||
"desc": "You have advantage on Wisdom (Survival) checks to track your favored enemies, as well as on Intelligence checks to recall information about them."
|
||||
},
|
||||
{
|
||||
"name": "Natural Explorer (Forest)",
|
||||
"source": "Ranger 1",
|
||||
"desc": "When making an Intelligence or Wisdom check related to your favored terrain, your proficiency bonus is doubled if you are using a skill you're proficient in."
|
||||
},
|
||||
{
|
||||
"name": "Colossus Slayer",
|
||||
"source": "Hunter Archetype",
|
||||
"desc": "Your tenacity can wear down the most potent foes. Once per turn when you hit a creature with a weapon attack, the creature takes an extra 1d8 damage if it's below its hit point maximum."
|
||||
}
|
||||
],
|
||||
"spellcasting": {
|
||||
"class": "Ranger",
|
||||
"ability": "WISDOM",
|
||||
"save_dc": 13,
|
||||
"attack_bonus": 5,
|
||||
"cantrips": [],
|
||||
"slots": {
|
||||
"1": {
|
||||
"total": 3,
|
||||
"expended": 1
|
||||
}
|
||||
},
|
||||
"spells": [
|
||||
{
|
||||
"name": "Hunter's Mark",
|
||||
"level": 1,
|
||||
"prepared": true,
|
||||
"desc": "You choose a creature you can see within range and mystically mark it as your quarry."
|
||||
},
|
||||
{
|
||||
"name": "Cure Wounds",
|
||||
"level": 1,
|
||||
"prepared": true,
|
||||
"desc": "A creature you touch regains a number of hit points equal to 1d8 + your spellcasting ability modifier."
|
||||
}
|
||||
]
|
||||
},
|
||||
"traits": [
|
||||
{
|
||||
"name": "Fey Ancestry",
|
||||
"desc": "Arannis has advantage on saving throws against being charmed, and magic cannot put her to sleep."
|
||||
},
|
||||
{
|
||||
"name": "Natural Explorer",
|
||||
"desc": "Difficult terrain does not slow Arannis's group while traveling."
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Longbow",
|
||||
"desc": "_Ranged Weapon Attack:_ +7 to hit, range 150/600 ft., one target. _Hit:_ 7 (1d8 + 3) piercing damage."
|
||||
},
|
||||
{
|
||||
"name": "Shortsword",
|
||||
"desc": "_Melee Weapon Attack:_ +5 to hit, reach 5 ft., one target. _Hit:_ 6 (1d6 + 3) piercing damage."
|
||||
}
|
||||
],
|
||||
"equipped_armor": {
|
||||
"id": "armor_leather",
|
||||
"name": "Leather Armor",
|
||||
"armor_category": "light",
|
||||
"ac_base": 11,
|
||||
"ac_modifier": 1,
|
||||
"dex_cap": null,
|
||||
"stealth_disadvantage": false,
|
||||
"strength_requirement": null
|
||||
},
|
||||
"equipped_shield": null,
|
||||
"armor_class": {
|
||||
"value": 15,
|
||||
"base": 10,
|
||||
"expr": "base + sum(modifiers)",
|
||||
"modifiers": [
|
||||
{
|
||||
"source": "Leather Armor",
|
||||
"value": 1,
|
||||
"type": "armor"
|
||||
},
|
||||
{
|
||||
"source": "Dexterity Modifier",
|
||||
"value": 3,
|
||||
"type": "ability"
|
||||
},
|
||||
{
|
||||
"source": "Ring of Protection +1",
|
||||
"value": 1,
|
||||
"type": "bonus",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"abilities": {
|
||||
"strength": 12,
|
||||
"dexterity": {
|
||||
"value": 16,
|
||||
"base": 14,
|
||||
"modifier": 3,
|
||||
"modifiers": [
|
||||
{
|
||||
"source": "Elven Agility",
|
||||
"value": 2,
|
||||
"type": "bonus"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constitution": 14,
|
||||
"intelligence": 10,
|
||||
"wisdom": 15,
|
||||
"charisma": 8
|
||||
},
|
||||
"inventory": [
|
||||
{
|
||||
"id": "armor_leather",
|
||||
"entity_ref": "entities/armor.json",
|
||||
"name": "Leather Armor",
|
||||
"quantity": 1,
|
||||
"weight": 10.0,
|
||||
"equipped": true,
|
||||
"ac_base": 11,
|
||||
"dex_cap": null
|
||||
},
|
||||
{
|
||||
"id": "ring_protection",
|
||||
"entity_ref": "entities/item.json",
|
||||
"name": "Ring of Protection +1",
|
||||
"quantity": 1,
|
||||
"weight": 0.1,
|
||||
"equipped": true,
|
||||
"grants_modifiers": [
|
||||
{
|
||||
"target": "armor_class",
|
||||
"value": 1,
|
||||
"type": "bonus",
|
||||
"source": "Ring of Protection +1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "longbow",
|
||||
"entity_ref": "entities/weapon.json",
|
||||
"name": "Longbow",
|
||||
"quantity": 1,
|
||||
"weight": 2.0,
|
||||
"equipped": true
|
||||
},
|
||||
{
|
||||
"id": "shortsword",
|
||||
"entity_ref": "entities/weapon.json",
|
||||
"name": "Shortsword",
|
||||
"quantity": 1,
|
||||
"weight": 2.0,
|
||||
"equipped": true
|
||||
},
|
||||
{
|
||||
"id": "arrow",
|
||||
"entity_ref": "entities/item.json",
|
||||
"name": "Arrow",
|
||||
"quantity": 20,
|
||||
"weight": 1.0,
|
||||
"equipped": false
|
||||
},
|
||||
{
|
||||
"id": "potion_healing",
|
||||
"entity_ref": "entities/item.json",
|
||||
"name": "Potion of Healing",
|
||||
"quantity": 2,
|
||||
"weight": 1.0,
|
||||
"equipped": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
56
rulesets/open5e/examples/character/goblin.json
Normal file
56
rulesets/open5e/examples/character/goblin.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"$schema": "../../../../schema/entity.json",
|
||||
"uuid": "e5b72e61-a83d-4c3d-b4b1-e28a5a41c101",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "character",
|
||||
"display_name": "Goblin",
|
||||
"description": "Small humanoid (goblinoid), neutral evil.",
|
||||
"version": "1.0.0",
|
||||
"template_id": "stat_block",
|
||||
"tags": ["monster", "goblinoid", "humanoid", "cr-1/4"],
|
||||
"properties": {
|
||||
"size": "small",
|
||||
"type": "Humanoid",
|
||||
"subtype": "goblinoid",
|
||||
"alignment": "neutral evil",
|
||||
"armor_class": 15,
|
||||
"armor_description": "leather armor, shield",
|
||||
"hit_points": {
|
||||
"current": 7,
|
||||
"max": 7,
|
||||
"formula": "2d6 + 0",
|
||||
"hit_dice": "2d6"
|
||||
},
|
||||
"speed": 30,
|
||||
"speed_desc": "30 ft.",
|
||||
"challenge_rating": "1/4 (50 XP)",
|
||||
"cr": "1/4",
|
||||
"abilities": {
|
||||
"strength": 8,
|
||||
"dexterity": 14,
|
||||
"constitution": 10,
|
||||
"intelligence": 10,
|
||||
"wisdom": 8,
|
||||
"charisma": 8
|
||||
},
|
||||
"skills": ["stealth +6"],
|
||||
"senses": "darkvision 60 ft., passive Perception 9",
|
||||
"languages": ["Common", "Goblin"],
|
||||
"traits": [
|
||||
{
|
||||
"name": "Nimble Escape",
|
||||
"desc": "The goblin can take the Disengage or Hide action as a bonus action on each of its turns."
|
||||
}
|
||||
],
|
||||
"actions": [
|
||||
{
|
||||
"name": "Scimitar",
|
||||
"desc": "_Melee Weapon Attack:_ +4 to hit, reach 5 ft., one target. _Hit:_ 5 (1d6 + 2) slashing damage."
|
||||
},
|
||||
{
|
||||
"name": "Shortbow",
|
||||
"desc": "_Ranged Weapon Attack:_ +4 to hit, range 80/320 ft., one target. _Hit:_ 5 (1d6 + 2) piercing damage."
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
492
rulesets/open5e/import-export.md
Normal file
492
rulesets/open5e/import-export.md
Normal file
@@ -0,0 +1,492 @@
|
||||
# Open5e Ruleset — Import & Export Specification Guide
|
||||
|
||||
This document is the technical integration and schema guide for third-party applications, services, and developers who want to **import** or **export** Open5e (D&D 5th Edition SRD) entities—including **Player Characters / Creatures**, **Weapons**, **Armor**, **Shields**, **Spells**, and **Generic Items**.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
1. [Overview & Architecture](#1-overview--architecture)
|
||||
2. [What Files to Distribute to External Apps](#2-what-files-to-distribute-to-external-apps)
|
||||
3. [The Universal Entity Envelope (`entity.json`)](#3-the-universal-entity-envelope-entityjson)
|
||||
4. [Entity Specifications & Payloads](#4-entity-specifications--payloads)
|
||||
- [4.1 Character (`character.json`)](#41-character-entity-characterjson)
|
||||
- [4.2 Base Item (`item.json`)](#42-base-item-entity-itemjson)
|
||||
- [4.3 Weapon (`weapon.json`)](#43-weapon-entity-weaponjson)
|
||||
- [4.4 Armor (`armor.json`)](#44-armor-entity-armorjson)
|
||||
- [4.5 Shield (`shield.json`)](#45-shield-entity-shieldjson)
|
||||
- [4.6 Spell (`spell.json`)](#46-spell-entity-spelljson)
|
||||
5. [Validation & Conformance Rules](#5-validation--conformance-rules)
|
||||
6. [Import & Export Implementation Checklist](#6-import--export-implementation-checklist)
|
||||
|
||||
---
|
||||
|
||||
## 1. Overview & Architecture
|
||||
|
||||
In our system, all data entities (characters, items, spells, etc.) follow a two-tier JSON architecture:
|
||||
|
||||
1. **The Universal Envelope (`entity.json`)**: Provides standard metadata required across all rulesets and systems (`uuid`, `ruleset_id`, `entity_type`, `display_name`, `version`, `tags`, etc.).
|
||||
2. **The Entity Properties Payload (`properties`)**: Houses ruleset-specific and entity-specific fields defined by the entity schema (e.g. ability scores, spell slots, armor classes, damage dice).
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ Universal Envelope (schema/entity.json) │
|
||||
│ ├─ uuid: "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2" │
|
||||
│ ├─ ruleset_id: "open5e" │
|
||||
│ ├─ entity_type: "character" | "weapon" | "spell" | ... │
|
||||
│ ├─ display_name: "Arannis" │
|
||||
│ └─ properties: { ... } <── Validated by Entity Schema │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ Entity-Specific Schema (entities/{type}.json) │
|
||||
│ ├─ character.json │
|
||||
│ ├─ weapon.json (inherits from item.json) │
|
||||
│ ├─ armor.json (inherits from item.json) │
|
||||
│ ├─ shield.json (inherits from item.json) │
|
||||
│ ├─ spell.json │
|
||||
│ └─ item.json │
|
||||
└──────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. What Files to Distribute to External Apps
|
||||
|
||||
To give an external application complete import and export capability, package and provide the following files:
|
||||
|
||||
### A. Root Schemas (`schema/`)
|
||||
1. **[`schema/entity.json`](../../schema/entity.json)**: The universal envelope schema. Every exported file must validate against this schema.
|
||||
2. **[`schema/entity-definition.json`](../../schema/entity-definition.json)**: The meta-schema describing how entity types and schemas are declared.
|
||||
|
||||
### B. Ruleset Manifest (`rulesets/open5e/`)
|
||||
3. **[`rulesets/open5e/manifest.json`](manifest.json)**: Identifies the ruleset (`open5e`), version, author, and all registered entities and templates.
|
||||
|
||||
### C. Entity Property Schemas (`rulesets/open5e/entities/`)
|
||||
4. **[`character.json`](entities/character.json)**: 5e Character and creature schema (attributes, vitals, proficiencies, attacks, spells, inventory).
|
||||
5. **[`item.json`](entities/item.json)**: Base schema for equipment, gear, and magic items.
|
||||
6. **[`weapon.json`](entities/weapon.json)**: Weapon schema (damage dice, damage types, ranges, weapon properties; inherits from `item.json`).
|
||||
7. **[`armor.json`](entities/armor.json)**: Armor schema (AC base, Dex caps, stealth penalty, strength requirement; inherits from `item.json`).
|
||||
8. **[`shield.json`](entities/shield.json)**: Shield schema (AC bonus, stealth penalty; inherits from `item.json`).
|
||||
9. **[`spell.json`](entities/spell.json)**: Spell schema (spell level 0–9, school, casting time, range, components, duration).
|
||||
|
||||
> **Integration Note on `$ref` Resolution**:
|
||||
> `weapon.json`, `armor.json`, and `shield.json` use JSON Schema `$ref: "item.json#/schema"`. When integrating with schema validators (such as Ajv, jsonschema, or Newtonsoft.Json), ensure all schema files are preloaded in the schema registry or resolved relative to their folder.
|
||||
|
||||
---
|
||||
|
||||
## 3. The Universal Entity Envelope (`entity.json`)
|
||||
|
||||
Every exported file **must** include these top-level fields:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `$schema` | `string` | Recommended | URI or relative path to schema (`"../../../schema/entity.json"`). |
|
||||
| `uuid` | `string` | **Yes** | Unique identifier (standard UUID v4 format recommended). |
|
||||
| `ruleset_id` | `string` | **Yes** | Must be `"open5e"` for this ruleset. |
|
||||
| `entity_type` | `string` | **Yes** | One of: `"character"`, `"item"`, `"weapon"`, `"armor"`, `"shield"`, `"spell"`. |
|
||||
| `display_name` | `string` | **Yes** | Human-readable primary label (e.g. `"Arannis"`, `"Longsword +1"`). |
|
||||
| `description` | `string` | No | Short synopsis or narrative summary. |
|
||||
| `version` | `string` | No | Data version format (default `"1.0.0"`). |
|
||||
| `created_at` | `string` | No | ISO 8601 timestamp (e.g. `"2026-09-07T12:00:00Z"`). |
|
||||
| `updated_at` | `string` | No | ISO 8601 timestamp of last modification. |
|
||||
| `tags` | `array<string>`| No | Filter and categorization tags (e.g. `["pc", "ranger", "elf"]`). |
|
||||
| `template_id` | `string` | No | Optional UI template override (e.g. `"character_sheet"`). |
|
||||
| `properties` | `object` | **Yes** | The entity's payload data (detailed below). |
|
||||
|
||||
---
|
||||
|
||||
## 4. Entity Specifications & Payloads
|
||||
|
||||
### 4.1 Character Entity (`character.json`)
|
||||
|
||||
Used for **Player Characters (PCs)**, **Non-Player Characters (NPCs)**, and **Monsters / Creatures**.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `level` (`integer`, 1–20)
|
||||
- `abilities` (`object` containing `strength`, `dexterity`, `constitution`, `intelligence`, `wisdom`, `charisma`)
|
||||
- `hit_points` (`object` containing `current` and `max`)
|
||||
|
||||
#### Full Property Breakdown:
|
||||
| Group | Field | Type | Description / Example |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| **Identity** | `player_name` | `string` | Player name (optional for NPCs/monsters). |
|
||||
| | `class` | `string` | Primary class (e.g. `"Ranger"`). |
|
||||
| | `subclass` | `string` | Archetype / subclass (e.g. `"Hunter"`). |
|
||||
| | `race` | `string` | Character race (e.g. `"Elf"`). |
|
||||
| | `background` | `string` | Background (e.g. `"Outlander"`). |
|
||||
| | `alignment` | `string` | Moral alignment (e.g. `"Chaotic Good"`). |
|
||||
| | `experience_points` | `integer` | Current total XP. |
|
||||
| | `inspiration` | `boolean` | Inspiration point toggle. |
|
||||
| **Abilities** | `abilities` | `object` | 6 scores. Can be raw integers or score objects: `{"strength": 12, "dexterity": 16, "constitution": 14, "intelligence": 10, "wisdom": 15, "charisma": 8}`. |
|
||||
| | `proficiency_bonus` | `integer` | Bonus added to proficient rolls (auto-calculated from level or explicit). |
|
||||
| | `saving_throws` | `array<string>` | Formatted saves (e.g. `["Strength +3", "Dexterity +5"]`). |
|
||||
| | `skills` | `array<string>` | Formatted skill proficiencies (e.g. `["Athletics +3", "Stealth +5"]`). |
|
||||
| | `passive_perception` | `integer` | Passive Wisdom (Perception) score (e.g. `15`). |
|
||||
| **Combat** | `hit_points` | `object` | `{"current": 24, "max": 24, "temp": 0, "formula": "3d10 + 6"}`. |
|
||||
| | `hit_dice` | `object` | `{"total": 3, "current": 3, "dice": "1d10"}`. |
|
||||
| | `death_saves` | `object` | `{"successes": 0, "failures": 0}` (0–3 each). |
|
||||
| | `speed` | `integer` | Walking speed in feet (e.g. `35`). |
|
||||
| | `speed_desc` | `string` | Display string including flight/swim (e.g. `"30 ft., fly 60 ft."`). |
|
||||
| | `initiative` | `integer` | Initiative modifier (usually Dex modifier). |
|
||||
| | `armor_class` | `integer\|object` | Total AC (e.g. `15`). |
|
||||
| | `equipped_armor` | `object\|null` | Snapshot: `{"name": "Studded Leather", "ac_base": 12, "dex_cap": null}`. |
|
||||
| | `equipped_shield` | `object\|null` | Snapshot: `{"name": "Shield", "ac_bonus": 2}`. |
|
||||
| | `attacks` | `array<object>` | Weapon/spell attack rows: `[{"name": "Longbow", "atk_bonus": "+7", "damage": "1d8 + 3", "damage_type": "Piercing", "range": "150/600 ft."}]`. |
|
||||
| **Inventory & Wealth** | `currency` | `object` | `{"cp": 15, "sp": 40, "ep": 0, "gp": 25, "pp": 0}`. |
|
||||
| | `inventory` | `array<object>` | Carried items matching `item.json` schema. |
|
||||
| **Roleplay** | `personality_traits` | `string` | Personality trait text. |
|
||||
| | `ideals` | `string` | Character ideals. |
|
||||
| | `bonds` | `string` | Character bonds. |
|
||||
| | `flaws` | `string` | Character flaws. |
|
||||
| | `features_and_traits` | `array<object>` | Class/racial features: `[{"name": "Colossus Slayer", "source": "Hunter Archetype", "desc": "..."}]`. |
|
||||
| **Spellcasting** | `spellcasting` | `object` | `{"class": "Ranger", "ability": "Wisdom", "save_dc": 13, "attack_bonus": 5, "cantrips": [], "slots": {"1": {"total": 3, "expended": 1}}, "spells": [...]}`. |
|
||||
| **NPC / Monster** | `challenge_rating` | `number\|string` | CR (e.g. `0.25`, `"1/4"`, `14`). |
|
||||
| | `actions` | `array<object>` | Monster stat block actions: `[{"name": "Scimitar", "desc": "Melee Weapon Attack..."}]`. |
|
||||
| | `reactions` | `array<object>` | Creature reactions. |
|
||||
| | `legendary_actions` | `array<object>` | Creature legendary actions. |
|
||||
|
||||
#### Complete Export/Import Example (`character`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "4c8f3824-3453-48bf-9a0e-a4c336b3bcf2",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "character",
|
||||
"display_name": "Arannis",
|
||||
"description": "Elven Ranger exploring ancient ruins.",
|
||||
"version": "1.0.0",
|
||||
"tags": ["pc", "ranger", "elf"],
|
||||
"properties": {
|
||||
"player_name": "Tom",
|
||||
"level": 3,
|
||||
"class": "Ranger",
|
||||
"subclass": "Hunter",
|
||||
"race": "Elf",
|
||||
"background": "Outlander",
|
||||
"alignment": "Chaotic Good",
|
||||
"experience_points": 900,
|
||||
"inspiration": true,
|
||||
"proficiency_bonus": 2,
|
||||
"initiative": 3,
|
||||
"speed": 35,
|
||||
"hit_points": {
|
||||
"current": 24,
|
||||
"max": 24,
|
||||
"temp": 0,
|
||||
"formula": "3d10 + 6"
|
||||
},
|
||||
"hit_dice": {
|
||||
"total": 3,
|
||||
"current": 3,
|
||||
"dice": "1d10"
|
||||
},
|
||||
"death_saves": {
|
||||
"successes": 0,
|
||||
"failures": 0
|
||||
},
|
||||
"armor_class": 15,
|
||||
"abilities": {
|
||||
"strength": 12,
|
||||
"dexterity": 16,
|
||||
"constitution": 14,
|
||||
"intelligence": 10,
|
||||
"wisdom": 15,
|
||||
"charisma": 8
|
||||
},
|
||||
"saving_throws": ["Strength +3", "Dexterity +5"],
|
||||
"skills": ["Athletics +3", "Perception +5", "Stealth +5", "Survival +5"],
|
||||
"passive_perception": 15,
|
||||
"languages": ["Common", "Elvish", "Sylvan"],
|
||||
"attacks": [
|
||||
{
|
||||
"name": "Longbow",
|
||||
"atk_bonus": "+7",
|
||||
"damage": "1d8 + 3",
|
||||
"damage_type": "Piercing",
|
||||
"range": "150/600 ft."
|
||||
}
|
||||
],
|
||||
"currency": {
|
||||
"cp": 15,
|
||||
"sp": 40,
|
||||
"ep": 0,
|
||||
"gp": 25,
|
||||
"pp": 0
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.2 Base Item Entity (`item.json`)
|
||||
|
||||
Used for general adventuring gear, tools, containers, consumables, and magic items.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `name` (`string`)
|
||||
|
||||
#### Property Fields:
|
||||
| Field | Type | Default | Description / Enum |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `name` | `string` | — | Item name (e.g. `"Rope, Hempen (50 feet)"`). |
|
||||
| `description` | `string` | `""` | Detailed description or rules text. |
|
||||
| `cost` | `object` | `{"quantity": 1, "unit": "gp"}` | Value (`quantity`: number, `unit`: `"cp"`, `"sp"`, `"ep"`, `"gp"`, `"pp"`). |
|
||||
| `weight` | `number` | `0` | Weight in pounds (lbs). |
|
||||
| `rarity` | `string` | `"common"` | `"common"`, `"uncommon"`, `"rare"`, `"very rare"`, `"legendary"`, `"artifact"`. |
|
||||
| `attunement` | `boolean` | `false` | Whether the item requires attunement. |
|
||||
| `equipped` | `boolean` | `false` | Equipped/worn status. |
|
||||
| `quantity` | `integer` | `1` | Count carried. |
|
||||
| `grants_modifiers`| `array<object>` | `[]` | Stat bonuses granted when equipped (e.g. `[{"target": "armor_class", "value": 1, "type": "bonus", "source": "Cloak of Protection"}]`). |
|
||||
|
||||
#### Example Payload (`item`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "8b5123d1-419b-4351-92b0-9b34e56711aa",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "item",
|
||||
"display_name": "Potion of Healing",
|
||||
"description": "A magical red fluid that restores hit points when consumed.",
|
||||
"properties": {
|
||||
"name": "Potion of Healing",
|
||||
"description": "You regain 2d4 + 2 hit points when you drink this potion.",
|
||||
"cost": { "quantity": 50, "unit": "gp" },
|
||||
"weight": 0.5,
|
||||
"rarity": "common",
|
||||
"attunement": false,
|
||||
"equipped": false,
|
||||
"quantity": 2
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.3 Weapon Entity (`weapon.json`)
|
||||
|
||||
Inherits all fields from `item.json` plus combat damage specifications.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `name` (`string`)
|
||||
- `damage_dice` (`string`, e.g. `"1d8"`, `"2d6"`)
|
||||
- `damage_type` (`string`: `"bludgeoning"`, `"piercing"`, or `"slashing"`)
|
||||
|
||||
#### Additional Property Fields:
|
||||
| Field | Type | Default | Description / Enum |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `weapon_category` | `string` | `"simple"` | `"simple"` or `"martial"`. |
|
||||
| `weapon_range` | `string` | `"melee"` | `"melee"` or `"ranged"`. |
|
||||
| `damage_dice` | `string` | `"1d6"` | Damage roll formula (e.g. `"1d8"`, `"2d6"`). |
|
||||
| `damage_type` | `string` | `"slashing"` | `"bludgeoning"`, `"piercing"`, `"slashing"`. |
|
||||
| `properties` | `array<string>` | `[]` | 5e weapon properties: `"ammunition"`, `"finesse"`, `"heavy"`, `"light"`, `"loading"`, `"range"`, `"reach"`, `"special"`, `"thrown"`, `"two-handed"`, `"versatile"`. |
|
||||
| `range_normal` | `integer\|null` | `null` | Normal range in feet (e.g. `20` for dagger, `150` for longbow). |
|
||||
| `range_long` | `integer\|null` | `null` | Disadvantage range in feet (e.g. `60` for dagger, `600` for longbow). |
|
||||
| `versatile_dice`| `string\|null` | `null` | Alternate two-handed damage die (e.g. `"1d10"` for longsword/battleaxe). |
|
||||
|
||||
#### Example Payload (`weapon`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "7f139d42-26cb-4029-a1b7-6ec7e34ef399",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "weapon",
|
||||
"display_name": "Longsword",
|
||||
"description": "A versatile martial melee blade.",
|
||||
"properties": {
|
||||
"name": "Longsword",
|
||||
"description": "A classic double-edged straight blade with a cruciform hilt.",
|
||||
"cost": { "quantity": 15, "unit": "gp" },
|
||||
"weight": 3.0,
|
||||
"rarity": "common",
|
||||
"weapon_category": "martial",
|
||||
"weapon_range": "melee",
|
||||
"damage_dice": "1d8",
|
||||
"damage_type": "slashing",
|
||||
"properties": ["versatile"],
|
||||
"versatile_dice": "1d10",
|
||||
"equipped": true,
|
||||
"quantity": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.4 Armor Entity (`armor.json`)
|
||||
|
||||
Inherits all fields from `item.json` plus protection and armor class modifiers.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `name` (`string`)
|
||||
- `armor_category` (`string`: `"light"`, `"medium"`, or `"heavy"`)
|
||||
- `ac_base` (`integer`, minimum `0`)
|
||||
|
||||
#### Additional Property Fields:
|
||||
| Field | Type | Description |
|
||||
| :--- | :--- | :--- |
|
||||
| `armor_category` | `string` | `"light"`, `"medium"`, or `"heavy"`. |
|
||||
| `ac_base` | `integer` | Base AC provided (e.g. `11` for Leather, `14` for Scale Mail, `18` for Plate). |
|
||||
| `ac_modifier` | `integer` | Bonus over base 10 (e.g. `+1` for Leather, `+4` for Scale Mail, `+8` for Plate). |
|
||||
| `dex_cap` | `integer\|null` | Max Dex modifier allowed: `null` (uncapped, light armor), `2` (medium armor cap), `0` (heavy armor, no Dex). |
|
||||
| `strength_requirement` | `integer\|null` | Minimum Strength score required without -10 speed penalty (e.g. `13` or `15`). `null` if none. |
|
||||
| `stealth_disadvantage` | `boolean` | `true` if wearing this armor imposes disadvantage on Stealth checks. |
|
||||
|
||||
#### Example Payload (`armor`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "21950d81-817b-40fa-9861-bbd51381aa01",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "armor",
|
||||
"display_name": "Breastplate",
|
||||
"description": "Fitted metal chest armor with leather backing.",
|
||||
"properties": {
|
||||
"name": "Breastplate",
|
||||
"description": "Protects the wearer's torso while leaving limbs free.",
|
||||
"cost": { "quantity": 400, "unit": "gp" },
|
||||
"weight": 20.0,
|
||||
"rarity": "common",
|
||||
"armor_category": "medium",
|
||||
"ac_base": 14,
|
||||
"dex_cap": 2,
|
||||
"strength_requirement": null,
|
||||
"stealth_disadvantage": false,
|
||||
"equipped": true,
|
||||
"quantity": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.5 Shield Entity (`shield.json`)
|
||||
|
||||
Inherits all fields from `item.json` plus shield-specific AC bonus and penalties.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `name` (`string`)
|
||||
- `ac_bonus` (`integer`, standard 5e value is `2`)
|
||||
|
||||
#### Additional Property Fields:
|
||||
| Field | Type | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `ac_bonus` | `integer` | `2` | Armor Class bonus added when wielded. |
|
||||
| `strength_requirement` | `integer\|null` | `null` | Minimum Strength required to wield without penalty. |
|
||||
| `stealth_disadvantage` | `boolean` | `false` | Imposes disadvantage on Stealth rolls if `true`. |
|
||||
|
||||
#### Example Payload (`shield`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "43e2f901-b841-47fa-89fa-948123da6711",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "shield",
|
||||
"display_name": "Shield",
|
||||
"description": "A wooden or metal shield carried in one hand.",
|
||||
"properties": {
|
||||
"name": "Shield",
|
||||
"description": "Wielding a shield increases your Armor Class by 2.",
|
||||
"cost": { "quantity": 10, "unit": "gp" },
|
||||
"weight": 6.0,
|
||||
"rarity": "common",
|
||||
"ac_bonus": 2,
|
||||
"strength_requirement": null,
|
||||
"stealth_disadvantage": false,
|
||||
"equipped": true,
|
||||
"quantity": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4.6 Spell Entity (`spell.json`)
|
||||
|
||||
Defines complete 5e spells, cantrips, and rituals.
|
||||
|
||||
#### Required Fields in `properties`:
|
||||
- `level` (`integer`, 0–9; 0 represents cantrips)
|
||||
- `school` (`string`: abjuration, conjuration, divination, enchantment, evocation, illusion, necromancy, transmutation)
|
||||
- `casting_time` (`string`, e.g. `"1 action"`, `"1 bonus action"`, `"1 reaction"`)
|
||||
- `range` (`string`, e.g. `"Self"`, `"Touch"`, `"60 feet"`, `"120 feet"`)
|
||||
- `duration` (`string`, e.g. `"Instantaneous"`, `"Concentration, up to 1 minute"`)
|
||||
- `description` (`string`, the full rules text)
|
||||
|
||||
#### Additional Property Fields:
|
||||
| Field | Type | Default | Description |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `components` | `object` | `{"verbal": true, "somatic": true, "material": false}` | Component breakdown: `verbal` (`bool`), `somatic` (`bool`), `material` (`bool`), `material_specified` (`string`). |
|
||||
| `concentration` | `boolean` | `false` | Requires spellcaster concentration. |
|
||||
| `ritual` | `boolean` | `false` | Can be cast as a 10-minute ritual without expending a spell slot. |
|
||||
| `higher_levels` | `string` | `""` | Additional damage or target effects when upcast with higher level slots. |
|
||||
|
||||
#### Example Payload (`spell`):
|
||||
```json
|
||||
{
|
||||
"$schema": "../../../schema/entity.json",
|
||||
"uuid": "e58123f0-49a1-4322-9fa1-01f4218aef82",
|
||||
"ruleset_id": "open5e",
|
||||
"entity_type": "spell",
|
||||
"display_name": "Fireball",
|
||||
"description": "A bright streak that blossoms with a low roar into an explosion of flame.",
|
||||
"properties": {
|
||||
"level": 3,
|
||||
"school": "evocation",
|
||||
"casting_time": "1 action",
|
||||
"range": "150 feet",
|
||||
"duration": "Instantaneous",
|
||||
"components": {
|
||||
"verbal": true,
|
||||
"somatic": true,
|
||||
"material": true,
|
||||
"material_specified": "a tiny ball of bat guano and sulfur"
|
||||
},
|
||||
"concentration": false,
|
||||
"ritual": false,
|
||||
"description": "Each creature in a 20-foot-radius sphere centered on that point must make a Dexterity saving throw. A target takes 8d6 fire damage on a failed save, or half as much damage on a successful one.",
|
||||
"higher_levels": "When you cast this spell using a spell slot of 4th level or higher, the damage increases by 1d6 for each slot level above 3rd."
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Validation & Conformance Rules
|
||||
|
||||
When parsing or validating imported entities, external apps should follow these rules:
|
||||
|
||||
1. **Top-Level Envelope Validation**:
|
||||
- Verify `ruleset_id === "open5e"`.
|
||||
- Ensure `entity_type` matches one of the 6 registered entity types (`character`, `item`, `weapon`, `armor`, `shield`, `spell`).
|
||||
- Ensure `uuid` is a valid unique string or UUID v4.
|
||||
2. **Sub-Schema Dispatch**:
|
||||
- Extract `properties` and validate against the corresponding `entities/{entity_type}.json` schema.
|
||||
3. **Graceful Defaults**:
|
||||
- If an optional field is missing (such as `temp` HP or `currency.ep`), assume default 0 or empty structures rather than throwing a validation exception.
|
||||
4. **Extensibility**:
|
||||
- Extra custom fields in `properties` are permitted (`additionalProperties: true` in base schemas) to allow apps to preserve their own proprietary metadata (e.g. inventory IDs, campaign notes, asset URLs) without breaking import/export compatibility.
|
||||
|
||||
---
|
||||
|
||||
## 6. Import & Export Implementation Checklist
|
||||
|
||||
When implementing an integration adapter in another application:
|
||||
|
||||
- [ ] **Export Checklist**:
|
||||
- [ ] Generate or preserve a stable `uuid` (UUID v4).
|
||||
- [ ] Set `"ruleset_id": "open5e"`.
|
||||
- [ ] Set `"entity_type"` to the exact entity type slug.
|
||||
- [ ] Populate `"display_name"` with the character/item name.
|
||||
- [ ] Place all game attributes inside the `"properties"` root object.
|
||||
- [ ] Export numbers as JSON numbers (not stringified quotes, except for dice formulas like `"1d8 + 3"`).
|
||||
- [ ] Format ISO 8601 dates for `created_at` and `updated_at`.
|
||||
|
||||
- [ ] **Import Checklist**:
|
||||
- [ ] Check `"ruleset_id" === "open5e"`.
|
||||
- [ ] Inspect `"entity_type"` to determine character vs. item vs. spell importer.
|
||||
- [ ] Read `properties.abilities` (handle both raw numeric maps and modifier objects).
|
||||
- [ ] Safely read `properties.hit_points` (`current`, `max`, `temp`).
|
||||
- [ ] Safely import or map `properties.attacks` and `properties.currency`.
|
||||
- [ ] If importing items or weapons into a character inventory, unpack nested items into the character's equipment list.
|
||||
127
rulesets/open5e/manifest.json
Normal file
127
rulesets/open5e/manifest.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"$schema": "../../schema/manifest.json",
|
||||
"id": "open5e",
|
||||
"uuid": "7a35e4d2-f67b-4890-a292-6a7593c72b21",
|
||||
"name": "Open5e (D&D 5th Edition SRD)",
|
||||
"version": "1.0.0",
|
||||
"description": "5th Edition System Reference Document ruleset providing entity schemas and sheet templates.",
|
||||
"license": "OGL 1.0a",
|
||||
"author": {
|
||||
"name": "Open5e Community",
|
||||
"url": "https://open5e.com"
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"id": "character",
|
||||
"name": "Player Character",
|
||||
"description": "A player character with attributes, proficiencies, classes, equipment, and spells.",
|
||||
"schema": "entities/character.json",
|
||||
"default_template": "character_sheet",
|
||||
"templates": [
|
||||
"character_sheet",
|
||||
"character_sheet_alt",
|
||||
"spellcasting_sheet",
|
||||
"stat_block",
|
||||
"character_card",
|
||||
"character_list_item"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "item",
|
||||
"name": "Item / Gear",
|
||||
"description": "Generic items, equipment, adventuring gear, and magical items.",
|
||||
"schema": "entities/item.json"
|
||||
},
|
||||
{
|
||||
"id": "weapon",
|
||||
"name": "Weapon",
|
||||
"description": "Simple and martial melee and ranged weapons.",
|
||||
"schema": "entities/weapon.json"
|
||||
},
|
||||
{
|
||||
"id": "armor",
|
||||
"name": "Armor",
|
||||
"description": "Wearable armor with base AC, dex caps, and strength/stealth rules.",
|
||||
"schema": "entities/armor.json"
|
||||
},
|
||||
{
|
||||
"id": "shield",
|
||||
"name": "Shield",
|
||||
"description": "Equippable shields providing armor class bonuses.",
|
||||
"schema": "entities/shield.json"
|
||||
},
|
||||
{
|
||||
"id": "spell",
|
||||
"name": "Spell",
|
||||
"description": "5e Spells with level, school, casting time, range, and components.",
|
||||
"schema": "entities/spell.json"
|
||||
}
|
||||
],
|
||||
"templates": [
|
||||
{
|
||||
"id": "character_sheet",
|
||||
"name": "Standard Character Sheet",
|
||||
"description": "Full interactive 5e character sheet matching the official 3-column print layout.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "sheet",
|
||||
"engine": "html",
|
||||
"html": "templates/character_sheet.html",
|
||||
"css": ["templates/character_sheet.css"],
|
||||
"js": ["templates/character_sheet.js"]
|
||||
},
|
||||
{
|
||||
"id": "character_sheet_alt",
|
||||
"name": "Alternative Character Sheet",
|
||||
"description": "5e character sheet with saving throws and skills grouped by ability score.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "sheet",
|
||||
"engine": "html",
|
||||
"html": "templates/character_sheet_alt.html",
|
||||
"css": ["templates/character_sheet_alt.css"]
|
||||
},
|
||||
{
|
||||
"id": "spellcasting_sheet",
|
||||
"name": "Spellcasting Sheet",
|
||||
"description": "Dedicated 5e spellcasting sheet with cantrips, spell slots 1-9, and spell save DC.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "sheet",
|
||||
"engine": "html",
|
||||
"html": "templates/spellcasting_sheet.html",
|
||||
"css": ["templates/spellcasting_sheet.css"]
|
||||
},
|
||||
{
|
||||
"id": "stat_block",
|
||||
"name": "Creature Stat Block",
|
||||
"description": "Monster manual style stat block with full actions, traits, and combat statistics.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "view",
|
||||
"engine": "html",
|
||||
"html": "templates/stat_block.html",
|
||||
"css": ["templates/stat_block.css"]
|
||||
},
|
||||
{
|
||||
"id": "character_card",
|
||||
"name": "NPC Character Card",
|
||||
"description": "Compact NPC card with 8 stat boxes and quick action summaries.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "card",
|
||||
"engine": "html",
|
||||
"html": "templates/character_card.html",
|
||||
"css": ["templates/character_card.css"]
|
||||
},
|
||||
{
|
||||
"id": "character_list_item",
|
||||
"name": "Character List Item",
|
||||
"description": "Uniform fixed-height quick-reference list item card with non-wrapping horizontal action row.",
|
||||
"entity_types": ["character"],
|
||||
"mode": "card",
|
||||
"engine": "html",
|
||||
"html": "templates/character_list_item.html",
|
||||
"css": ["templates/character_list_item.css"]
|
||||
}
|
||||
],
|
||||
"assets": {
|
||||
"path": "assets",
|
||||
"images": []
|
||||
}
|
||||
}
|
||||
155
rulesets/open5e/templates/character_card.css
Normal file
155
rulesets/open5e/templates/character_card.css
Normal file
@@ -0,0 +1,155 @@
|
||||
.card-page-wrapper {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background-color: #f4f6f8;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.npc-card {
|
||||
width: 100%;
|
||||
max-width: 580px;
|
||||
background: #ffffff;
|
||||
border: 1.5px solid #922610;
|
||||
border-radius: 4px;
|
||||
padding: 16px 18px 18px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: #922610;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.card-cr {
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-style: italic;
|
||||
font-size: 13.5px;
|
||||
color: #666;
|
||||
margin: 4px 0 14px 0;
|
||||
}
|
||||
|
||||
.stat-boxes-row {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border: 1.5px solid #222;
|
||||
border-radius: 2px;
|
||||
padding: 6px 2px 4px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 48px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-advantage {
|
||||
position: absolute;
|
||||
top: 1.5px;
|
||||
left: 2.5px;
|
||||
font-size: 8.5px;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.badge-proficiency {
|
||||
position: absolute;
|
||||
top: 1.5px;
|
||||
right: 2.5px;
|
||||
font-size: 8.5px;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-mod {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #111;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-code {
|
||||
font-size: 9.5px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-top: 3px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.action-boxes-grid {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.action-card-box {
|
||||
flex: 1 1 calc(50% - 8px);
|
||||
min-width: 140px;
|
||||
border: 1px solid #333;
|
||||
border-radius: 2px;
|
||||
padding: 8px 10px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.action-box-title {
|
||||
font-size: 13.5px;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.action-box-desc {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.35;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
/* Single action takes full width */
|
||||
.action-boxes-grid > .action-card-box:only-child {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 440px) {
|
||||
.card-page-wrapper {
|
||||
padding: 8px;
|
||||
}
|
||||
.stat-boxes-row {
|
||||
gap: 3px;
|
||||
}
|
||||
.stat-mod {
|
||||
font-size: 13px;
|
||||
}
|
||||
.action-card-box {
|
||||
flex: 1 1 100%;
|
||||
}
|
||||
}
|
||||
|
||||
118
rulesets/open5e/templates/character_card.html
Normal file
118
rulesets/open5e/templates/character_card.html
Normal file
@@ -0,0 +1,118 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - Character Card</title>
|
||||
<link rel="stylesheet" href="character_card.css">
|
||||
</head>
|
||||
<body class="card-page-wrapper">
|
||||
<div class="npc-card">
|
||||
<!-- Top Bar: Name & Challenge/Level -->
|
||||
<div class="card-top">
|
||||
<h2 class="card-name">{{display_name}}</h2>
|
||||
<div class="card-cr">
|
||||
{{#if properties.challenge_rating}}
|
||||
CR {{properties.challenge_rating}}
|
||||
{{else if properties.cr}}
|
||||
CR {{properties.cr}}
|
||||
{{else if properties.level}}
|
||||
LVL {{properties.level}}
|
||||
{{else}}
|
||||
CR 1
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subtitle: Size, Type, Subtype, Alignment -->
|
||||
<div class="card-subtitle">
|
||||
{{#if properties.size}}{{properties.size}}{{else}}medium{{/if}}
|
||||
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{else if properties.race}} ({{properties.race}}){{/if}},
|
||||
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
|
||||
</div>
|
||||
|
||||
<!-- 8 Stat Boxes Row: S, D, C, I, W, Ch, AC, HP -->
|
||||
<div class="stat-boxes-row">
|
||||
<!-- STR -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "str"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "str"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.strength}}</span>
|
||||
<span class="stat-code">S</span>
|
||||
</div>
|
||||
|
||||
<!-- DEX -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "dex"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "dex"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.dexterity}}</span>
|
||||
<span class="stat-code">D</span>
|
||||
</div>
|
||||
|
||||
<!-- CON -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "con"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "con"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.constitution}}</span>
|
||||
<span class="stat-code">C</span>
|
||||
</div>
|
||||
|
||||
<!-- INT -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "int"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "int"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.intelligence}}</span>
|
||||
<span class="stat-code">I</span>
|
||||
</div>
|
||||
|
||||
<!-- WIS -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "wis"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "wis"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.wisdom}}</span>
|
||||
<span class="stat-code">W</span>
|
||||
</div>
|
||||
|
||||
<!-- CHA -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "cha"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "cha"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.charisma}}</span>
|
||||
<span class="stat-code">Ch</span>
|
||||
</div>
|
||||
|
||||
<!-- AC -->
|
||||
<div class="stat-box">
|
||||
<span class="stat-mod">{{val properties.armor_class}}</span>
|
||||
<span class="stat-code">AC</span>
|
||||
</div>
|
||||
|
||||
<!-- HP -->
|
||||
<div class="stat-box">
|
||||
<span class="stat-mod">
|
||||
{{#if properties.hit_points.current}}
|
||||
{{properties.hit_points.current}}
|
||||
{{else if properties.hit_points.max}}
|
||||
{{properties.hit_points.max}}
|
||||
{{else}}
|
||||
{{val properties.hit_points}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="stat-code">HP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action Summary Boxes -->
|
||||
{{#if properties.actions}}
|
||||
<div class="action-boxes-grid">
|
||||
{{#each properties.actions}}
|
||||
<div class="action-card-box">
|
||||
<div class="action-box-title">{{this.name}}</div>
|
||||
<div class="action-box-desc">{{formatText this.desc}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
245
rulesets/open5e/templates/character_list_item.css
Normal file
245
rulesets/open5e/templates/character_list_item.css
Normal file
@@ -0,0 +1,245 @@
|
||||
/* Character List Item Template - Uniform Fixed Height with Non-Wrapping Horizontal Actions */
|
||||
:root {
|
||||
--card-border: #922610;
|
||||
--card-title: #922610;
|
||||
--box-border: #222222;
|
||||
--bg-page: #f4f6f8;
|
||||
--bg-card: #ffffff;
|
||||
--bg-box: #ffffff;
|
||||
--text-main: #111111;
|
||||
--text-muted: #666666;
|
||||
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
body.list-item-page-wrapper {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background-color: var(--bg-page);
|
||||
font-family: var(--font-sans);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Card has fixed height so all list cards are identical height */
|
||||
.npc-list-card {
|
||||
width: 100%;
|
||||
max-width: 580px;
|
||||
height: 236px;
|
||||
background: var(--bg-card);
|
||||
border: 1.5px solid var(--card-border);
|
||||
border-radius: 4px;
|
||||
padding: 12px 14px;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.card-top {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.card-name {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: var(--card-title);
|
||||
line-height: 1.15;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.card-cr {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
white-space: nowrap;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.card-subtitle {
|
||||
font-style: italic;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin: 2px 0 10px 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 8 Stat Boxes */
|
||||
.stat-boxes-row {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
border: 1.2px solid var(--box-border);
|
||||
border-radius: 2px;
|
||||
padding: 4px 1px 3px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 44px;
|
||||
background: var(--bg-box);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.badge-advantage {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 2px;
|
||||
font-size: 8px;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.badge-proficiency {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 2px;
|
||||
font-size: 8px;
|
||||
font-weight: 800;
|
||||
color: #111;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.stat-mod {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: var(--text-main);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.stat-code {
|
||||
font-size: 9px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-top: 2px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* Actions Row: Single Non-Wrapping Row with Horizontal Scrolling */
|
||||
.action-row-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
min-height: 0; /* Allows container to shrink inside flex parent */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.action-boxes-row {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 8px;
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: #ccc transparent;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
|
||||
.action-boxes-row::-webkit-scrollbar {
|
||||
height: 4px;
|
||||
}
|
||||
|
||||
.action-boxes-row::-webkit-scrollbar-thumb {
|
||||
background: #ccc;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.action-boxes-row::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
/* Individual Action Card */
|
||||
.action-card-box {
|
||||
flex: 0 0 170px;
|
||||
max-width: 240px;
|
||||
border: 1px solid #333;
|
||||
border-radius: 2px;
|
||||
padding: 6px 8px;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* When only 1 action exists (e.g. Acolyte Club), expand full width */
|
||||
.action-boxes-row > .action-card-box:only-child {
|
||||
flex: 1 0 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.action-box-title {
|
||||
font-size: 12.5px;
|
||||
font-weight: 700;
|
||||
color: #222;
|
||||
margin-bottom: 2px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.action-box-desc {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.25;
|
||||
color: #444;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Empty State (e.g. Unnamed Monster) */
|
||||
.no-actions-box {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #ccc;
|
||||
border-radius: 2px;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.no-actions-text {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 440px) {
|
||||
.list-item-page-wrapper {
|
||||
padding: 6px;
|
||||
}
|
||||
.stat-boxes-row {
|
||||
gap: 3px;
|
||||
}
|
||||
.stat-mod {
|
||||
font-size: 12px;
|
||||
}
|
||||
.action-card-box {
|
||||
flex: 0 0 145px;
|
||||
}
|
||||
}
|
||||
|
||||
127
rulesets/open5e/templates/character_list_item.html
Normal file
127
rulesets/open5e/templates/character_list_item.html
Normal file
@@ -0,0 +1,127 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/rulesets/open5e/templates/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - List Item</title>
|
||||
<link rel="stylesheet" href="character_list_item.css">
|
||||
</head>
|
||||
<body class="list-item-page-wrapper">
|
||||
<div class="npc-list-card">
|
||||
|
||||
<!-- Header: Name & Challenge/Level -->
|
||||
<div class="card-top">
|
||||
<h2 class="card-name">{{display_name}}</h2>
|
||||
<div class="card-cr">
|
||||
{{#if properties.challenge_rating}}
|
||||
CR {{properties.challenge_rating}}
|
||||
{{else if properties.cr}}
|
||||
CR {{properties.cr}}
|
||||
{{else if properties.level}}
|
||||
LVL {{properties.level}}
|
||||
{{else}}
|
||||
CR 1
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subtitle: Size, Type, Subtype, Alignment -->
|
||||
<div class="card-subtitle">
|
||||
{{#if properties.size}}{{properties.size}}{{else}}medium{{/if}}
|
||||
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{else if properties.race}} ({{properties.race}}){{/if}},
|
||||
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
|
||||
</div>
|
||||
|
||||
<!-- 8 Stat Boxes Row: S, D, C, I, W, Ch, AC, HP -->
|
||||
<div class="stat-boxes-row">
|
||||
<!-- STR -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "str"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "str"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.strength}}</span>
|
||||
<span class="stat-code">S</span>
|
||||
</div>
|
||||
|
||||
<!-- DEX -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "dex"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "dex"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.dexterity}}</span>
|
||||
<span class="stat-code">D</span>
|
||||
</div>
|
||||
|
||||
<!-- CON -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "con"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "con"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.constitution}}</span>
|
||||
<span class="stat-code">C</span>
|
||||
</div>
|
||||
|
||||
<!-- INT -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "int"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "int"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.intelligence}}</span>
|
||||
<span class="stat-code">I</span>
|
||||
</div>
|
||||
|
||||
<!-- WIS -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "wis"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "wis"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.wisdom}}</span>
|
||||
<span class="stat-code">W</span>
|
||||
</div>
|
||||
|
||||
<!-- CHA -->
|
||||
<div class="stat-box">
|
||||
<span class="badge-advantage">{{advBadge properties "cha"}}</span>
|
||||
<span class="badge-proficiency">{{profBadge properties.saving_throws "cha"}}</span>
|
||||
<span class="stat-mod">{{modText properties.abilities.charisma}}</span>
|
||||
<span class="stat-code">Ch</span>
|
||||
</div>
|
||||
|
||||
<!-- AC -->
|
||||
<div class="stat-box">
|
||||
<span class="stat-mod">{{val properties.armor_class}}</span>
|
||||
<span class="stat-code">AC</span>
|
||||
</div>
|
||||
|
||||
<!-- HP -->
|
||||
<div class="stat-box">
|
||||
<span class="stat-mod">
|
||||
{{#if properties.hit_points.current}}
|
||||
{{properties.hit_points.current}}
|
||||
{{else if properties.hit_points.max}}
|
||||
{{properties.hit_points.max}}
|
||||
{{else}}
|
||||
{{val properties.hit_points}}
|
||||
{{/if}}
|
||||
</span>
|
||||
<span class="stat-code">HP</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Actions Row: Single Non-Wrapping Row with Horizontal Scroll -->
|
||||
<div class="action-row-container">
|
||||
{{#if properties.actions}}
|
||||
<div class="action-boxes-row">
|
||||
{{#each properties.actions}}
|
||||
<div class="action-card-box">
|
||||
<div class="action-box-title">{{this.name}}</div>
|
||||
<div class="action-box-desc">{{formatText this.desc}}</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="no-actions-box">
|
||||
<span class="no-actions-text">— No listed actions —</span>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
585
rulesets/open5e/templates/character_sheet.css
Normal file
585
rulesets/open5e/templates/character_sheet.css
Normal file
@@ -0,0 +1,585 @@
|
||||
/* D&D 5e Standard Character Sheet Styles */
|
||||
:root {
|
||||
--border-color: #2b2b2b;
|
||||
--accent-red: #922610;
|
||||
--bg-page: #eaedf0;
|
||||
--bg-sheet: #ffffff;
|
||||
--bg-box: #f9fbfd;
|
||||
--text-main: #1e1e1e;
|
||||
--text-muted: #666666;
|
||||
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-serif: "Libre Baskerville", "Georgia", serif;
|
||||
}
|
||||
|
||||
body.sheet-page-wrapper {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: var(--bg-page);
|
||||
font-family: var(--font-sans);
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.character-sheet-container {
|
||||
width: 100%;
|
||||
max-width: 1050px;
|
||||
background: var(--bg-sheet);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 20px 24px 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ================= HEADER ================= */
|
||||
.sheet-header {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-name-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.character-name {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 26px;
|
||||
color: var(--accent-red);
|
||||
margin: 0 0 6px 0;
|
||||
line-height: 1.1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.header-label {
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.header-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 12px;
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.detail-val {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* ================= 3-COLUMN MAIN GRID ================= */
|
||||
.sheet-main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 340px 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sheet-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* Generic Section Box */
|
||||
.section-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
background: #ffffff;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.box-bottom-label {
|
||||
text-align: center;
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 6px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* ================= LEFT COLUMN ================= */
|
||||
.abilities-and-skills-row {
|
||||
display: grid;
|
||||
grid-template-columns: 88px 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
/* 6 Abilities */
|
||||
.abilities-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.ability-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 6px 4px 4px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--bg-box);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ab-title {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--accent-red);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.ab-modifier {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
margin: 2px 0;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.ab-score-pill {
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
border: 1px solid #999;
|
||||
border-radius: 10px;
|
||||
padding: 1px 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
/* Skills & Saves Stack */
|
||||
.skills-and-saves-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.top-stat-pill {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
padding: 4px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.pill-checkbox {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.pill-checkbox.checked {
|
||||
background-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.pill-value {
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
border-right: 1px solid #ccc;
|
||||
padding-right: 8px;
|
||||
}
|
||||
|
||||
.pill-label {
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Proficiency Lists (Saves & Skills) */
|
||||
.prof-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.prof-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 11.5px;
|
||||
margin-bottom: 3.5px;
|
||||
gap: 6px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.prof-item .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prof-item .dot.filled {
|
||||
background-color: var(--accent-red);
|
||||
border-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.prof-bonus {
|
||||
font-weight: 700;
|
||||
width: 22px;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.prof-name {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.skill-attr {
|
||||
color: #888;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* Passive Perception */
|
||||
.passive-perception-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 18px;
|
||||
padding: 6px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.passive-score {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.passive-label {
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.prof-block-content {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.prof-line {
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* ================= MIDDLE COLUMN ================= */
|
||||
.combat-top-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.combat-badge {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 8px 4px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.badge-ac {
|
||||
border-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.badge-value {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.badge-label {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
/* HP Boxes */
|
||||
.hp-max-line {
|
||||
font-size: 10.5px;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.hp-current-val, .temp-hp-val {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.hd-and-death-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.half-box {
|
||||
padding: 6px 8px;
|
||||
}
|
||||
|
||||
.hd-total-line {
|
||||
font-size: 9.5px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.hd-current-val {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.death-save-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.ds-bubbles {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.ds-bubbles .bubble {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ds-bubbles .bubble.filled {
|
||||
background-color: var(--accent-red);
|
||||
}
|
||||
|
||||
/* Attacks Table */
|
||||
.attacks-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 11px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.attacks-table th {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 2px 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.attacks-table td {
|
||||
padding: 5px 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.atk-name {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.atk-bonus {
|
||||
font-weight: 700;
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.attack-notes {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.35;
|
||||
color: #444;
|
||||
margin-top: 6px;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.attack-note-item {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
/* Equipment */
|
||||
.equipment-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.coins-stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.coin-row {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.coin-tag {
|
||||
font-weight: bold;
|
||||
font-size: 8.5px;
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.coin-val {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.items-list-container {
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 3px 0;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.equipped-tag {
|
||||
color: var(--accent-red);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.item-meta {
|
||||
color: #777;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
/* ================= RIGHT COLUMN ================= */
|
||||
.roleplay-box {
|
||||
min-height: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.box-content {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.4;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.box-content p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.empty-hint {
|
||||
color: #999;
|
||||
font-style: italic;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.features-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.features-content {
|
||||
font-size: 11.5px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.feature-name {
|
||||
color: var(--accent-red);
|
||||
display: block;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.feature-desc {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 900px) {
|
||||
.sheet-header {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.sheet-main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.abilities-and-skills-row {
|
||||
grid-template-columns: 90px 1fr;
|
||||
}
|
||||
}
|
||||
511
rulesets/open5e/templates/character_sheet.html
Normal file
511
rulesets/open5e/templates/character_sheet.html
Normal file
@@ -0,0 +1,511 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/rulesets/open5e/templates/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - Character Sheet</title>
|
||||
<link rel="stylesheet" href="character_sheet.css">
|
||||
</head>
|
||||
<body class="sheet-page-wrapper">
|
||||
<div class="character-sheet-container">
|
||||
|
||||
<!-- Top Header Banner -->
|
||||
<header class="sheet-header">
|
||||
<div class="header-name-box">
|
||||
<h1 class="character-name">{{display_name}}</h1>
|
||||
<label class="header-label">CHARACTER NAME</label>
|
||||
</div>
|
||||
<div class="header-details-grid">
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.class}}{{#if properties.subclass}} ({{properties.subclass}}){{/if}} {{properties.level}}</div>
|
||||
<label class="header-label">CLASS & LEVEL</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}—{{/if}}</div>
|
||||
<label class="header-label">BACKGROUND</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.player_name}}{{properties.player_name}}{{else}}—{{/if}}</div>
|
||||
<label class="header-label">PLAYER NAME</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.race}}</div>
|
||||
<label class="header-label">RACE</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.alignment}}</div>
|
||||
<label class="header-label">ALIGNMENT</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.experience_points}}{{properties.experience_points}}{{else}}0{{/if}} XP</div>
|
||||
<label class="header-label">EXPERIENCE POINTS</label>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main 3-Column Body -->
|
||||
<main class="sheet-main-grid">
|
||||
|
||||
<!-- ================= LEFT COLUMN ================= -->
|
||||
<section class="sheet-col col-left">
|
||||
|
||||
<div class="abilities-and-skills-row">
|
||||
<!-- 6 Ability Score Boxes -->
|
||||
<div class="abilities-stack">
|
||||
<!-- STR -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">STRENGTH</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.strength}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.strength}}</div>
|
||||
</div>
|
||||
<!-- DEX -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">DEXTERITY</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.dexterity}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.dexterity}}</div>
|
||||
</div>
|
||||
<!-- CON -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">CONSTITUTION</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.constitution}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.constitution}}</div>
|
||||
</div>
|
||||
<!-- INT -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">INTELLIGENCE</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.intelligence}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.intelligence}}</div>
|
||||
</div>
|
||||
<!-- WIS -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">WISDOM</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.wisdom}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.wisdom}}</div>
|
||||
</div>
|
||||
<!-- CHA -->
|
||||
<div class="ability-box">
|
||||
<span class="ab-title">CHARISMA</span>
|
||||
<span class="ab-modifier">{{modText properties.abilities.charisma}}</span>
|
||||
<div class="ab-score-pill">{{val properties.abilities.charisma}}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Proficiency, Inspiration, Saves & Skills -->
|
||||
<div class="skills-and-saves-stack">
|
||||
|
||||
<!-- Inspiration & Proficiency Bonus -->
|
||||
<div class="top-stat-pill">
|
||||
<span class="pill-checkbox {{#if properties.inspiration}}checked{{/if}}"></span>
|
||||
<span class="pill-label">INSPIRATION</span>
|
||||
</div>
|
||||
<div class="top-stat-pill">
|
||||
<span class="pill-value">+{{calcProf properties.level properties.cr}}</span>
|
||||
<span class="pill-label">PROFICIENCY BONUS</span>
|
||||
</div>
|
||||
|
||||
<!-- Saving Throws Box -->
|
||||
<div class="section-box saves-box">
|
||||
<ul class="prof-list">
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'str')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'strength'}}</span>
|
||||
<span class="prof-name">Strength</span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'dex')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'dexterity'}}</span>
|
||||
<span class="prof-name">Dexterity</span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'con')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'constitution'}}</span>
|
||||
<span class="prof-name">Constitution</span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'int')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'intelligence'}}</span>
|
||||
<span class="prof-name">Intelligence</span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'wis')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'wisdom'}}</span>
|
||||
<span class="prof-name">Wisdom</span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'cha')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{saveBonus properties 'charisma'}}</span>
|
||||
<span class="prof-name">Charisma</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="box-bottom-label">SAVING THROWS</div>
|
||||
</div>
|
||||
|
||||
<!-- Skills Box (Alphabetical 18 Skills) -->
|
||||
<div class="section-box skills-box">
|
||||
<ul class="prof-list">
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'acrobatics')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'acrobatics' 'dexterity'}}</span>
|
||||
<span class="prof-name">Acrobatics <small class="skill-attr">(Dex)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'animal_handling')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'animal_handling' 'wisdom'}}</span>
|
||||
<span class="prof-name">Animal Handling <small class="skill-attr">(Wis)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'arcana')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'arcana' 'intelligence'}}</span>
|
||||
<span class="prof-name">Arcana <small class="skill-attr">(Int)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'athletics')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'athletics' 'strength'}}</span>
|
||||
<span class="prof-name">Athletics <small class="skill-attr">(Str)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'deception')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'deception' 'charisma'}}</span>
|
||||
<span class="prof-name">Deception <small class="skill-attr">(Cha)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'history')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'history' 'intelligence'}}</span>
|
||||
<span class="prof-name">History <small class="skill-attr">(Int)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'insight')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'insight' 'wisdom'}}</span>
|
||||
<span class="prof-name">Insight <small class="skill-attr">(Wis)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'intimidation')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'intimidation' 'charisma'}}</span>
|
||||
<span class="prof-name">Intimidation <small class="skill-attr">(Cha)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'investigation')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'investigation' 'intelligence'}}</span>
|
||||
<span class="prof-name">Investigation <small class="skill-attr">(Int)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'medicine')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'medicine' 'wisdom'}}</span>
|
||||
<span class="prof-name">Medicine <small class="skill-attr">(Wis)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'nature')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'nature' 'intelligence'}}</span>
|
||||
<span class="prof-name">Nature <small class="skill-attr">(Int)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'perception')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'perception' 'wisdom'}}</span>
|
||||
<span class="prof-name">Perception <small class="skill-attr">(Wis)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'performance')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'performance' 'charisma'}}</span>
|
||||
<span class="prof-name">Performance <small class="skill-attr">(Cha)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'persuasion')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'persuasion' 'charisma'}}</span>
|
||||
<span class="prof-name">Persuasion <small class="skill-attr">(Cha)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'religion')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'religion' 'intelligence'}}</span>
|
||||
<span class="prof-name">Religion <small class="skill-attr">(Int)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'sleight_of_hand')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'sleight_of_hand' 'dexterity'}}</span>
|
||||
<span class="prof-name">Sleight of Hand <small class="skill-attr">(Dex)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'stealth')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'stealth' 'dexterity'}}</span>
|
||||
<span class="prof-name">Stealth <small class="skill-attr">(Dex)</small></span>
|
||||
</li>
|
||||
<li class="prof-item">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'survival')}}filled{{/if}}"></span>
|
||||
<span class="prof-bonus">{{skillBonus properties 'survival' 'wisdom'}}</span>
|
||||
<span class="prof-name">Survival <small class="skill-attr">(Wis)</small></span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="box-bottom-label">SKILLS</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Passive Perception Box -->
|
||||
<div class="passive-perception-box">
|
||||
<span class="passive-score">{{passivePerception properties}}</span>
|
||||
<span class="passive-label">PASSIVE WISDOM (PERCEPTION)</span>
|
||||
</div>
|
||||
|
||||
<!-- Other Proficiencies & Languages Box -->
|
||||
<div class="section-box proficiencies-box">
|
||||
<div class="prof-block-content">
|
||||
{{#if properties.other_proficiencies_and_languages.armor}}
|
||||
<div class="prof-line"><strong>Armor:</strong> {{join properties.other_proficiencies_and_languages.armor}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.other_proficiencies_and_languages.weapons}}
|
||||
<div class="prof-line"><strong>Weapons:</strong> {{join properties.other_proficiencies_and_languages.weapons}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.other_proficiencies_and_languages.tools}}
|
||||
<div class="prof-line"><strong>Tools:</strong> {{join properties.other_proficiencies_and_languages.tools}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.languages}}
|
||||
<div class="prof-line"><strong>Languages:</strong> {{join properties.languages}}</div>
|
||||
{{else if properties.other_proficiencies_and_languages.languages}}
|
||||
<div class="prof-line"><strong>Languages:</strong> {{join properties.other_proficiencies_and_languages.languages}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">OTHER PROFICIENCIES & LANGUAGES</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= MIDDLE COLUMN ================= -->
|
||||
<section class="sheet-col col-middle">
|
||||
|
||||
<!-- Combat Top Row: AC, Initiative, Speed -->
|
||||
<div class="combat-top-row">
|
||||
<div class="combat-badge badge-ac">
|
||||
<span class="badge-value">{{val properties.armor_class}}</span>
|
||||
<span class="badge-label">ARMOR CLASS</span>
|
||||
</div>
|
||||
<div class="combat-badge badge-init">
|
||||
<span class="badge-value">{{#if properties.initiative}}{{modSign properties.initiative}}{{else}}{{modText properties.abilities.dexterity}}{{/if}}</span>
|
||||
<span class="badge-label">INITIATIVE</span>
|
||||
</div>
|
||||
<div class="combat-badge badge-speed">
|
||||
<span class="badge-value">{{#if properties.speed}}{{properties.speed}} ft.{{else}}30 ft.{{/if}}</span>
|
||||
<span class="badge-label">SPEED</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Current Hit Points -->
|
||||
<div class="section-box hp-box">
|
||||
<div class="hp-max-line">
|
||||
Hit Point Maximum: <strong>{{properties.hit_points.max}}</strong>
|
||||
</div>
|
||||
<div class="hp-current-val">
|
||||
{{properties.hit_points.current}}
|
||||
</div>
|
||||
<div class="box-bottom-label">CURRENT HIT POINTS</div>
|
||||
</div>
|
||||
|
||||
<!-- Temporary Hit Points -->
|
||||
<div class="section-box temp-hp-box">
|
||||
<div class="temp-hp-val">
|
||||
{{#if properties.hit_points.temp}}{{properties.hit_points.temp}}{{else}}--{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">TEMPORARY HIT POINTS</div>
|
||||
</div>
|
||||
|
||||
<!-- Hit Dice & Death Saves Row -->
|
||||
<div class="hd-and-death-row">
|
||||
<!-- Hit Dice -->
|
||||
<div class="section-box half-box hd-box">
|
||||
<div class="hd-total-line">Total: {{#if properties.hit_dice.total}}{{properties.hit_dice.total}}{{else}}{{properties.level}}{{/if}}</div>
|
||||
<div class="hd-current-val">
|
||||
{{#if properties.hit_dice.dice}}{{properties.hit_dice.dice}}{{else if properties.hit_points.hit_dice}}{{properties.hit_points.hit_dice}}{{else}}1d10{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">HIT DICE</div>
|
||||
</div>
|
||||
|
||||
<!-- Death Saves -->
|
||||
<div class="section-box half-box death-saves-box">
|
||||
<div class="death-save-line">
|
||||
<span class="ds-title">SUCCESSES</span>
|
||||
<span class="ds-bubbles">
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 1)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 2)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 3)}}filled{{/if}}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="death-save-line">
|
||||
<span class="ds-title">FAILURES</span>
|
||||
<span class="ds-bubbles">
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 1)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 2)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 3)}}filled{{/if}}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-bottom-label">DEATH SAVES</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attacks & Spellcasting -->
|
||||
<div class="section-box attacks-box">
|
||||
<table class="attacks-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>NAME</th>
|
||||
<th>ATK BONUS</th>
|
||||
<th>DAMAGE/TYPE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if properties.attacks}}
|
||||
{{#each properties.attacks}}
|
||||
<tr>
|
||||
<td class="atk-name">{{this.name}}</td>
|
||||
<td class="atk-bonus">{{#if this.atk_bonus}}{{this.atk_bonus}}{{else}}+0{{/if}}</td>
|
||||
<td class="atk-damage">{{this.damage}} {{this.damage_type}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else if properties.actions}}
|
||||
{{#each properties.actions}}
|
||||
<tr>
|
||||
<td class="atk-name">{{this.name}}</td>
|
||||
<td class="atk-bonus">+5</td>
|
||||
<td class="atk-damage">{{this.desc}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr><td colspan="3" class="empty-hint">No attacks equipped</td></tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{#if properties.actions}}
|
||||
<div class="attack-notes">
|
||||
{{#each properties.actions}}
|
||||
<p class="attack-note-item"><em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="box-bottom-label">ATTACKS & SPELLCASTING</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment & Coins -->
|
||||
<div class="section-box equipment-box">
|
||||
<div class="equipment-layout">
|
||||
<!-- Coins Column -->
|
||||
<div class="coins-stack">
|
||||
<div class="coin-row">
|
||||
<span class="coin-tag">CP</span>
|
||||
<span class="coin-val">{{#if properties.currency.cp}}{{properties.currency.cp}}{{else}}0{{/if}}</span>
|
||||
</div>
|
||||
<div class="coin-row">
|
||||
<span class="coin-tag">SP</span>
|
||||
<span class="coin-val">{{#if properties.currency.sp}}{{properties.currency.sp}}{{else}}0{{/if}}</span>
|
||||
</div>
|
||||
<div class="coin-row">
|
||||
<span class="coin-tag">EP</span>
|
||||
<span class="coin-val">{{#if properties.currency.ep}}{{properties.currency.ep}}{{else}}0{{/if}}</span>
|
||||
</div>
|
||||
<div class="coin-row">
|
||||
<span class="coin-tag">GP</span>
|
||||
<span class="coin-val">{{#if properties.currency.gp}}{{properties.currency.gp}}{{else}}0{{/if}}</span>
|
||||
</div>
|
||||
<div class="coin-row">
|
||||
<span class="coin-tag">PP</span>
|
||||
<span class="coin-val">{{#if properties.currency.pp}}{{properties.currency.pp}}{{else}}0{{/if}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment List -->
|
||||
<div class="items-list-container">
|
||||
<ul class="items-list">
|
||||
{{#each properties.inventory}}
|
||||
<li class="item-row">
|
||||
<span class="item-name">{{this.name}}{{#if this.equipped}} <small class="equipped-tag">(E)</small>{{/if}}</span>
|
||||
<span class="item-meta">x{{this.quantity}}{{#if this.weight}} • {{this.weight}} lb{{/if}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-bottom-label">EQUIPMENT</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= RIGHT COLUMN ================= -->
|
||||
<section class="sheet-col col-right">
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">
|
||||
{{#if properties.personality_traits}}
|
||||
<p>{{formatText properties.personality_traits}}</p>
|
||||
{{else}}
|
||||
<span class="empty-hint">None</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">PERSONALITY TRAITS</div>
|
||||
</div>
|
||||
|
||||
<!-- Ideals -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">
|
||||
{{#if properties.ideals}}
|
||||
<p>{{formatText properties.ideals}}</p>
|
||||
{{else}}
|
||||
<span class="empty-hint">None</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">IDEALS</div>
|
||||
</div>
|
||||
|
||||
<!-- Bonds -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">
|
||||
{{#if properties.bonds}}
|
||||
<p>{{formatText properties.bonds}}</p>
|
||||
{{else}}
|
||||
<span class="empty-hint">None</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">BONDS</div>
|
||||
</div>
|
||||
|
||||
<!-- Flaws -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">
|
||||
{{#if properties.flaws}}
|
||||
<p>{{formatText properties.flaws}}</p>
|
||||
{{else}}
|
||||
<span class="empty-hint">None</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">FLAWS</div>
|
||||
</div>
|
||||
|
||||
<!-- Features & Traits (Large Box) -->
|
||||
<div class="section-box features-box">
|
||||
<div class="features-content">
|
||||
{{#if properties.features_and_traits}}
|
||||
{{#each properties.features_and_traits}}
|
||||
<div class="feature-item">
|
||||
<strong class="feature-name">{{this.name}}</strong>
|
||||
<p class="feature-desc">{{formatText this.desc}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else if properties.traits}}
|
||||
{{#each properties.traits}}
|
||||
<div class="feature-item">
|
||||
<strong class="feature-name">{{this.name}}</strong>
|
||||
<p class="feature-desc">{{formatText this.desc}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">FEATURES & TRAITS</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
5
rulesets/open5e/templates/character_sheet.js
Normal file
5
rulesets/open5e/templates/character_sheet.js
Normal file
@@ -0,0 +1,5 @@
|
||||
// Starter client-side script for open5e character sheet
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
console.log('Open5e Character Sheet template initialized.');
|
||||
});
|
||||
|
||||
487
rulesets/open5e/templates/character_sheet_alt.css
Normal file
487
rulesets/open5e/templates/character_sheet_alt.css
Normal file
@@ -0,0 +1,487 @@
|
||||
/* D&D 5e Alternative (Ability-Grouped) Character Sheet Styles */
|
||||
:root {
|
||||
--border-color: #2b2b2b;
|
||||
--accent-red: #922610;
|
||||
--bg-page: #eaedf0;
|
||||
--bg-sheet: #ffffff;
|
||||
--bg-box: #f9fbfd;
|
||||
--text-main: #1e1e1e;
|
||||
--text-muted: #666666;
|
||||
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-serif: "Libre Baskerville", "Georgia", serif;
|
||||
}
|
||||
|
||||
body.sheet-page-wrapper {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: var(--bg-page);
|
||||
font-family: var(--font-sans);
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.character-sheet-container {
|
||||
width: 100%;
|
||||
max-width: 1050px;
|
||||
background: var(--bg-sheet);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 20px 24px 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.sheet-header {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 16px;
|
||||
align-items: stretch;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header-name-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.character-name {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 26px;
|
||||
color: var(--accent-red);
|
||||
margin: 0 0 6px 0;
|
||||
line-height: 1.1;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.header-label {
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.header-details-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px 12px;
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.detail-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
.detail-val {
|
||||
font-size: 13.5px;
|
||||
font-weight: 600;
|
||||
color: var(--text-main);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* 3-Column Grid */
|
||||
.sheet-main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 340px 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sheet-col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.section-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px 12px;
|
||||
background: #ffffff;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.box-bottom-label {
|
||||
text-align: center;
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.5px;
|
||||
margin-top: 6px;
|
||||
padding-top: 4px;
|
||||
border-top: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Ability-Grouped Box Styles */
|
||||
.alt-top-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.alt-stat-pill {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 16px;
|
||||
padding: 5px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.pill-value {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.pill-checkbox {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.pill-checkbox.checked {
|
||||
background-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.pill-label {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.ability-group-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
display: grid;
|
||||
grid-template-columns: 78px 1fr;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.ag-score-badge {
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 4px 2px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.ag-title {
|
||||
font-size: 8px;
|
||||
font-weight: bold;
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.ag-mod {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.ag-score {
|
||||
font-size: 10px;
|
||||
color: #555;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 8px;
|
||||
padding: 0 6px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.ag-sublist {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ag-subitem {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
gap: 6px;
|
||||
margin: 2px 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.ag-subitem .dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ag-subitem .dot.filled {
|
||||
background-color: var(--accent-red);
|
||||
border-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.ag-bonus {
|
||||
font-weight: 700;
|
||||
width: 20px;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ag-name {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* Passive Perception */
|
||||
.passive-perception-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 18px;
|
||||
padding: 6px 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.passive-score {
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ffffff;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.passive-label {
|
||||
font-size: 9.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Combat Stats */
|
||||
.combat-top-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.combat-badge {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 8px 4px;
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.badge-ac { border-color: var(--accent-red); }
|
||||
|
||||
.badge-value {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.badge-label {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
.hp-max-line {
|
||||
font-size: 10.5px;
|
||||
color: #666;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.hp-current-val, .temp-hp-val {
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.hd-and-death-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.half-box { padding: 6px 8px; }
|
||||
|
||||
.hd-total-line { font-size: 9.5px; color: #666; }
|
||||
|
||||
.hd-current-val {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.death-save-line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.ds-bubbles { display: flex; gap: 4px; }
|
||||
|
||||
.ds-bubbles .bubble {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ds-bubbles .bubble.filled { background-color: var(--accent-red); }
|
||||
|
||||
/* Attacks Table */
|
||||
.attacks-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 11px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.attacks-table th {
|
||||
font-size: 8.5px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding: 2px 4px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.attacks-table td {
|
||||
padding: 5px 4px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.atk-name { font-weight: 600; }
|
||||
.atk-bonus { font-weight: 700; color: var(--accent-red); }
|
||||
|
||||
.attack-notes {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.35;
|
||||
color: #444;
|
||||
margin-top: 6px;
|
||||
max-height: 120px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.attack-note-item { margin: 4px 0; }
|
||||
|
||||
/* Right Column Roleplay Boxes */
|
||||
.roleplay-box {
|
||||
min-height: 64px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.box-content { font-size: 11.5px; line-height: 1.4; color: #333; }
|
||||
.box-content p { margin: 0; }
|
||||
|
||||
.empty-hint { color: #999; font-style: italic; font-size: 11px; }
|
||||
|
||||
.features-box {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 250px;
|
||||
}
|
||||
|
||||
.features-content { font-size: 11.5px; line-height: 1.4; }
|
||||
.feature-item { margin-bottom: 8px; }
|
||||
.feature-name { color: var(--accent-red); display: block; margin-bottom: 2px; }
|
||||
.feature-desc { margin: 0; }
|
||||
|
||||
/* Bottom Wide Row */
|
||||
.sheet-bottom-row {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.prof-block-content { font-size: 11.5px; line-height: 1.45; }
|
||||
.prof-line { margin-bottom: 4px; }
|
||||
|
||||
.equipment-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 56px 1fr;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.coins-stack { display: flex; flex-direction: column; gap: 4px; }
|
||||
|
||||
.coin-row {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 3px;
|
||||
padding: 2px 4px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 10px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.coin-tag { font-weight: bold; font-size: 8.5px; color: var(--accent-red); }
|
||||
.coin-val { font-weight: 600; }
|
||||
|
||||
.items-list-container { max-height: 180px; overflow-y: auto; }
|
||||
.items-list { list-style: none; margin: 0; padding: 0; font-size: 11px; }
|
||||
.item-row { display: flex; justify-content: space-between; padding: 3px 0; border-bottom: 1px solid #eee; }
|
||||
.equipped-tag { color: var(--accent-red); font-weight: bold; }
|
||||
.item-meta { color: #777; font-size: 10px; }
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 900px) {
|
||||
.sheet-header { grid-template-columns: 1fr; }
|
||||
.sheet-main-grid { grid-template-columns: 1fr; }
|
||||
.sheet-bottom-row { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
472
rulesets/open5e/templates/character_sheet_alt.html
Normal file
472
rulesets/open5e/templates/character_sheet_alt.html
Normal file
@@ -0,0 +1,472 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/rulesets/open5e/templates/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - Alternative Character Sheet</title>
|
||||
<link rel="stylesheet" href="character_sheet_alt.css">
|
||||
</head>
|
||||
<body class="sheet-page-wrapper">
|
||||
<div class="character-sheet-container">
|
||||
|
||||
<!-- Top Header Banner -->
|
||||
<header class="sheet-header">
|
||||
<div class="header-name-box">
|
||||
<h1 class="character-name">{{display_name}}</h1>
|
||||
<label class="header-label">CHARACTER NAME</label>
|
||||
</div>
|
||||
<div class="header-details-grid">
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.class}}{{#if properties.subclass}} ({{properties.subclass}}){{/if}} {{properties.level}}</div>
|
||||
<label class="header-label">CLASS & LEVEL</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}—{{/if}}</div>
|
||||
<label class="header-label">BACKGROUND</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.player_name}}{{properties.player_name}}{{else}}—{{/if}}</div>
|
||||
<label class="header-label">PLAYER NAME</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.race}}</div>
|
||||
<label class="header-label">RACE</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{properties.alignment}}</div>
|
||||
<label class="header-label">ALIGNMENT</label>
|
||||
</div>
|
||||
<div class="detail-cell">
|
||||
<div class="detail-val">{{#if properties.experience_points}}{{properties.experience_points}}{{else}}0{{/if}} XP</div>
|
||||
<label class="header-label">EXPERIENCE POINTS</label>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Main 3-Column Layout -->
|
||||
<main class="sheet-main-grid">
|
||||
|
||||
<!-- ================= LEFT COLUMN: ABILITY-GROUPED STATS ================= -->
|
||||
<section class="sheet-col col-left">
|
||||
|
||||
<!-- Top Controls: Proficiency Bonus & Inspiration -->
|
||||
<div class="alt-top-row">
|
||||
<div class="alt-stat-pill">
|
||||
<span class="pill-value">+{{calcProf properties.level properties.cr}}</span>
|
||||
<span class="pill-label">PROFICIENCY BONUS</span>
|
||||
</div>
|
||||
<div class="alt-stat-pill">
|
||||
<span class="pill-checkbox {{#if properties.inspiration}}checked{{/if}}"></span>
|
||||
<span class="pill-label">INSPIRATION</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- STRENGTH GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">STRENGTH</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.strength}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.strength}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'str')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'strength'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'athletics')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'athletics' 'strength'}}</span>
|
||||
<span class="ag-name">Athletics</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- DEXTERITY GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">DEXTERITY</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.dexterity}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.dexterity}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'dex')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'dexterity'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'acrobatics')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'acrobatics' 'dexterity'}}</span>
|
||||
<span class="ag-name">Acrobatics</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'sleight_of_hand')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'sleight_of_hand' 'dexterity'}}</span>
|
||||
<span class="ag-name">Sleight of Hand</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'stealth')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'stealth' 'dexterity'}}</span>
|
||||
<span class="ag-name">Stealth</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- CONSTITUTION GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">CONSTITUTION</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.constitution}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.constitution}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'con')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'constitution'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- INTELLIGENCE GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">INTELLIGENCE</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.intelligence}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.intelligence}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'int')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'intelligence'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'arcana')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'arcana' 'intelligence'}}</span>
|
||||
<span class="ag-name">Arcana</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'history')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'history' 'intelligence'}}</span>
|
||||
<span class="ag-name">History</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'investigation')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'investigation' 'intelligence'}}</span>
|
||||
<span class="ag-name">Investigation</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'nature')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'nature' 'intelligence'}}</span>
|
||||
<span class="ag-name">Nature</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'religion')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'religion' 'intelligence'}}</span>
|
||||
<span class="ag-name">Religion</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- WISDOM GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">WISDOM</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.wisdom}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.wisdom}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'wis')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'wisdom'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'animal_handling')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'animal_handling' 'wisdom'}}</span>
|
||||
<span class="ag-name">Animal Handling</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'insight')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'insight' 'wisdom'}}</span>
|
||||
<span class="ag-name">Insight</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'medicine')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'medicine' 'wisdom'}}</span>
|
||||
<span class="ag-name">Medicine</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'perception')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'perception' 'wisdom'}}</span>
|
||||
<span class="ag-name">Perception</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'survival')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'survival' 'wisdom'}}</span>
|
||||
<span class="ag-name">Survival</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- CHARISMA GROUP -->
|
||||
<div class="ability-group-box">
|
||||
<div class="ag-score-badge">
|
||||
<span class="ag-title">CHARISMA</span>
|
||||
<span class="ag-mod">{{modText properties.abilities.charisma}}</span>
|
||||
<span class="ag-score">{{val properties.abilities.charisma}}</span>
|
||||
</div>
|
||||
<ul class="ag-sublist">
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (hasSave properties.saving_throws 'cha')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{saveBonus properties 'charisma'}}</span>
|
||||
<span class="ag-name">SAVING THROWS</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'deception')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'deception' 'charisma'}}</span>
|
||||
<span class="ag-name">Deception</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'intimidation')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'intimidation' 'charisma'}}</span>
|
||||
<span class="ag-name">Intimidation</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'performance')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'performance' 'charisma'}}</span>
|
||||
<span class="ag-name">Performance</span>
|
||||
</li>
|
||||
<li class="ag-subitem">
|
||||
<span class="dot {{#if (isSkillProf properties.skills 'persuasion')}}filled{{/if}}"></span>
|
||||
<span class="ag-bonus">{{skillBonus properties 'persuasion' 'charisma'}}</span>
|
||||
<span class="ag-name">Persuasion</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Passive Perception Box -->
|
||||
<div class="passive-perception-box">
|
||||
<span class="passive-score">{{passivePerception properties}}</span>
|
||||
<span class="passive-label">PASSIVE WISDOM (PERCEPTION)</span>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= MIDDLE COLUMN ================= -->
|
||||
<section class="sheet-col col-middle">
|
||||
|
||||
<!-- Combat Top Row -->
|
||||
<div class="combat-top-row">
|
||||
<div class="combat-badge badge-ac">
|
||||
<span class="badge-value">{{val properties.armor_class}}</span>
|
||||
<span class="badge-label">ARMOR CLASS</span>
|
||||
</div>
|
||||
<div class="combat-badge badge-init">
|
||||
<span class="badge-value">{{#if properties.initiative}}{{modSign properties.initiative}}{{else}}{{modText properties.abilities.dexterity}}{{/if}}</span>
|
||||
<span class="badge-label">INITIATIVE</span>
|
||||
</div>
|
||||
<div class="combat-badge badge-speed">
|
||||
<span class="badge-value">{{#if properties.speed}}{{properties.speed}} ft.{{else}}30 ft.{{/if}}</span>
|
||||
<span class="badge-label">SPEED</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- HP Box -->
|
||||
<div class="section-box hp-box">
|
||||
<div class="hp-max-line">
|
||||
Hit Point Maximum: <strong>{{properties.hit_points.max}}</strong>
|
||||
</div>
|
||||
<div class="hp-current-val">{{properties.hit_points.current}}</div>
|
||||
<div class="box-bottom-label">CURRENT HIT POINTS</div>
|
||||
</div>
|
||||
|
||||
<!-- Temp HP -->
|
||||
<div class="section-box temp-hp-box">
|
||||
<div class="temp-hp-val">{{#if properties.hit_points.temp}}{{properties.hit_points.temp}}{{else}}--{{/if}}</div>
|
||||
<div class="box-bottom-label">TEMPORARY HIT POINTS</div>
|
||||
</div>
|
||||
|
||||
<!-- Hit Dice & Death Saves -->
|
||||
<div class="hd-and-death-row">
|
||||
<div class="section-box half-box hd-box">
|
||||
<div class="hd-total-line">Total: {{#if properties.hit_dice.total}}{{properties.hit_dice.total}}{{else}}{{properties.level}}{{/if}}</div>
|
||||
<div class="hd-current-val">
|
||||
{{#if properties.hit_dice.dice}}{{properties.hit_dice.dice}}{{else if properties.hit_points.hit_dice}}{{properties.hit_points.hit_dice}}{{else}}1d10{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">HIT DICE</div>
|
||||
</div>
|
||||
<div class="section-box half-box death-saves-box">
|
||||
<div class="death-save-line">
|
||||
<span class="ds-title">SUCCESSES</span>
|
||||
<span class="ds-bubbles">
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 1)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 2)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.successes 3)}}filled{{/if}}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="death-save-line">
|
||||
<span class="ds-title">FAILURES</span>
|
||||
<span class="ds-bubbles">
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 1)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 2)}}filled{{/if}}"></span>
|
||||
<span class="bubble {{#if (gte properties.death_saves.failures 3)}}filled{{/if}}"></span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="box-bottom-label">DEATH SAVES</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Attacks & Spellcasting -->
|
||||
<div class="section-box attacks-box">
|
||||
<table class="attacks-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>NAME</th>
|
||||
<th>ATK BONUS</th>
|
||||
<th>DAMAGE/TYPE</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#if properties.attacks}}
|
||||
{{#each properties.attacks}}
|
||||
<tr>
|
||||
<td class="atk-name">{{this.name}}</td>
|
||||
<td class="atk-bonus">{{#if this.atk_bonus}}{{this.atk_bonus}}{{else}}+0{{/if}}</td>
|
||||
<td class="atk-damage">{{this.damage}} {{this.damage_type}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else if properties.actions}}
|
||||
{{#each properties.actions}}
|
||||
<tr>
|
||||
<td class="atk-name">{{this.name}}</td>
|
||||
<td class="atk-bonus">+5</td>
|
||||
<td class="atk-damage">{{this.desc}}</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<tr><td colspan="3" class="empty-hint">No attacks equipped</td></tr>
|
||||
{{/if}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{#if properties.actions}}
|
||||
<div class="attack-notes">
|
||||
{{#each properties.actions}}
|
||||
<p class="attack-note-item"><em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}</p>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="box-bottom-label">ATTACKS & SPELLCASTING</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= RIGHT COLUMN ================= -->
|
||||
<section class="sheet-col col-right">
|
||||
|
||||
<!-- Personality Traits -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">{{#if properties.personality_traits}}<p>{{formatText properties.personality_traits}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
|
||||
<div class="box-bottom-label">PERSONALITY TRAITS</div>
|
||||
</div>
|
||||
|
||||
<!-- Ideals -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">{{#if properties.ideals}}<p>{{formatText properties.ideals}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
|
||||
<div class="box-bottom-label">IDEALS</div>
|
||||
</div>
|
||||
|
||||
<!-- Bonds -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">{{#if properties.bonds}}<p>{{formatText properties.bonds}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
|
||||
<div class="box-bottom-label">BONDS</div>
|
||||
</div>
|
||||
|
||||
<!-- Flaws -->
|
||||
<div class="section-box roleplay-box">
|
||||
<div class="box-content">{{#if properties.flaws}}<p>{{formatText properties.flaws}}</p>{{else}}<span class="empty-hint">None</span>{{/if}}</div>
|
||||
<div class="box-bottom-label">FLAWS</div>
|
||||
</div>
|
||||
|
||||
<!-- Features & Traits -->
|
||||
<div class="section-box features-box">
|
||||
<div class="features-content">
|
||||
{{#if properties.features_and_traits}}
|
||||
{{#each properties.features_and_traits}}
|
||||
<div class="feature-item">
|
||||
<strong class="feature-name">{{this.name}}</strong>
|
||||
<p class="feature-desc">{{formatText this.desc}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else if properties.traits}}
|
||||
{{#each properties.traits}}
|
||||
<div class="feature-item">
|
||||
<strong class="feature-name">{{this.name}}</strong>
|
||||
<p class="feature-desc">{{formatText this.desc}}</p>
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">FEATURES & TRAITS</div>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
<!-- Bottom Wide Row: Proficiencies & Equipment/Notes -->
|
||||
<div class="sheet-bottom-row">
|
||||
<!-- Other Proficiencies & Languages -->
|
||||
<div class="section-box proficiencies-box">
|
||||
<div class="prof-block-content">
|
||||
{{#if properties.other_proficiencies_and_languages.armor}}
|
||||
<div class="prof-line"><strong>Armor:</strong> {{join properties.other_proficiencies_and_languages.armor}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.other_proficiencies_and_languages.weapons}}
|
||||
<div class="prof-line"><strong>Weapons:</strong> {{join properties.other_proficiencies_and_languages.weapons}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.other_proficiencies_and_languages.tools}}
|
||||
<div class="prof-line"><strong>Tools:</strong> {{join properties.other_proficiencies_and_languages.tools}}</div>
|
||||
{{/if}}
|
||||
{{#if properties.languages}}
|
||||
<div class="prof-line"><strong>Languages:</strong> {{join properties.languages}}</div>
|
||||
{{else if properties.other_proficiencies_and_languages.languages}}
|
||||
<div class="prof-line"><strong>Languages:</strong> {{join properties.other_proficiencies_and_languages.languages}}</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="box-bottom-label">OTHER PROFICIENCIES & LANGUAGES</div>
|
||||
</div>
|
||||
|
||||
<!-- Equipment & Notes -->
|
||||
<div class="section-box equipment-box">
|
||||
<div class="equipment-layout">
|
||||
<div class="coins-stack">
|
||||
<div class="coin-row"><span class="coin-tag">CP</span><span class="coin-val">{{#if properties.currency.cp}}{{properties.currency.cp}}{{else}}0{{/if}}</span></div>
|
||||
<div class="coin-row"><span class="coin-tag">SP</span><span class="coin-val">{{#if properties.currency.sp}}{{properties.currency.sp}}{{else}}0{{/if}}</span></div>
|
||||
<div class="coin-row"><span class="coin-tag">EP</span><span class="coin-val">{{#if properties.currency.ep}}{{properties.currency.ep}}{{else}}0{{/if}}</span></div>
|
||||
<div class="coin-row"><span class="coin-tag">GP</span><span class="coin-val">{{#if properties.currency.gp}}{{properties.currency.gp}}{{else}}0{{/if}}</span></div>
|
||||
<div class="coin-row"><span class="coin-tag">PP</span><span class="coin-val">{{#if properties.currency.pp}}{{properties.currency.pp}}{{else}}0{{/if}}</span></div>
|
||||
</div>
|
||||
<div class="items-list-container">
|
||||
<ul class="items-list">
|
||||
{{#each properties.inventory}}
|
||||
<li class="item-row">
|
||||
<span class="item-name">{{this.name}}{{#if this.equipped}} <small class="equipped-tag">(E)</small>{{/if}}</span>
|
||||
<span class="item-meta">x{{this.quantity}}{{#if this.weight}} • {{this.weight}} lb{{/if}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box-bottom-label">EQUIPMENT & CHARACTER NOTES</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
221
rulesets/open5e/templates/spellcasting_sheet.css
Normal file
221
rulesets/open5e/templates/spellcasting_sheet.css
Normal file
@@ -0,0 +1,221 @@
|
||||
/* D&D 5e Spellcasting Sheet Styles */
|
||||
:root {
|
||||
--border-color: #2b2b2b;
|
||||
--accent-red: #922610;
|
||||
--bg-page: #eaedf0;
|
||||
--bg-sheet: #ffffff;
|
||||
--bg-box: #f9fbfd;
|
||||
--text-main: #1e1e1e;
|
||||
--text-muted: #666666;
|
||||
--font-sans: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
--font-serif: "Libre Baskerville", "Georgia", serif;
|
||||
}
|
||||
|
||||
body.sheet-page-wrapper {
|
||||
margin: 0;
|
||||
padding: 24px;
|
||||
background-color: var(--bg-page);
|
||||
font-family: var(--font-sans);
|
||||
color: var(--text-main);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.spellcasting-sheet-container {
|
||||
width: 100%;
|
||||
max-width: 1050px;
|
||||
background: var(--bg-sheet);
|
||||
border: 2px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 20px 24px 24px;
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Header */
|
||||
.spell-header {
|
||||
display: grid;
|
||||
grid-template-columns: 320px 1fr;
|
||||
gap: 16px;
|
||||
border-bottom: 2px solid var(--border-color);
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.spell-header-class-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.spell-class-name {
|
||||
font-family: var(--font-serif);
|
||||
font-size: 26px;
|
||||
color: var(--accent-red);
|
||||
margin: 0 0 6px 0;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.spell-header-label {
|
||||
font-size: 9px;
|
||||
font-weight: bold;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.spell-header-stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12px;
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 10px 14px;
|
||||
background: var(--bg-box);
|
||||
}
|
||||
|
||||
.spell-stat-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-right: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.spell-stat-cell:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.spell-stat-val {
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
/* 3-Column Layout */
|
||||
.spells-main-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.spells-column {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.spell-level-box {
|
||||
border: 1.5px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spell-level-header {
|
||||
background: var(--bg-box);
|
||||
border-bottom: 1.5px solid var(--border-color);
|
||||
padding: 6px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.level-num {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: #ffffff;
|
||||
background: var(--accent-red);
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.level-title {
|
||||
font-size: 11px;
|
||||
font-weight: bold;
|
||||
color: var(--text-main);
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.spell-level-header.has-slots {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.slot-counters {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
font-size: 10.5px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.slot-field strong {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.spell-items-list {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 6px 10px;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.spell-item-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.spell-item-row:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.spell-prep-circle {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border: 1.2px solid var(--border-color);
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.spell-prep-circle.prepared {
|
||||
background: var(--accent-red);
|
||||
border-color: var(--accent-red);
|
||||
}
|
||||
|
||||
.spell-prep-circle.invisible {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.spell-name {
|
||||
color: var(--text-main);
|
||||
}
|
||||
|
||||
.spell-item-row.empty {
|
||||
color: #bbb;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 900px) {
|
||||
.spell-header {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.spells-main-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
277
rulesets/open5e/templates/spellcasting_sheet.html
Normal file
277
rulesets/open5e/templates/spellcasting_sheet.html
Normal file
@@ -0,0 +1,277 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<base href="/rulesets/open5e/templates/">
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - Spellcasting Sheet</title>
|
||||
<link rel="stylesheet" href="spellcasting_sheet.css">
|
||||
</head>
|
||||
<body class="sheet-page-wrapper">
|
||||
<div class="spellcasting-sheet-container">
|
||||
|
||||
<!-- Top Spellcasting Header -->
|
||||
<header class="spell-header">
|
||||
<div class="spell-header-class-box">
|
||||
<h1 class="spell-class-name">
|
||||
{{#if properties.spellcasting.class}}
|
||||
{{properties.spellcasting.class}}
|
||||
{{else if properties.class}}
|
||||
{{properties.class}}
|
||||
{{else}}
|
||||
Spellcaster
|
||||
{{/if}}
|
||||
</h1>
|
||||
<label class="spell-header-label">SPELLCASTING CLASS</label>
|
||||
</div>
|
||||
|
||||
<div class="spell-header-stats-grid">
|
||||
<div class="spell-stat-cell">
|
||||
<span class="spell-stat-val">
|
||||
{{#if properties.spellcasting.ability}}
|
||||
{{properties.spellcasting.ability}}
|
||||
{{else}}
|
||||
WISDOM
|
||||
{{/if}}
|
||||
</span>
|
||||
<label class="spell-header-label">SPELLCASTING ABILITY</label>
|
||||
</div>
|
||||
|
||||
<div class="spell-stat-cell">
|
||||
<span class="spell-stat-val">
|
||||
{{#if properties.spellcasting.save_dc}}
|
||||
{{properties.spellcasting.save_dc}}
|
||||
{{else}}
|
||||
13
|
||||
{{/if}}
|
||||
</span>
|
||||
<label class="spell-header-label">SPELL SAVE DC</label>
|
||||
</div>
|
||||
|
||||
<div class="spell-stat-cell">
|
||||
<span class="spell-stat-val">
|
||||
{{#if properties.spellcasting.attack_bonus}}
|
||||
{{modSign properties.spellcasting.attack_bonus}}
|
||||
{{else}}
|
||||
+5
|
||||
{{/if}}
|
||||
</span>
|
||||
<label class="spell-header-label">SPELL ATTACK BONUS</label>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- 3-Column Spells Grid -->
|
||||
<main class="spells-main-grid">
|
||||
|
||||
<!-- ================= COLUMN 1: Cantrips, Level 1, Level 2 ================= -->
|
||||
<section class="spells-column">
|
||||
|
||||
<!-- Cantrips (Level 0) -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header">
|
||||
<span class="level-num">0</span>
|
||||
<span class="level-title">CANTRIPS</span>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#if properties.spellcasting.cantrips}}
|
||||
{{#each properties.spellcasting.cantrips}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle invisible"></span>
|
||||
<span class="spell-name">{{#if this.name}}{{this.name}}{{else}}{{this}}{{/if}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
{{else}}
|
||||
<li class="spell-item-row empty"><span class="spell-name">—</span></li>
|
||||
{{/if}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 1 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">1</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 1}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 1}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 1)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 2 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">2</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 2}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 2}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 2)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= COLUMN 2: Level 3, Level 4, Level 5 ================= -->
|
||||
<section class="spells-column">
|
||||
|
||||
<!-- Level 3 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">3</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 3}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 3}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 3)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 4 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">4</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 4}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 4}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 4)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 5 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">5</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 5}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 5}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 5)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
<!-- ================= COLUMN 3: Level 6, 7, 8, 9 ================= -->
|
||||
<section class="spells-column">
|
||||
|
||||
<!-- Level 6 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">6</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 6}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 6}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 6)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 7 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">7</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 7}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 7}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 7)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 8 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">8</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 8}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 8}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 8)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Level 9 Spells -->
|
||||
<div class="spell-level-box">
|
||||
<div class="spell-level-header has-slots">
|
||||
<span class="level-num">9</span>
|
||||
<div class="slot-counters">
|
||||
<span class="slot-field">Total: <strong>{{slotTotal ../properties.spellcasting.slots 9}}</strong></span>
|
||||
<span class="slot-field">Expended: <strong>{{slotExpended ../properties.spellcasting.slots 9}}</strong></span>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="spell-items-list">
|
||||
{{#each (filterSpells properties.spellcasting.spells 9)}}
|
||||
<li class="spell-item-row">
|
||||
<span class="spell-prep-circle {{#if this.prepared}}prepared{{/if}}"></span>
|
||||
<span class="spell-name">{{this.name}}</span>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</section>
|
||||
|
||||
</main>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
142
rulesets/open5e/templates/stat_block.css
Normal file
142
rulesets/open5e/templates/stat_block.css
Normal file
@@ -0,0 +1,142 @@
|
||||
.stat-block-wrapper {
|
||||
margin: 0;
|
||||
padding: 16px;
|
||||
background-color: #f0f2f5;
|
||||
font-family: system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
color: #222;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.stat-block {
|
||||
width: 100%;
|
||||
max-width: 650px;
|
||||
background: #ffffff;
|
||||
padding: 24px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
border-radius: 4px;
|
||||
border-top: 6px solid #922610;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.sb-header {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.sb-name {
|
||||
font-family: "Libre Baskerville", "Georgia", serif;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #922610;
|
||||
margin: 0 0 4px 0;
|
||||
line-height: 1.15;
|
||||
}
|
||||
|
||||
.sb-type {
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.red-bar {
|
||||
height: 3px;
|
||||
background-color: #922610;
|
||||
margin: 10px 0;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.sb-vitals, .sb-meta, .sb-traits {
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.vital-row, .meta-row {
|
||||
margin: 4px 0;
|
||||
}
|
||||
|
||||
.sb-abilities {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
gap: 4px;
|
||||
text-align: center;
|
||||
padding: 6px 0;
|
||||
}
|
||||
|
||||
.sb-ability {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ab-label {
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: #922610;
|
||||
}
|
||||
|
||||
.ab-val {
|
||||
font-size: 13.5px;
|
||||
margin-top: 2px;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.sb-traits {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.trait-block, .action-block {
|
||||
font-size: 14px;
|
||||
line-height: 1.45;
|
||||
margin: 8px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #eee;
|
||||
text-align: justify;
|
||||
}
|
||||
|
||||
.trait-block:last-child, .action-block:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.sb-section {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-family: "Libre Baskerville", "Georgia", serif;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
color: #922610;
|
||||
margin: 0 0 4px 0;
|
||||
}
|
||||
|
||||
.section-divider {
|
||||
height: 1.5px;
|
||||
background-color: #922610;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.legendary-preamble {
|
||||
font-size: 13.5px;
|
||||
font-style: italic;
|
||||
line-height: 1.4;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
|
||||
/* Responsive adjustments for phones */
|
||||
@media (max-width: 480px) {
|
||||
.stat-block-wrapper {
|
||||
padding: 8px;
|
||||
}
|
||||
.stat-block {
|
||||
padding: 14px;
|
||||
}
|
||||
.sb-name {
|
||||
font-size: 24px;
|
||||
}
|
||||
.ab-val {
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
234
rulesets/open5e/templates/stat_block.html
Normal file
234
rulesets/open5e/templates/stat_block.html
Normal file
@@ -0,0 +1,234 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{display_name}} - Stat Block</title>
|
||||
<link rel="stylesheet" href="stat_block.css">
|
||||
</head>
|
||||
<body class="stat-block-wrapper">
|
||||
<div class="stat-block">
|
||||
<!-- Header -->
|
||||
<header class="sb-header">
|
||||
<h1 class="sb-name">{{display_name}}</h1>
|
||||
<div class="sb-type">
|
||||
{{#if properties.size}}{{properties.size}}{{else}}Medium{{/if}}
|
||||
{{#if properties.type}}{{properties.type}}{{else}}Humanoid{{/if}}{{#if properties.subtype}} ({{properties.subtype}}){{/if}},
|
||||
{{#if properties.alignment}}{{properties.alignment}}{{else}}unaligned{{/if}}
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="red-bar"></div>
|
||||
|
||||
<!-- Core Vitals -->
|
||||
<div class="sb-vitals">
|
||||
<div class="vital-row">
|
||||
<strong>Armor Class</strong> {{val properties.armor_class}}{{#if properties.armor_description}} ({{properties.armor_description}}){{/if}}
|
||||
</div>
|
||||
<div class="vital-row">
|
||||
<strong>Hit Points</strong>
|
||||
{{#if properties.hit_points.current}}
|
||||
{{properties.hit_points.current}}
|
||||
{{else if properties.hit_points.max}}
|
||||
{{properties.hit_points.max}}
|
||||
{{else}}
|
||||
{{val properties.hit_points}}
|
||||
{{/if}}
|
||||
{{#if properties.hit_points.formula}}
|
||||
({{properties.hit_points.formula}})
|
||||
{{else if properties.hit_points.hit_dice}}
|
||||
({{properties.hit_points.hit_dice}})
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="vital-row">
|
||||
<strong>Speed</strong>
|
||||
{{#if properties.speed_desc}}
|
||||
{{properties.speed_desc}}
|
||||
{{else if properties.speed}}
|
||||
{{properties.speed}} ft.
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="red-bar"></div>
|
||||
|
||||
<!-- Ability Scores Table -->
|
||||
<div class="sb-abilities">
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">STR</span>
|
||||
<span class="ab-val">{{val properties.abilities.strength}} ({{modText properties.abilities.strength}})</span>
|
||||
</div>
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">DEX</span>
|
||||
<span class="ab-val">{{val properties.abilities.dexterity}} ({{modText properties.abilities.dexterity}})</span>
|
||||
</div>
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">CON</span>
|
||||
<span class="ab-val">{{val properties.abilities.constitution}} ({{modText properties.abilities.constitution}})</span>
|
||||
</div>
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">INT</span>
|
||||
<span class="ab-val">{{val properties.abilities.intelligence}} ({{modText properties.abilities.intelligence}})</span>
|
||||
</div>
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">WIS</span>
|
||||
<span class="ab-val">{{val properties.abilities.wisdom}} ({{modText properties.abilities.wisdom}})</span>
|
||||
</div>
|
||||
<div class="sb-ability">
|
||||
<span class="ab-label">CHA</span>
|
||||
<span class="ab-val">{{val properties.abilities.charisma}} ({{modText properties.abilities.charisma}})</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="red-bar"></div>
|
||||
|
||||
<!-- Optional Monster Meta -->
|
||||
<div class="sb-meta">
|
||||
{{#if properties.saving_throws}}
|
||||
<div class="meta-row">
|
||||
<strong>Saving Throws</strong> {{join properties.saving_throws}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.skills}}
|
||||
<div class="meta-row">
|
||||
<strong>Skills</strong> {{join properties.skills}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.damage_vulnerabilities}}
|
||||
<div class="meta-row">
|
||||
<strong>Damage Vulnerabilities</strong> {{join properties.damage_vulnerabilities}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.damage_resistances}}
|
||||
<div class="meta-row">
|
||||
<strong>Damage Resistances</strong> {{join properties.damage_resistances}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.damage_immunities}}
|
||||
<div class="meta-row">
|
||||
<strong>Damage Immunities</strong> {{join properties.damage_immunities}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.condition_immunities}}
|
||||
<div class="meta-row">
|
||||
<strong>Condition Immunities</strong> {{join properties.condition_immunities}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.senses}}
|
||||
<div class="meta-row">
|
||||
<strong>Senses</strong> {{properties.senses}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.languages}}
|
||||
<div class="meta-row">
|
||||
<strong>Languages</strong> {{join properties.languages}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.challenge_rating}}
|
||||
<div class="meta-row">
|
||||
<strong>Challenge</strong> {{properties.challenge_rating}}
|
||||
</div>
|
||||
{{else if properties.cr}}
|
||||
<div class="meta-row">
|
||||
<strong>Challenge</strong> {{properties.cr}}{{#if properties.xp}} ({{properties.xp}} XP){{/if}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.proficiency_bonus}}
|
||||
<div class="meta-row">
|
||||
<strong>Proficiency Bonus</strong> +{{properties.proficiency_bonus}}
|
||||
</div>
|
||||
{{else if (calcProf properties.level properties.cr)}}
|
||||
<div class="meta-row">
|
||||
<strong>Proficiency Bonus</strong> +{{calcProf properties.level properties.cr}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<!-- Special Traits / Features -->
|
||||
{{#if properties.traits}}
|
||||
<div class="sb-traits">
|
||||
{{#each properties.traits}}
|
||||
<div class="trait-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
<!-- Actions Section -->
|
||||
{{#if properties.actions}}
|
||||
<section class="sb-section">
|
||||
<h2 class="section-title">Actions</h2>
|
||||
<div class="section-divider"></div>
|
||||
{{#each properties.actions}}
|
||||
<div class="action-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<!-- Bonus Actions Section -->
|
||||
{{#if properties.bonus_actions}}
|
||||
<section class="sb-section">
|
||||
<h2 class="section-title">Bonus Actions</h2>
|
||||
<div class="section-divider"></div>
|
||||
{{#each properties.bonus_actions}}
|
||||
<div class="action-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<!-- Reactions Section -->
|
||||
{{#if properties.reactions}}
|
||||
<section class="sb-section">
|
||||
<h2 class="section-title">Reactions</h2>
|
||||
<div class="section-divider"></div>
|
||||
{{#each properties.reactions}}
|
||||
<div class="action-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<!-- Legendary Actions Section -->
|
||||
{{#if properties.legendary_actions}}
|
||||
<section class="sb-section">
|
||||
<h2 class="section-title">Legendary Actions</h2>
|
||||
<div class="section-divider"></div>
|
||||
{{#if properties.legendary_actions.description}}
|
||||
<p class="legendary-preamble">{{formatText properties.legendary_actions.description}}</p>
|
||||
{{else if properties.legendariesDescription}}
|
||||
<p class="legendary-preamble">{{formatText properties.legendariesDescription}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if properties.legendary_actions.actions}}
|
||||
{{#each properties.legendary_actions.actions}}
|
||||
<div class="action-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{else if properties.legendaries}}
|
||||
{{#each properties.legendaries}}
|
||||
<div class="action-block">
|
||||
<em><strong>{{this.name}}:</strong></em> {{formatText this.desc}}
|
||||
</div>
|
||||
{{/each}}
|
||||
{{/if}}
|
||||
</section>
|
||||
{{/if}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user