mirror of
https://github.com/andwati/andwati.com.git
synced 2026-10-18 01:12:53 +00:00
[[extra.menu]] silently swallowed ~20 later extra.* keys (posts_page_title, search_placeholder, show_rss_link, notfound_*, etc.) into the last menu entry/tag_descriptions table, since TOML absorbs bare keys into the last open table until a new header appears. show_rss_link was the only visibly broken one (RSS link missing from homepage); the rest were masked by matching template defaults. Also replaced Tera's default() filter with explicit if/else where values feed into JSON-LD, since default()'s fallback gets escaped in a way `safe` can't undo. Added author/dateModified to BlogPosting list entries.
161 lines
7.6 KiB
HTML
161 lines
7.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{% block title %}{{ config.title }}{% endblock title %}</title>
|
|
<meta name="description" content="{% block description %}{{ config.description }}{% endblock description %}">
|
|
<meta name="view-transition" content="same-origin">
|
|
|
|
<!-- Favicon -->
|
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
|
<link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32.png">
|
|
<link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16.png">
|
|
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
|
<link rel="manifest" href="/site.webmanifest">
|
|
<meta name="theme-color" content="#131311">
|
|
|
|
{% if config.extra.google_site_verification %}
|
|
<meta name="google-site-verification" content="{{ config.extra.google_site_verification }}">
|
|
{% endif %}
|
|
|
|
<!-- Canonical URL -->
|
|
<link rel="canonical" href="{% block canonical %}{{ config.base_url }}/{% endblock canonical %}">
|
|
|
|
<!-- Open Graph -->
|
|
<meta property="og:site_name" content="{{ config.title }}">
|
|
<meta property="og:title" content="{% block og_title %}{{ config.title }}{% endblock og_title %}">
|
|
<meta property="og:description" content="{% block og_description %}{{ config.description }}{% endblock og_description %}">
|
|
<meta property="og:url" content="{% block og_url %}{{ config.base_url }}/{% endblock og_url %}">
|
|
<meta property="og:type" content="{% block og_type %}website{% endblock og_type %}">
|
|
<meta property="og:locale" content="en_US">
|
|
<meta property="og:image" content="{% block og_image %}{{ config.base_url }}/images/og-image.png{% endblock og_image %}">
|
|
<meta property="og:image:width" content="1200">
|
|
<meta property="og:image:height" content="630">
|
|
<meta property="og:image:alt" content="{{ config.title }} — {{ config.description }}">
|
|
{% block og_extra %}{% endblock og_extra %}
|
|
|
|
<!-- Twitter Card -->
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:site" content="{{ config.extra.twitter_handle | default(value="@andwati_") }}">
|
|
<meta name="twitter:title" content="{% block twitter_title %}{{ config.title }}{% endblock twitter_title %}">
|
|
<meta name="twitter:description" content="{% block twitter_description %}{{ config.description }}{% endblock twitter_description %}">
|
|
<meta name="twitter:image" content="{% block twitter_image %}{{ config.base_url }}/images/og-image.png{% endblock twitter_image %}">
|
|
{% block twitter_extra %}{% endblock twitter_extra %}
|
|
|
|
<!-- Theme initializer (runs before paint to avoid flash) -->
|
|
<script>
|
|
(function() {
|
|
try {
|
|
var storedTheme = localStorage.getItem('theme');
|
|
var prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
var shouldUseDark = storedTheme ? storedTheme === 'dark' : prefersDark;
|
|
if (shouldUseDark) {
|
|
document.documentElement.classList.add('dark');
|
|
} else {
|
|
document.documentElement.classList.remove('dark');
|
|
}
|
|
} catch (e) {
|
|
document.documentElement.classList.add('dark');
|
|
}
|
|
})();
|
|
</script>
|
|
|
|
<!-- SEO: WebSite + Person JSON-LD (sitewide graph) -->
|
|
<script type="application/ld+json">
|
|
{
|
|
"@context": "https://schema.org",
|
|
"@graph": [
|
|
{
|
|
"@type": "WebSite",
|
|
"@id": "{{ config.base_url | safe }}/#website",
|
|
"url": "{{ config.base_url | safe }}/",
|
|
"name": {{ config.title | json_encode | safe }},
|
|
"description": {{ config.description | json_encode | safe }},
|
|
"inLanguage": "en-US"
|
|
},
|
|
{
|
|
"@type": "Person",
|
|
"@id": "{{ config.base_url | safe }}/#author",
|
|
"name": {{ config.extra.author | json_encode | safe }},
|
|
"url": "{{ config.base_url | safe }}/about/",
|
|
"sameAs": [
|
|
{% if config.extra.github_url %}"{{ config.extra.github_url | safe }}"{% if config.extra.twitter_url or config.extra.youtube_url %},{% endif %}{% endif %}
|
|
{% if config.extra.twitter_url %}"{{ config.extra.twitter_url | safe }}"{% if config.extra.youtube_url %},{% endif %}{% endif %}
|
|
{% if config.extra.youtube_url %}"{{ config.extra.youtube_url | safe }}"{% endif %}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
</script>
|
|
|
|
{% if config.extra.google_analytics %}
|
|
<!-- Google Analytics -->
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id={{ config.extra.google_analytics }}"></script>
|
|
<script>
|
|
window.dataLayer = window.dataLayer || [];
|
|
function gtag(){dataLayer.push(arguments);}
|
|
gtag('js', new Date());
|
|
gtag('config', '{{ config.extra.google_analytics }}', { anonymize_ip: true });
|
|
</script>
|
|
{% endif %}
|
|
|
|
{% block head %}{% endblock head %}
|
|
|
|
<!-- Font preloads (eliminate FOUT on first paint) -->
|
|
<link rel="preload" href="{{ get_url(path="fonts/source-serif-4/source-serif-4-v14-latin-regular.woff2") }}" as="font" type="font/woff2" crossorigin>
|
|
<link rel="preload" href="{{ get_url(path="fonts/ibm-plex-serif/ibm-plex-serif-v20-latin-700.woff2") }}" as="font" type="font/woff2" crossorigin>
|
|
|
|
<!-- CSS -->
|
|
<link rel="stylesheet" href="{{ get_url(path="main.css") }}">
|
|
|
|
<!-- Fonts (self-hosted) -->
|
|
<link rel="stylesheet" href="{{ get_url(path="css/fonts.css") }}">
|
|
|
|
<!-- RSS Feed -->
|
|
<link rel="alternate" type="application/rss+xml" title="{{ config.title }} RSS Feed" href="{{ get_url(path="rss.xml") }}">
|
|
|
|
</head>
|
|
<body class="smooth-scroll">
|
|
<div class="site-container">
|
|
{% include "partials/header.html" %}
|
|
|
|
<main class="main-content">
|
|
{% block content %}{% endblock content %}
|
|
</main>
|
|
|
|
{% include "partials/footer.html" %}
|
|
</div>
|
|
<button id="scroll-to-top" class="scroll-to-top" aria-label="Scroll to top">
|
|
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
|
<polyline points="18 15 12 9 6 15"></polyline>
|
|
</svg>
|
|
</button>
|
|
<!-- Search Modal Overlay -->
|
|
<div id="search-overlay" class="search-overlay" aria-hidden="true">
|
|
<div class="search-modal" role="dialog" aria-modal="true" aria-label="Search posts">
|
|
<div class="search-header">
|
|
<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
|
|
<input type="text" id="search-input" placeholder="{{ config.extra.search_placeholder | default(value="Search posts...") }}" autocomplete="off">
|
|
</div>
|
|
<div id="search-results" class="search-results"></div>
|
|
<div class="search-footer">
|
|
<span>Navigate: <kbd>↑</kbd> <kbd>↓</kbd></span>
|
|
<span>Select: <kbd>Enter</kbd></span>
|
|
<span>Close: <kbd>ESC</kbd></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Global Scripts -->
|
|
<script defer src="{{ get_url(path="js/theme.js") }}"></script>
|
|
<script defer src="{{ get_url(path="js/scroll-top.js") }}"></script>
|
|
<script defer src="{{ get_url(path="js/copy-code.js") }}"></script>
|
|
<!-- Search Scripts -->
|
|
<script defer src="{{ get_url(path="js/elasticlunr.min.js") }}"></script>
|
|
<script defer src="{{ get_url(path="js/search.js") }}"></script>
|
|
|
|
{% block scripts %}{% endblock scripts %}
|
|
</body>
|
|
</html>
|