via site.title_template ──────────────────────────────
// Template: '{title} — {site}'. If pageTitle equals site.title (homepage) we
// just use the site title alone, otherwise apply the template.
const _siteTitle = safeSite.title || 'Klonkt';
const _rawTitle = pageTitle || _siteTitle;
const _tpl = safeSite.title_template || '{title} — {site}';
const _finalTitle = (_rawTitle === _siteTitle)
? _siteTitle
: _tpl.replace('{title}', _rawTitle).replace('{site}', _siteTitle);
// ── Robots: noindex on listing pages and on per-post override ─────
let _shouldIndex = safeSite.robots_index !== 0;
if (typeof post !== 'undefined' && post && post.noindex) _shouldIndex = false;
// Listing pages (search/tag/type/archive) shouldn't be indexed (dupe content)
if (currentPath) {
if (/^\/(?:search|tag|type|archive|users|prutter|account|admin)(?:$|\/)/.test(currentPath)) {
_shouldIndex = false;
}
}
// Special-flagged views from routes opt out too
if (isSpecialPg && (currentPath === '/search' || /^\/(tag|type|archive|users)\//.test(currentPath))) {
_shouldIndex = false;
}
// ── Canonical URL (only when we have one configured) ─────────────
let _canonical = null;
if (safeSite.canonical) {
const _base = safeSite.canonical.replace(/\/+$/, '');
let _path = '/';
if (typeof post !== 'undefined' && post && post.slug) _path = '/' + post.slug;
else if (currentPath) _path = currentPath;
_canonical = _base + _path;
}
// ── Social bits (OG/Twitter) ──────────────────────────────────────
const _socialTitle = (typeof post !== 'undefined' && post && post.title) ? post.title : _siteTitle;
const _socialDescr = (typeof socialDescr !== 'undefined' && socialDescr)
? socialDescr
: (safeSite.default_description || safeSite.description || '');
const _socialImage = (typeof socialImage !== 'undefined' && socialImage)
? socialImage
: (safeSite.og_image_default || safeSite.default_cover || '');
const _ogType = isPostPage ? 'article' : 'website';
// ── JSON-LD ───────────────────────────────────────────────────────
const _publisher = {
'@type': safeSite.schema_type === 'Organization' ? 'Organization' : 'Person',
name: safeSite.publisher_name || _siteTitle,
url: safeSite.publisher_url || (_canonical ? _canonical.split(/(?<=^[^/]*\/\/[^/]+)\//)[0] + '/' : null),
};
if (safeSite.publisher_logo) {
_publisher.logo = { '@type': 'ImageObject', url: safeSite.publisher_logo };
}
let _jsonLd = null;
if (typeof post !== 'undefined' && post && post.slug) {
_jsonLd = {
'@context': 'https://schema.org',
'@type': (post.type === 'foto' || post.type === 'video') ? 'CreativeWork' : 'Article',
headline: post.title || _siteTitle,
description: _socialDescr,
datePublished: post.published_at || post.created_at || new Date().toISOString(),
dateModified: post.updated_at || post.published_at || new Date().toISOString(),
publisher: _publisher,
};
if (_socialImage) _jsonLd.image = _socialImage;
if (post.author_username) _jsonLd.author = { '@type': 'Person', name: post.author_username };
if (Array.isArray(post.tags) && post.tags.length) _jsonLd.keywords = post.tags.join(', ');
} else if (isHomePage) {
_jsonLd = {
'@context': 'https://schema.org',
'@type': 'WebSite',
name: _siteTitle,
description: safeSite.description || safeSite.default_description || '',
publisher: _publisher,
};
}
%>
<%= _finalTitle %>
<% if (safeSite.author) { %><% } %>
<% if (_canonical) { %><% } %>
<% if (safeSite.google_verification) { %><% } %>
<% if (safeSite.bing_verification) { %><% } %>
<% if (safeSite.pinterest_verification) { %><% } %>
<% if (safeSite.yandex_verification) { %><% } %>
<% if (site) { %>
<% } %>
<% if (_socialImage) { %>
<% } %>
<% if (_canonical) { %><% } %>
<% if (typeof post !== 'undefined' && post && post.published_at) { %>
<% if (post.author_username) { %><% } %>
<% } %>
<% if (safeSite.facebook_app_id) { %><% } %>
<% if (_socialImage) { %><% } %>
<% if (safeSite.twitter) { %><% } %>
<% if (_jsonLd) { %>
<% } %>
<%- include('partials/shared-styles') %>
<% if (safeSite.custom_css) { %>
<% } %>
<% if (safeSite.custom_head_html) { %>
<%- safeSite.custom_head_html %>
<% } %>
<% if (typeof isViewer !== 'undefined' && isViewer) { %>
👁️Kijker-modus — je kunt alles bekijken, maar niets wijzigen.
<% } %>
<%# Site-chrome (topnav + profielkop + view-switcher) in één vaste slot #pcms-chrome.
Bij htmx-navigatie wordt dit slot out-of-band ververst (zie chrome.ejs +
render.js), zodat de kop ALTIJD bij de nieuwe pagina/artiest hoort terwijl de
audioplayer (los in document.body) blijft leven → geen verspringen. Op de
hub-landing is het slot leeg: de hero is daar de header. %>
<%- include('partials/chrome') %>
<%# Geen hx-history-elt: htmx' eigen history staat uit (zie de link-boost).
Back/forward wordt door onze popstate-listener gedaan, die de partial
her-fetcht (incl. correcte OOB-chrome). Met hx-history-elt + htmx-history
aan dumpte htmx de partial ongefilterd hier → dubbele kop. %>
<%- pageContent %>
<%- include('partials/footer') %>
<% if (!(typeof bodyClass === 'string' && bodyClass.indexOf('on-auth') >= 0)) { %>
<%- include('partials/bottom-tab') %>
<% } %>
<% if (user) { %>
<%- include('partials/profile-sheet') %>
<% } %>
<% if (site && site.enable_audio_player && audioTracks && audioTracks.length > 0) { %>
<% } %>
<% if (safeSite.custom_foot_html) { %>
<%- safeSite.custom_foot_html %>
<% } %>