diff --git a/server.cjs b/server.cjs index 42c81d2..10b7e10 100644 --- a/server.cjs +++ b/server.cjs @@ -2365,6 +2365,10 @@ function escapeHtml(value) { .replace(/'/g, ''') } +function stripSiteTitle(title) { + return String(title || '').replace(/\s+\|\s+Toothless' TSS Bot$/, '') +} + function decodeRouteSegment(value) { try { return decodeURIComponent(value || '').replace(/\+/g, ' ').trim() @@ -2617,6 +2621,54 @@ function routeSeo(pathname) { return byPath[cleanPath] || notFoundSeo } +function routeFallbackSections(seo) { + const sections = [ + { href: '/teams', label: 'TSS team leaderboard' }, + { href: '/players', label: 'TSS player leaderboard' }, + { href: '/battle-logs', label: 'Battle logs' }, + { href: '/tournaments', label: 'Tournaments' }, + { href: '/blog', label: 'Blog' }, + ] + + if (seo.status === 404) { + return [ + '

Use the links below to browse the live War Thunder Tournament Service leaderboards, battle logs, tournament brackets, and updates.

', + ``, + ].join('\n') + } + + if (seo.type === 'BlogPosting') { + const published = seo.publishedAt ? `

Published ${escapeHtml(seo.publishedAt)} by ${escapeHtml(seo.author || "Toothless' TSS Bot")}.

` : '' + return [ + published, + '

Read more War Thunder TSS Bot news, feature updates, tournament tracking notes, and community announcements.

', + '

Browse all blog posts

', + ].filter(Boolean).join('\n') + } + + if (seo.path === '/') { + return [ + '

Toothless TSS Bot tracks War Thunder Tournament Service activity across teams, players, battle logs, tournaments, and squadron profiles.

', + ``, + ].join('\n') + } + + return [ + '

This page is part of Toothless TSS Bot, a War Thunder Tournament Service tracker for leaderboards, squadron profiles, battle logs, player stats, and tournament brackets.

', + ``, + ].join('\n') +} + +function routeFallbackHtml(seo) { + return [ + '
', + `

${escapeHtml(stripSiteTitle(seo.title))}

`, + `

${escapeHtml(seo.description)}

`, + routeFallbackSections(seo), + '
', + ].join('\n') +} + function routeStructuredData(origin, seo, canonicalUrl) { const siteId = `${origin}/#website` const pageId = `${canonicalUrl}#webpage` @@ -2661,7 +2713,7 @@ function routeStructuredData(origin, seo, canonicalUrl) { ...(seo.path === '/' ? [] : [{ '@type': 'ListItem', position: 2, - name: seo.title.replace(/\s+\|\s+Toothless' TSS Bot$/, ''), + name: stripSiteTitle(seo.title), item: canonicalUrl, }]), ], @@ -2672,7 +2724,7 @@ function routeStructuredData(origin, seo, canonicalUrl) { items.push({ '@context': 'https://schema.org', '@type': 'BlogPosting', - headline: seo.title.replace(/\s+\|\s+Toothless' TSS Bot$/, ''), + headline: stripSiteTitle(seo.title), description: seo.description, url: canonicalUrl, image: imageUrl, @@ -2725,6 +2777,7 @@ function htmlWithSeo(req, data) { .replaceAll('__SEO_MODIFIED_TIME__', escapeHtml(seo.publishedAt || '')) .replaceAll('__SEO_JSON_LD__', routeStructuredData(origin, seo, canonicalUrl).replace(/', `
\n${routeFallbackHtml(seo)}\n
`) } function requestPathname(req) {