- Editorial web platform built with Node.js, Express, EJS, and Vanilla CSS - Markdown content engine with YAML frontmatter, bilingual variants, and hot-reload watcher - Unified SQLite with FTS5 search indexing, query logging, and schema filters - Centered editorial post layout with margin-docked left TOC and mobile bottom sheet - WordPress-grade comment system with moderation dashboard and bulk operations under /guanli - Complete SEO suite (JSON-LD structured data, RSS/Atom feeds, XML sitemap, robots.txt) - CI/CD workflows for linting, security audits (Gitleaks), automated tests, and Docker releases
63 lines
2.6 KiB
Plaintext
63 lines
2.6 KiB
Plaintext
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><%= typeof title !== 'undefined' ? `${title} - ${site.title}` : site.title %></title>
|
|
|
|
<meta name="description" content="<%= (seo && seo.description) || site.tagline %>">
|
|
<link rel="canonical" href="<%= (seo && seo.canonicalUrl) || site.url %>">
|
|
|
|
<!-- Open Graph / Social Media -->
|
|
<meta property="og:site_name" content="<%= site.title %>">
|
|
<meta property="og:title" content="<%= (seo && seo.title) || site.title %>">
|
|
<meta property="og:description" content="<%= (seo && seo.description) || site.tagline %>">
|
|
<meta property="og:type" content="<%= (seo && seo.type) || 'website' %>">
|
|
<meta property="og:url" content="<%= (seo && seo.canonicalUrl) || site.url %>">
|
|
<% if (seo && seo.ogImage) { %>
|
|
<meta property="og:image" content="<%= seo.ogImage %>">
|
|
<% } %>
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="<%= (seo && seo.title) || site.title %>">
|
|
<meta name="twitter:description" content="<%= (seo && seo.description) || site.tagline %>">
|
|
<% if (seo && seo.ogImage) { %>
|
|
<meta name="twitter:image" content="<%= seo.ogImage %>">
|
|
<% } %>
|
|
|
|
<!-- JSON-LD Structured Data -->
|
|
<% if (typeof getJsonLd === 'function') { %>
|
|
<script type="application/ld+json">
|
|
<%- getJsonLd() %>
|
|
</script>
|
|
<% } %>
|
|
|
|
<!-- RSS Link -->
|
|
<link rel="alternate" type="application/rss+xml" title="<%= site.title %> RSS Feed" href="/feed">
|
|
|
|
<!-- Google Fonts: Cardo (Twenty Twenty-Four Typography) & Inter -->
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Cardo:ital,wght@0,400;0,700;1,400&family=Inter:wght@400;500;600&display=swap" rel="stylesheet">
|
|
|
|
<!-- Core Stylesheets -->
|
|
<link rel="stylesheet" href="/css/fonts.css">
|
|
<link rel="stylesheet" href="/css/base.css">
|
|
<link rel="stylesheet" href="/css/components.css">
|
|
<link rel="stylesheet" href="/css/prism-okaidia.css">
|
|
|
|
<!-- Custom CSS overrides from PV if provided -->
|
|
<link rel="stylesheet" href="/css/custom/custom.css" onerror="this.remove()">
|
|
|
|
<!-- Plugin head injection -->
|
|
<%- pluginHook('injectHead', { pageType: typeof pageType !== 'undefined' ? pageType : 'default' }) %>
|
|
</head>
|
|
<body>
|
|
<%- pluginHook('injectBodyStart', { pageType: typeof pageType !== 'undefined' ? pageType : 'default' }) %>
|
|
|
|
<%- include('./header') %>
|
|
|
|
<main class="site-main">
|
|
<div class="container">
|