This commit is contained in:
@@ -63,8 +63,8 @@ export function dungeonTemplate(data) {
|
||||
padding: 0;
|
||||
font-family: ${bodyFont};
|
||||
color: #1a1a1a;
|
||||
font-size: 0.75em;
|
||||
line-height: 1.4em;
|
||||
font-size: 0.7em;
|
||||
line-height: 1.35em;
|
||||
}
|
||||
.content-page {
|
||||
height: 100vh;
|
||||
@@ -91,7 +91,7 @@ export function dungeonTemplate(data) {
|
||||
font-family: ${quoteFont};
|
||||
margin: 0.3em 0 0.6em;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.4em;
|
||||
line-height: 1.35em;
|
||||
}
|
||||
.columns {
|
||||
display: grid;
|
||||
@@ -102,7 +102,7 @@ export function dungeonTemplate(data) {
|
||||
.col {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.3em;
|
||||
gap: 0.25em;
|
||||
overflow-wrap: break-word;
|
||||
word-break: normal;
|
||||
hyphens: auto;
|
||||
@@ -110,7 +110,7 @@ export function dungeonTemplate(data) {
|
||||
.section-block {
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
margin-bottom: 0.4em;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
h2 {
|
||||
font-family: ${headingFont};
|
||||
@@ -129,29 +129,29 @@ export function dungeonTemplate(data) {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
.room h3 {
|
||||
margin: 0.15em 0 0.08em;
|
||||
margin: 0.1em 0 0.05em;
|
||||
font-size: 0.95em;
|
||||
font-weight: bold;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
.room p {
|
||||
margin: 0 0 0.35em;
|
||||
font-size: 0.9em;
|
||||
margin: 0 0 0.25em;
|
||||
font-size: 0.85em;
|
||||
font-weight: normal;
|
||||
line-height: 1.35em;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.encounter, .npc, .treasure, .plot-resolution {
|
||||
margin: 0 0 0.35em;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.35em;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
.random-events {
|
||||
margin: 0.2em 0;
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
font-size: 0.9em;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
.random-events table {
|
||||
margin-top: 0.15em;
|
||||
@@ -228,8 +228,8 @@ export function dungeonTemplate(data) {
|
||||
}
|
||||
li {
|
||||
margin: 0.08em 0;
|
||||
font-size: 0.9em;
|
||||
line-height: 1.35em;
|
||||
font-size: 0.85em;
|
||||
line-height: 1.3em;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@@ -268,12 +268,30 @@ export function dungeonTemplate(data) {
|
||||
${data.rooms && data.rooms.length > 0 ? `
|
||||
<div class="section-block">
|
||||
<h2>Locations</h2>
|
||||
${data.rooms.map(room => `
|
||||
${data.rooms.map(room => {
|
||||
let desc = room.description || '';
|
||||
// Truncate to 2 sentences max
|
||||
const sentences = desc.match(/[^.!?]+[.!?]+/g) || [desc];
|
||||
if (sentences.length > 2) {
|
||||
desc = sentences.slice(0, 2).join(' ').trim();
|
||||
}
|
||||
// Also limit by character count (~150 chars)
|
||||
if (desc.length > 150) {
|
||||
desc = desc.substring(0, 147).trim();
|
||||
const lastPeriod = desc.lastIndexOf('.');
|
||||
if (lastPeriod > 100) {
|
||||
desc = desc.substring(0, lastPeriod + 1);
|
||||
} else {
|
||||
desc += '...';
|
||||
}
|
||||
}
|
||||
return `
|
||||
<div class="room">
|
||||
<h3>${escapeHtml(room.name)}</h3>
|
||||
<p>${escapeHtml(room.description)}</p>
|
||||
<p>${escapeHtml(desc)}</p>
|
||||
</div>
|
||||
`).join('')}
|
||||
`;
|
||||
}).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
@@ -287,7 +305,20 @@ export function dungeonTemplate(data) {
|
||||
${data.encounters.map((encounter, index) => {
|
||||
// Truncate details to 4 sentences max to prevent overflow
|
||||
let details = encounter.details || '';
|
||||
// Keep location prefix in details (format: "Location Name: details")
|
||||
// Remove encounter name if it appears at start
|
||||
if (details.toLowerCase().startsWith(encounter.name.toLowerCase())) {
|
||||
details = details.substring(encounter.name.length).replace(/^:\s*/, '').trim();
|
||||
}
|
||||
// Remove location prefix if present (format: "Location Name: description")
|
||||
// Handle multiple colons - strip the first one that looks like a location
|
||||
const locationMatch = details.match(/^([^:]+):\s*(.+)$/);
|
||||
if (locationMatch) {
|
||||
const potentialLocation = locationMatch[1].trim();
|
||||
// If it looks like a location name (capitalized, not too long), remove it
|
||||
if (potentialLocation.length > 3 && potentialLocation.length < 50 && /^[A-Z]/.test(potentialLocation)) {
|
||||
details = locationMatch[2].trim();
|
||||
}
|
||||
}
|
||||
// Split into sentences and keep only first 4
|
||||
const sentences = details.match(/[^.!?]+[.!?]+/g) || [details];
|
||||
if (sentences.length > 4) {
|
||||
@@ -322,22 +353,38 @@ export function dungeonTemplate(data) {
|
||||
${data.treasure && data.treasure.length > 0 ? `
|
||||
<div class="section-block">
|
||||
<h2>Treasure</h2>
|
||||
${data.treasure.map(item => `
|
||||
<div class="treasure">
|
||||
${typeof item === 'object' && item.name ? `<strong>${escapeHtml(item.name)}</strong> — ${escapeHtml(item.description)}` : escapeHtml(item)}
|
||||
</div>
|
||||
`).join('')}
|
||||
${data.treasure.map(item => {
|
||||
if (typeof item === 'object' && item.name && item.description) {
|
||||
return `<div class="treasure"><strong>${escapeHtml(item.name)}</strong> — ${escapeHtml(item.description)}</div>`;
|
||||
} else if (typeof item === 'string') {
|
||||
// Handle string format "Name — Description"
|
||||
const parts = item.split(/[—–-]/);
|
||||
if (parts.length >= 2) {
|
||||
return `<div class="treasure"><strong>${escapeHtml(parts[0].trim())}</strong> — ${escapeHtml(parts.slice(1).join(' ').trim())}</div>`;
|
||||
}
|
||||
return `<div class="treasure">${escapeHtml(item)}</div>`;
|
||||
}
|
||||
return '';
|
||||
}).filter(Boolean).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${data.npcs && data.npcs.length > 0 ? `
|
||||
<div class="section-block">
|
||||
<h2>NPCs</h2>
|
||||
${data.npcs.map(npc => `
|
||||
<div class="npc">
|
||||
<strong>${escapeHtml(npc.name)}</strong>: ${escapeHtml(npc.trait)}
|
||||
</div>
|
||||
`).join('')}
|
||||
${data.npcs.map(npc => {
|
||||
if (typeof npc === 'object' && npc.name && npc.trait) {
|
||||
return `<div class="npc"><strong>${escapeHtml(npc.name)}</strong>: ${escapeHtml(npc.trait)}</div>`;
|
||||
} else if (typeof npc === 'string') {
|
||||
// Handle string format "Name: Description"
|
||||
const parts = npc.split(/:/);
|
||||
if (parts.length >= 2) {
|
||||
return `<div class="npc"><strong>${escapeHtml(parts[0].trim())}</strong>: ${escapeHtml(parts.slice(1).join(':').trim())}</div>`;
|
||||
}
|
||||
return `<div class="npc">${escapeHtml(npc)}</div>`;
|
||||
}
|
||||
return '';
|
||||
}).filter(Boolean).join('')}
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
@@ -345,19 +392,19 @@ export function dungeonTemplate(data) {
|
||||
<div class="section-block">
|
||||
<h2>Plot Resolutions</h2>
|
||||
${data.plotResolutions.map(resolution => {
|
||||
// Truncate to 3 sentences max to prevent overflow
|
||||
// Truncate to 2 sentences max to prevent overflow
|
||||
let text = resolution || '';
|
||||
// Split into sentences and keep only first 3
|
||||
// Split into sentences and keep only first 2
|
||||
const sentences = text.match(/[^.!?]+[.!?]+/g) || [text];
|
||||
if (sentences.length > 3) {
|
||||
text = sentences.slice(0, 3).join(' ').trim();
|
||||
if (sentences.length > 2) {
|
||||
text = sentences.slice(0, 2).join(' ').trim();
|
||||
}
|
||||
// Also limit by character count as fallback (max ~200 chars)
|
||||
if (text.length > 200) {
|
||||
text = text.substring(0, 197).trim();
|
||||
// Also limit by character count as fallback (max ~150 chars)
|
||||
if (text.length > 150) {
|
||||
text = text.substring(0, 147).trim();
|
||||
// Try to end at a sentence boundary
|
||||
const lastPeriod = text.lastIndexOf('.');
|
||||
if (lastPeriod > 150) {
|
||||
if (lastPeriod > 100) {
|
||||
text = text.substring(0, lastPeriod + 1);
|
||||
} else {
|
||||
text += '...';
|
||||
|
||||
Reference in New Issue
Block a user