Adds import support from our other project.

This commit is contained in:
2026-09-07 21:57:59 -07:00
parent a3d1a139e9
commit a3dace36de
150 changed files with 17716 additions and 21 deletions

View 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%;
}
}

View 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>

View 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;
}
}

View 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">&mdash; No listed actions &mdash;</span>
</div>
{{/if}}
</div>
</div>
</body>
</html>

View 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;
}
}

View 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 &amp; LEVEL</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}&mdash;{{/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}}&mdash;{{/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 &amp; 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 &amp; 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}} &bull; {{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 &amp; TRAITS</div>
</div>
</section>
</main>
</div>
</body>
</html>

View File

@@ -0,0 +1,5 @@
// Starter client-side script for open5e character sheet
window.addEventListener('DOMContentLoaded', () => {
console.log('Open5e Character Sheet template initialized.');
});

View 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; }
}

View 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 &amp; LEVEL</label>
</div>
<div class="detail-cell">
<div class="detail-val">{{#if properties.background}}{{properties.background}}{{else}}&mdash;{{/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}}&mdash;{{/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 &amp; 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 &amp; 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 &amp; 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}} &bull; {{this.weight}} lb{{/if}}</span>
</li>
{{/each}}
</ul>
</div>
</div>
<div class="box-bottom-label">EQUIPMENT &amp; CHARACTER NOTES</div>
</div>
</div>
</div>
</body>
</html>

View 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;
}
}

View 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">&mdash;</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>

View 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;
}
}

View 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>