96 lines
4.0 KiB
JavaScript
96 lines
4.0 KiB
JavaScript
/**
|
|
* Block Renderer - Project Friction
|
|
* Converts a block array into HTML strings.
|
|
* Used by both the main exhibit (app.js) and the builder preview.
|
|
*/
|
|
class BlockRenderer {
|
|
|
|
static render(blocks) {
|
|
if (!Array.isArray(blocks)) return '';
|
|
return blocks.map(function(block) { return BlockRenderer.renderBlock(block); }).join('\n');
|
|
}
|
|
|
|
static renderBlock(block) {
|
|
if (!block || !block.type) return '';
|
|
switch (block.type) {
|
|
case 'heading': return BlockRenderer.renderHeading(block);
|
|
case 'paragraph': return BlockRenderer.renderParagraph(block);
|
|
case 'image': return BlockRenderer.renderImage(block);
|
|
case 'quote': return BlockRenderer.renderQuote(block);
|
|
case 'gallery': return BlockRenderer.renderGallery(block);
|
|
case 'grid4': return BlockRenderer.renderGrid4(block);
|
|
case 'statistics': return BlockRenderer.renderStatistics(block);
|
|
case 'map-button': return BlockRenderer.renderMapButton(block);
|
|
default: return '';
|
|
}
|
|
}
|
|
|
|
static renderHeading(block) {
|
|
var level = Math.min(Math.max(parseInt(block.level) || 2, 1), 6);
|
|
return '<h' + level + ' class="block block-heading block-heading-' + level + '">' + (block.text || '') + '</h' + level + '>';
|
|
}
|
|
|
|
static renderParagraph(block) {
|
|
return '<p class="block block-paragraph">' + (block.text || '') + '</p>';
|
|
}
|
|
|
|
static renderImage(block) {
|
|
return '<figure class="block block-image">'
|
|
+ '<img src="' + (block.src || '') + '" alt="' + (block.alt || '') + '" loading="lazy">'
|
|
+ (block.caption ? '<figcaption>' + block.caption + '</figcaption>' : '')
|
|
+ '</figure>';
|
|
}
|
|
|
|
static renderQuote(block) {
|
|
return '<blockquote class="block block-quote quote-block">'
|
|
+ '<p class="quote-text">' + (block.text || '') + '</p>'
|
|
+ (block.author ? '<cite class="quote-attribution">— ' + block.author + '</cite>' : '')
|
|
+ '</blockquote>';
|
|
}
|
|
|
|
static renderGallery(block) {
|
|
var images = block.images || [];
|
|
var items = images.map(function(img) {
|
|
return '<div class="gallery-item">'
|
|
+ '<img src="' + (img.src || '') + '" alt="' + (img.alt || '') + '" loading="lazy">'
|
|
+ (img.caption ? '<div class="gallery-item-caption">' + img.caption + '</div>' : '')
|
|
+ '</div>';
|
|
}).join('');
|
|
return '<div class="block block-gallery"><div class="gallery-scroll">' + items + '</div></div>';
|
|
}
|
|
|
|
static renderGrid4(block) {
|
|
var images = (block.images || []).slice(0, 4);
|
|
var items = images.map(function(img) {
|
|
return '<div class="grid4-item">'
|
|
+ '<img src="' + (img.src || '') + '" alt="' + (img.alt || '') + '" loading="lazy">'
|
|
+ (img.caption ? '<div class="grid4-caption">' + img.caption + '</div>' : '')
|
|
+ '</div>';
|
|
}).join('');
|
|
return '<div class="block block-grid4"><div class="grid4-grid">' + items + '</div></div>';
|
|
}
|
|
|
|
static renderStatistics(block) {
|
|
var stats = block.stats || [];
|
|
var items = stats.map(function(s) {
|
|
return '<div class="stat-card">'
|
|
+ '<div class="stat-number">' + (s.number || '') + '</div>'
|
|
+ '<div class="stat-label">' + (s.label || '') + '</div>'
|
|
+ '</div>';
|
|
}).join('');
|
|
return '<div class="block block-statistics"><div class="block-stats-grid">' + items + '</div></div>';
|
|
}
|
|
|
|
static renderMapButton(block) {
|
|
return '<div class="block block-map-button-wrap">'
|
|
+ '<button class="map-button block-map-btn"'
|
|
+ ' data-lat="' + (block.lat || 0) + '"'
|
|
+ ' data-lng="' + (block.lng || 0) + '"'
|
|
+ ' data-zoom="' + (block.zoom || 6) + '">'
|
|
+ '<span class="map-button-icon">🗺</span> '
|
|
+ (block.label || 'View on Map')
|
|
+ '</button></div>';
|
|
}
|
|
}
|
|
|