feat: complete CDN elimination + SVG icon build pipeline

CDN cleanup:
- entry.html.twig: remove PhotoSwipe CDN + duplicate JS (main.js handles it)
- dailies.html.twig: remove PhotoSwipe CDN + duplicate JS
- home.html.twig: remove PhotoSwipe CDN + MapLibre CDN + maplibre-utils.js
  script tags; add {% block map_assets %} to register map bundle

SVG build pipeline:
- Add lucide-static devDependency; gen-weather-icons.js reads icon SVGs
  from node_modules and emits templates/partials/weather-icons.html.twig
- package.json build script now runs gen-weather-icons.js before esbuild
- entry-journal.html.twig includes generated partial instead of hardcoded map
- Rebuild: main.js 83.7kb, map.js 860.7kb, all fonts; zero CDN requests remain
This commit is contained in:
2026-06-25 00:13:17 +02:00
parent b017d2ff64
commit f6ee4944f9
8 changed files with 82 additions and 272 deletions
+6 -123
View File
@@ -1,129 +1,12 @@
{% extends 'default.html.twig' %}
{% extends 'partials/base.html.twig' %}
{% block content %}
{% set weather_icons = {
'Sunny': '☀️', 'Partly cloudy': '⛅', 'Cloudy': '☁️',
'Foggy': '🌫️', 'Drizzle': '🌦️', 'Rain': '🌧️',
'Snow': '❄️', 'Thunderstorm': '⛈️'
} %}
{% set hero = null %}
{% if page.header.hero_image and page.media[page.header.hero_image] is defined %}
{% set hero = page.media[page.header.hero_image] %}
{% elseif page.media.images|length > 0 %}
{% set hero = page.media.images|first %}
{% endif %}
<a class="back-pill entry-back-fixed" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
<article class="entry">
{% if hero %}
<div class="entry-hero">
<img src="{{ hero.cropResize(1440, 720).url }}" alt="{{ page.title }}" loading="eager">
</div>
{% endif %}
{% set entry = page %}
{% include 'partials/entry-journal.html.twig' %}
<header class="entry-header">
<div class="entry-header-meta">
<time class="entry-date" datetime="{{ page.date|date('Y-m-d') }}">
{{ page.date|date('l, d F Y') }}
</time>
{% if page.header.location_city or page.header.location_country %}
<p class="entry-location">
{%- set _loc = [] -%}
{%- if page.header.location_city -%}{%- set _loc = _loc|merge([page.header.location_city]) -%}{%- endif -%}
{%- if page.header.location_country -%}{%- set _loc = _loc|merge([page.header.location_country]) -%}{%- endif -%}
📍 {{ _loc|join(', ') }}
</p>
{% endif %}
{% if page.header.weather_desc or page.header.weather_temp_c %}
<p class="entry-weather">
{% if page.header.weather_desc %}
{{ weather_icons[page.header.weather_desc] ?? '🌡️' }} {{ page.header.weather_desc }}
{% endif %}
{% if page.header.weather_temp_c %}
· {{ page.header.weather_temp_c|round }}°C
{% endif %}
</p>
{% endif %}
</div>
<h1 class="entry-title">{{ page.title }}</h1>
<div class="entry-title-rule"></div>
</header>
<div class="entry-body">
{{ page.content|raw }}
</div>
{% set images = page.media.images %}
{% if images|length > 0 %}
<div class="entry-gallery" id="entry-gallery">
{% for image in images %}
<button class="gallery-thumb" data-full="{{ image.url }}" data-alt="{{ image.filename }}" aria-label="View {{ image.filename }}">
<img src="{{ image.cropResize(300, 300).url }}" alt="{{ image.filename }}" loading="lazy">
</button>
{% endfor %}
</div>
<div class="lightbox" id="lightbox" role="dialog" aria-modal="true" aria-label="Photo viewer" hidden>
<button class="lightbox-close" id="lb-close" aria-label="Close">✕</button>
<button class="lightbox-prev" id="lb-prev" aria-label="Previous"></button>
<img class="lightbox-img" id="lb-img" src="" alt="">
<button class="lightbox-next" id="lb-next" aria-label="Next"></button>
</div>
<script>
(function() {
var gallery = document.getElementById('entry-gallery');
var lightbox = document.getElementById('lightbox');
var lbImg = document.getElementById('lb-img');
var thumbs = Array.from(gallery.querySelectorAll('.gallery-thumb'));
var current = 0;
function open(index) {
current = index;
var btn = thumbs[index];
lbImg.src = btn.dataset.full;
lbImg.alt = btn.dataset.alt;
lightbox.hidden = false;
document.body.style.overflow = 'hidden';
document.getElementById('lb-close').focus();
}
function close() {
lightbox.hidden = true;
document.body.style.overflow = '';
thumbs[current].focus();
}
function prev() { open((current - 1 + thumbs.length) % thumbs.length); }
function next() { open((current + 1) % thumbs.length); }
thumbs.forEach(function(btn, i) {
btn.addEventListener('click', function() { open(i); });
});
document.getElementById('lb-close').addEventListener('click', close);
document.getElementById('lb-prev').addEventListener('click', prev);
document.getElementById('lb-next').addEventListener('click', next);
lightbox.addEventListener('click', function(e) {
if (e.target === lightbox) close();
});
document.addEventListener('keydown', function(e) {
if (lightbox.hidden) return;
if (e.key === 'Escape') close();
if (e.key === 'ArrowLeft') prev();
if (e.key === 'ArrowRight') next();
});
})();
</script>
{% endif %}
<footer class="entry-footer">
<a class="back-pill" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
</footer>
</article>
<footer class="entry-footer">
<a class="back-pill" href="{{ page.parent().url }}" onclick="if(history.length > 1){ history.back(); return false; }">← Back</a>
</footer>
{% endblock %}