feat: claims for having the js parts in (not working, though)

This commit is contained in:
2026-04-26 19:26:11 +02:00
parent 57d255f79e
commit 09a1dcede3
42 changed files with 2662 additions and 353 deletions

View File

@@ -0,0 +1,169 @@
:root { color-scheme: light dark; }
@media only screen and (prefers-color-scheme: dark) {
:root:not([data-theme]) { --pico-background-color: #13171f; }
}
[data-theme='dark'] { --pico-background-color: #13171f; }
body { max-width: 960px; margin: 0 auto; padding: 2rem 1rem 4rem; background: var(--pico-background-color, var(--background-color)); color: var(--pico-color, var(--color)); }
main { display: grid; gap: 1rem; }
.blog-menu { position: relative; display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; border-top: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-bottom: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: .4rem 0; margin: -.15rem 0 .2rem; }
.blog-menu > .blog-menu-list { width: 100%; }
.blog-menu-list { list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: .25rem .75rem; margin: 0; padding: 0; }
.blog-menu-item { position: relative; }
.blog-menu-link { display: inline-flex; align-items: center; color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .94rem; line-height: 1.4; padding: .2rem .1rem; }
.blog-menu-item-with-children > .blog-menu-link::after { content: '▾'; font-size: .7em; margin-left: .38rem; opacity: .72; }
.blog-menu-link:hover,
.blog-menu-link:focus-visible { color: var(--pico-color, var(--color)); text-decoration: underline; }
.blog-menu-submenu { position: absolute; top: calc(100% + .12rem); left: 0; min-width: 12rem; display: none; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .3rem 0; z-index: 10; }
.blog-menu-submenu .blog-menu-list { flex-direction: column; flex-wrap: nowrap; gap: 0; margin: 0; }
.blog-menu-submenu .blog-menu-item { display: block; padding: 0; margin: 0; }
.blog-menu-submenu .blog-menu-link { display: block; padding: .22rem .75rem; font-size: .88rem; line-height: 1.3; }
.blog-menu-submenu .blog-menu-item a.blog-menu-link { margin: 0; }
.blog-menu-item-with-children:hover > .blog-menu-submenu,
.blog-menu-item-with-children:focus-within > .blog-menu-submenu { display: block; }
.blog-menu-calendar { position: relative; display: inline-flex; align-items: baseline; justify-content: center; margin-left: auto; align-self: baseline; flex-shrink: 0; }
.blog-menu-calendar-button { display: inline-flex; align-items: center; justify-content: center; width: auto; height: auto; margin: 0; padding: .2rem .1rem; border: 0; background: transparent; color: var(--pico-muted-color, var(--muted-color)); border-radius: 0; cursor: pointer; font: inherit; font-size: .94rem; line-height: 1.4; appearance: none; -webkit-appearance: none; vertical-align: baseline; }
.blog-menu-calendar-button svg { display: block; width: .9rem; height: .9rem; fill: none; stroke: currentColor; transform: translateY(2px); }
.blog-menu-calendar-button:hover,
.blog-menu-calendar-button:focus-visible { color: var(--pico-color, var(--color)); }
.blog-calendar-panel { position: absolute; top: calc(100% + .15rem); right: 0; width: min(17.5rem, 92vw); border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .32rem; z-index: 30; }
.blog-calendar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: .1rem; }
.blog-calendar-header strong { font-size: .9rem; line-height: 1.2; }
.blog-calendar-close { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: transparent; color: var(--pico-muted-color, var(--muted-color)); width: 1.35rem; height: 1.35rem; border-radius: .2rem; padding: 0; cursor: pointer; line-height: 1; }
.blog-calendar-close:hover,
.blog-calendar-close:focus-visible { color: var(--pico-color, var(--color)); border-color: var(--pico-color, var(--color)); }
.blog-calendar-content { display: grid; gap: .08rem; }
.blog-calendar-status { margin: .1rem 0 0; color: var(--pico-muted-color, var(--muted-color)); font-size: .74rem; }
[data-blog-calendar-root] { font-size: .86rem; }
[data-blog-calendar-root] [data-vc=header] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc=month],
[data-blog-calendar-root] [data-vc=year] { padding: .08rem .18rem; font-size: .9rem; line-height: 1.15; }
[data-blog-calendar-root] [data-vc=months],
[data-blog-calendar-root] [data-vc=years] { row-gap: .32rem; }
[data-blog-calendar-root] [data-vc=years] { grid-template-columns: repeat(4, minmax(0, 1fr)); }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { height: 1.72rem; }
[data-blog-calendar-root] [data-vc-months-month],
[data-blog-calendar-root] [data-vc-years-year] { word-break: normal; white-space: nowrap; }
[data-blog-calendar-root] [data-vc-years-year] { min-width: 2.5rem; font-size: .7rem; line-height: 1; }
[data-blog-calendar-root] [data-vc-week=days] { margin-bottom: .08rem; }
[data-blog-calendar-root] [data-vc-week-day] { font-size: .68rem; line-height: .9rem; min-width: 1.45rem; }
[data-blog-calendar-root] [data-vc-date] { padding-top: 0; padding-bottom: 0; }
[data-blog-calendar-root] [data-vc-date-btn] { min-height: 1.45rem; min-width: 1.45rem; font-size: .68rem; line-height: .9rem; }
[data-blog-calendar-has-posts='true'] [data-vc-date-btn] {
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
}
[data-blog-calendar-root] [data-vc-months-month][data-blog-calendar-has-posts='true'],
[data-blog-calendar-root] [data-vc-years-year][data-blog-calendar-has-posts='true'] {
background-color: hsl(var(--blog-calendar-heat-hue, 210) 88% 52% / var(--blog-calendar-heat-alpha, 0));
border-color: hsl(var(--blog-calendar-heat-hue, 210) 85% 42% / .95);
}
.post { border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: 1rem; background: var(--pico-card-background-color, var(--card-background-color)); min-width: 0; }
.post pre { position: relative; overflow-x: auto; max-width: 100%; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-radius: .3rem; margin: .9rem 0; padding: .85rem .9rem; background: var(--pico-code-background-color, rgba(33, 38, 45, .82)); box-sizing: border-box; }
.post pre code { display: block; font-size: .88rem; line-height: 1.5; white-space: pre; }
.code-copy-button {
position: absolute;
top: .4rem;
right: .4rem;
border: 1px solid var(--pico-muted-border-color, var(--muted-border-color));
background: var(--pico-card-background-color, var(--card-background-color));
color: var(--pico-muted-color, var(--muted-color));
border-radius: .25rem;
width: 1.8rem;
height: 1.8rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0;
cursor: pointer;
opacity: .88;
}
.code-copy-button:hover,
.code-copy-button:focus-visible { opacity: 1; color: var(--pico-color, var(--color)); }
.code-copy-icon { font-size: .95rem; line-height: 1; }
.code-copy-success .code-copy-button { color: var(--pico-ins-color, rgb(53, 117, 56)); border-color: var(--pico-ins-color, rgb(53, 117, 56)); }
.code-copy-failed .code-copy-button { color: var(--pico-del-color, rgb(183, 72, 72)); border-color: var(--pico-del-color, rgb(183, 72, 72)); }
.post iframe { width: 100%; min-height: 20rem; }
.macro-youtube, .macro-vimeo { margin-bottom: 1rem; }
.macro-gallery, .macro-photo-archive, .macro-tag-cloud { border: 1px dashed var(--pico-muted-border-color, var(--muted-border-color)); padding: .75rem; margin: 1rem 0; }
.gallery-container { display: grid; gap: .5rem; }
.macro-gallery.gallery-cols-1 .gallery-container { grid-template-columns: 1fr; }
.macro-gallery.gallery-cols-2 .gallery-container { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-3 .gallery-container { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-4 .gallery-container { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-5 .gallery-container { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-gallery.gallery-cols-6 .gallery-container { grid-template-columns: repeat(6, minmax(0, 1fr)); }
.gallery-item, .photo-archive-item { display: block; overflow: hidden; border-radius: .25rem; }
.gallery-item img, .photo-archive-item img { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; object-fit: cover; }
.lb-nav a, .lb-nav a:hover, .lb-nav a:focus-visible { border: 0; box-shadow: none; outline: none; text-decoration: none; }
.gallery-caption { margin-top: .5rem; text-align: center; color: var(--pico-muted-color, var(--muted-color)); font-size: .92rem; }
.gallery-empty, .photo-archive-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.photo-archive-container { display: grid; gap: 1rem; }
.photo-archive-month { display: grid; grid-template-columns: 3.25rem 1fr; gap: .75rem; align-items: start; }
.photo-archive-month-label { display: flex; justify-content: center; align-items: center; }
.photo-archive-month-label span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .08em; text-transform: uppercase; color: var(--pico-muted-color, var(--muted-color)); }
.photo-archive-gallery { display: grid; gap: .5rem; grid-template-columns: repeat(4, minmax(0, 1fr)); }
.photo-archive-single-month .photo-archive-gallery { grid-template-columns: repeat(5, minmax(0, 1fr)); }
.macro-tag-cloud { min-height: 14rem; }
.tag-cloud-canvas { display: block; width: 100%; height: auto; min-height: 12rem; }
.tag-cloud-empty { color: var(--pico-muted-color, var(--muted-color)); font-style: italic; }
.archive-day-group { display: grid; grid-template-columns: 5.25rem 1fr; gap: 1.25rem; align-items: stretch; }
.archive-day-marker { display: flex; justify-content: center; align-items: center; color: var(--pico-muted-color, var(--muted-color)); }
.archive-day-marker span { writing-mode: vertical-rl; transform: rotate(180deg); letter-spacing: .16em; font-size: 1.05rem; font-weight: 600; text-transform: uppercase; }
.archive-day-posts { display: grid; gap: 1rem; }
.archive-day-separator { position: relative; height: 2px; width: 100%; color: var(--pico-color, var(--color)); border-top: 1px solid currentColor; opacity: .18; margin: .45rem 0 .65rem; }
.archive-day-separator::before { content: ''; position: absolute; inset: 0; background: linear-gradient(to right, transparent 0%, transparent 18%, currentColor 58%, transparent 92%, transparent 100%); opacity: .85; }
.single-post { margin: 0; padding: 0; background: transparent; border: 0; box-shadow: none; }
.single-post-taxonomy { display: flex; flex-wrap: wrap; gap: .4rem .45rem; margin: -.1rem 0 .2rem; }
.single-post-taxonomy-bubble {
--bubble-accent: var(--pico-ins-color, rgb(53, 117, 56));
--bubble-bg: var(--bubble-accent);
display: inline-flex;
align-items: center;
border: 1px solid var(--bubble-accent);
border-radius: 999px;
padding: .1rem .5rem;
font-size: .74rem;
line-height: 1.35;
color: #000;
background: var(--bubble-bg, var(--bubble-accent));
text-decoration: none;
}
.single-post-taxonomy-bubble:hover,
.single-post-taxonomy-bubble:focus-visible { text-decoration: underline; }
.single-post-taxonomy-bubble-category { --bubble-accent: var(--pico-ins-color, rgb(53, 117, 56)); --bubble-bg: var(--pico-ins-color, rgb(53, 117, 56)); }
.single-post-taxonomy-bubble-tag { --bubble-accent: var(--pico-del-color, rgb(183, 72, 72)); --bubble-bg: var(--pico-del-color, rgb(183, 72, 72)); }
.single-post-backlinks { display: flex; flex-wrap: wrap; gap: .4rem .45rem; align-items: center; margin-top: 1.5rem; }
.single-post-backlinks-label { font-size: .74rem; line-height: 1.35; color: var(--pico-muted-color, var(--muted-color)); margin-right: .15rem; }
.single-post-backlink-bubble { --bubble-accent: var(--pico-primary, rgb(16, 107, 193)); --bubble-bg: var(--pico-primary, rgb(16, 107, 193)); color: var(--pico-primary-inverse, #fff); }
.preview-pagination { display: flex; justify-content: space-between; align-items: center; gap: .75rem; margin-top: .25rem; }
.preview-pagination-link { color: var(--pico-muted-color, var(--muted-color)); text-decoration: none; font-size: .92rem; opacity: .72; transition: opacity .15s ease-in-out; }
.preview-pagination-link:hover,
.preview-pagination-link:focus-visible { opacity: 1; text-decoration: underline; }
.preview-pagination .spacer { flex: 1; }
.not-found { display: grid; place-items: center; min-height: 48vh; }
.not-found article { max-width: 32rem; text-align: center; }
.language-switcher { position: fixed; right: .75rem; top: 1.5rem; display: flex; flex-direction: column; gap: .1rem; z-index: 100; }
.language-switcher-badge { display: block; padding: .05rem .1rem; font-size: .85rem; line-height: 1.1; text-decoration: none; border: 1px solid transparent; border-radius: .15rem; cursor: pointer; opacity: .7; transition: opacity .15s ease-in-out; }
.language-switcher-badge:hover,
.language-switcher-badge:focus-visible { opacity: 1; border-color: var(--pico-color, var(--color)); }
.language-switcher-badge-current { opacity: 1; border-color: var(--pico-primary, var(--primary)); }
.blog-search-widget, .blog-search-standalone { position: relative; margin-top: .15rem; }
.blog-search-standalone { position: fixed; right: .75rem; top: 1.5rem; z-index: 100; }
.blog-search-toggle { display: inline-flex; align-items: center; justify-content: center; border: 0; background: transparent; color: var(--pico-muted-color, var(--muted-color)); cursor: pointer; padding: .15rem; opacity: .7; transition: opacity .15s ease-in-out; }
.blog-search-toggle:hover, .blog-search-toggle:focus-visible { opacity: 1; color: var(--pico-color, var(--color)); }
.blog-search-toggle svg { display: block; }
.blog-search-panel { position: absolute; top: calc(100% + .25rem); right: 0; width: min(24rem, 90vw); z-index: 40; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-card-background-color, var(--card-background-color)); padding: .5rem; border-radius: .35rem; box-shadow: 0 4px 24px rgba(0,0,0,.25); }
.blog-search-panel .pagefind-ui { --pagefind-ui-scale: .8; --pagefind-ui-primary: var(--pico-primary, var(--primary)); --pagefind-ui-text: var(--pico-color, var(--color)); --pagefind-ui-background: var(--pico-card-background-color, var(--card-background-color)); --pagefind-ui-border: var(--pico-muted-border-color, var(--muted-border-color)); --pagefind-ui-tag: var(--pico-muted-border-color, var(--muted-border-color)); --pagefind-ui-border-width: 1px; --pagefind-ui-border-radius: .2rem; --pagefind-ui-image-border-radius: .2rem; --pagefind-ui-image-box-ratio: 0; --pagefind-ui-font: inherit; font-size: .85rem; }
.blog-search-panel .pagefind-ui__search-input { font-size: .85rem; padding: .3rem .5rem; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); background: var(--pico-background-color, var(--background-color)); color: var(--pico-color, var(--color)); border-radius: .2rem; width: 100%; }
.blog-search-panel .pagefind-ui__search-clear { color: var(--pico-muted-color, var(--muted-color)); background: none; font-size: .8rem; }
.blog-search-panel .pagefind-ui__search-clear:focus { outline-color: var(--pico-primary, var(--primary)); }
.blog-search-panel .pagefind-ui__drawer { max-height: min(60vh, 28rem); overflow-y: auto; }
.blog-search-panel .pagefind-ui__message { color: var(--pico-muted-color, var(--muted-color)); font-size: .78rem; padding: .25rem 0; }
.blog-search-panel .pagefind-ui__result { border-top: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); padding: .4rem 0; }
.blog-search-panel .pagefind-ui__result-link { color: var(--pico-primary, var(--primary)); font-size: .85rem; }
.blog-search-panel .pagefind-ui__result-title { font-size: .85rem; }
.blog-search-panel .pagefind-ui__result-excerpt { font-size: .78rem; color: var(--pico-muted-color, var(--muted-color)); }
.blog-search-panel .pagefind-ui__result-excerpt mark { background-color: var(--pico-primary-focus, rgba(255,223,0,.35)); color: inherit; }
.blog-search-panel .pagefind-ui__button { color: var(--pico-primary, var(--primary)); background: none; border: 1px solid var(--pico-muted-border-color, var(--muted-border-color)); border-radius: .2rem; font-size: .78rem; cursor: pointer; }
.blog-search-panel .pagefind-ui__button:hover { border-color: var(--pico-primary, var(--primary)); }

View File

@@ -0,0 +1,299 @@
(() => {
const button = document.querySelector('[data-blog-calendar-toggle]');
const panel = document.querySelector('[data-blog-calendar-panel]');
const closeButton = document.querySelector('[data-blog-calendar-close]');
const calendarRoot = document.querySelector('[data-blog-calendar-root]');
const status = document.querySelector('[data-blog-calendar-status]');
if (!button || !panel || !calendarRoot || !status) {
return;
}
const languagePrefix = document.documentElement.getAttribute('data-language-prefix') || '';
const labels = {
loading: panel.getAttribute('data-i18n-loading') || 'Loading calendar…',
error: panel.getAttribute('data-i18n-error') || 'Calendar data could not be loaded.',
};
let isInitialized = false;
let years = {};
let months = {};
let days = {};
let maxYearCount = 0;
let maxMonthCount = 0;
let maxDayCount = 0;
function pad2(value) {
return String(value).padStart(2, '0');
}
function normalizeCountMap(value) {
if (!value || typeof value !== 'object') {
return {};
}
const map = {};
for (const [key, rawCount] of Object.entries(value)) {
const count = Number(rawCount);
if (!Number.isFinite(count) || count <= 0) {
continue;
}
map[key] = Math.floor(count);
}
return map;
}
function computeMaxCount(value) {
const counts = Object.values(value || {});
if (counts.length === 0) {
return 0;
}
return Math.max(...counts.map((count) => Number(count) || 0));
}
function applyHeatStyle(target, count, maxCount) {
if (!(target instanceof HTMLElement) || !Number.isFinite(count) || count <= 0 || !Number.isFinite(maxCount) || maxCount <= 0) {
target?.style?.setProperty('--blog-calendar-heat-alpha', '0');
target?.style?.setProperty('--blog-calendar-heat-hue', '210');
return;
}
const normalized = Math.min(1, count / maxCount);
const hue = Math.round(210 - (210 * normalized));
const alpha = (0.30 + normalized * 0.65).toFixed(3);
target.style.setProperty('--blog-calendar-heat-hue', String(hue));
target.style.setProperty('--blog-calendar-heat-alpha', alpha);
}
function navigateTo(pathname) {
if (!pathname) {
return;
}
window.location.assign(languagePrefix + pathname);
}
function parseInitialYearMonth() {
const initialYearRaw = button.getAttribute('data-blog-calendar-year');
const initialMonthRaw = button.getAttribute('data-blog-calendar-month');
const initialYear = Number(initialYearRaw);
const initialMonth = Number(initialMonthRaw);
let selectedYear = Number.isInteger(initialYear) && initialYear > 0 ? initialYear : null;
let selectedMonth = Number.isInteger(initialMonth) && initialMonth >= 1 && initialMonth <= 12
? (initialMonth - 1)
: null;
if (!Number.isInteger(selectedYear) || !Number.isInteger(selectedMonth)) {
const rawPathname = window.location.pathname || '';
const pathname = languagePrefix && rawPathname.startsWith(languagePrefix + '/')
? rawPathname.slice(languagePrefix.length)
: rawPathname;
const parts = pathname.split('/').filter(Boolean);
const pathYear = Number(parts[0]);
const pathMonth = Number(parts[1]);
if (!Number.isInteger(selectedYear) && Number.isInteger(pathYear) && pathYear > 0 && String(parts[0]).length === 4) {
selectedYear = pathYear;
}
if (!Number.isInteger(selectedMonth) && Number.isInteger(pathMonth) && pathMonth >= 1 && pathMonth <= 12) {
selectedMonth = pathMonth - 1;
}
}
return { selectedYear, selectedMonth };
}
async function loadCalendarData() {
const response = await fetch('/calendar.json', { cache: 'no-store' });
if (!response.ok) {
throw new Error('calendar.json request failed');
}
const parsed = await response.json();
years = normalizeCountMap(parsed?.years);
months = normalizeCountMap(parsed?.months);
days = normalizeCountMap(parsed?.days);
maxYearCount = computeMaxCount(years);
maxMonthCount = computeMaxCount(months);
maxDayCount = computeMaxCount(days);
}
function getDateFromClickEvent(event) {
if (!(event?.target instanceof Element)) {
return '';
}
const dateEl = event.target.closest('[data-vc-date]');
if (!(dateEl instanceof HTMLElement)) {
return '';
}
return dateEl.dataset.vcDate || '';
}
async function initializeCalendar() {
if (isInitialized) {
return;
}
status.textContent = labels.loading;
try {
await loadCalendarData();
const Calendar = window.VanillaCalendarPro?.Calendar;
if (typeof Calendar !== 'function') {
throw new Error('Vanilla Calendar Pro is unavailable');
}
const initialYearMonth = parseInitialYearMonth();
const calendarOptions = {
...(Number.isInteger(initialYearMonth.selectedYear) ? { selectedYear: initialYearMonth.selectedYear } : {}),
...(Number.isInteger(initialYearMonth.selectedMonth) ? { selectedMonth: initialYearMonth.selectedMonth } : {}),
onCreateDateEls(_self, dateEl) {
const dateKey = dateEl.dataset.vcDate || '';
const count = Number(days[dateKey] || 0);
const buttonEl = dateEl.querySelector('[data-vc-date-btn]');
if (!(buttonEl instanceof HTMLElement)) {
return;
}
if (count <= 0) {
dateEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(buttonEl, 0, maxDayCount);
return;
}
dateEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(buttonEl, count, maxDayCount);
},
onCreateMonthEls(self, monthEl) {
if (!(monthEl instanceof HTMLElement)) {
return;
}
const monthIndex = Number(monthEl.dataset.vcMonthsMonth);
const selectedYear = Number(self?.context?.selectedYear);
if (!Number.isInteger(monthIndex) || !Number.isInteger(selectedYear)) {
monthEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(monthEl, 0, maxMonthCount);
return;
}
const monthKey = String(selectedYear) + '-' + pad2(monthIndex + 1);
const count = Number(months[monthKey] || 0);
if (count <= 0) {
monthEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(monthEl, 0, maxMonthCount);
return;
}
monthEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(monthEl, count, maxMonthCount);
},
onCreateYearEls(_self, yearEl) {
if (!(yearEl instanceof HTMLElement)) {
return;
}
const yearValue = Number(yearEl.dataset.vcYearsYear);
if (!Number.isInteger(yearValue)) {
yearEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(yearEl, 0, maxYearCount);
return;
}
const yearKey = String(yearValue);
const count = Number(years[yearKey] || 0);
if (count <= 0) {
yearEl.removeAttribute('data-blog-calendar-has-posts');
applyHeatStyle(yearEl, 0, maxYearCount);
return;
}
yearEl.setAttribute('data-blog-calendar-has-posts', 'true');
applyHeatStyle(yearEl, count, maxYearCount);
},
onClickDate(_self, event) {
const dateKey = getDateFromClickEvent(event);
if (!dateKey || !days[dateKey]) {
return;
}
const [year, month, day] = dateKey.split('-');
if (!year || !month || !day) {
return;
}
navigateTo('/' + year + '/' + month + '/' + day + '/');
},
onClickMonth(self) {
const selectedYear = Number(self?.context?.selectedYear);
const selectedMonth = Number(self?.context?.selectedMonth);
if (!Number.isInteger(selectedYear) || !Number.isInteger(selectedMonth)) {
return;
}
const monthKey = String(selectedYear) + '-' + pad2(selectedMonth + 1);
if (!months[monthKey]) {
return;
}
navigateTo('/' + String(selectedYear) + '/' + pad2(selectedMonth + 1) + '/');
},
onClickYear(self) {
const selectedYear = Number(self?.context?.selectedYear);
if (!Number.isInteger(selectedYear)) {
return;
}
const yearKey = String(selectedYear);
if (!years[yearKey]) {
return;
}
navigateTo('/' + String(selectedYear) + '/');
},
};
const calendar = new Calendar('[data-blog-calendar-root]', calendarOptions);
calendar.init();
status.textContent = '';
status.setAttribute('hidden', 'hidden');
isInitialized = true;
} catch {
status.textContent = labels.error;
status.removeAttribute('hidden');
}
}
function setPanelOpen(isOpen) {
if (isOpen) {
panel.removeAttribute('hidden');
void initializeCalendar();
return;
}
panel.setAttribute('hidden', 'hidden');
}
button.addEventListener('click', () => {
const isHidden = panel.hasAttribute('hidden');
setPanelOpen(isHidden);
});
if (closeButton) {
closeButton.addEventListener('click', () => {
setPanelOpen(false);
});
}
})();

View File

@@ -0,0 +1,136 @@
(function () {
function resolveCodeLanguage(codeElement) {
if (!codeElement) {
return '';
}
var direct = codeElement.getAttribute('data-code-language');
if (typeof direct === 'string' && direct.trim()) {
return direct.trim().toLowerCase();
}
var className = codeElement.className || '';
var classMatch = className.match(/(?:^|\s)language-([\w.+-]+)/i);
if (classMatch && classMatch[1]) {
return classMatch[1].toLowerCase();
}
return '';
}
function fallbackCopy(value) {
var textarea = document.createElement('textarea');
textarea.value = value;
textarea.setAttribute('readonly', 'readonly');
textarea.style.position = 'fixed';
textarea.style.opacity = '0';
textarea.style.pointerEvents = 'none';
document.body.appendChild(textarea);
textarea.focus();
textarea.select();
try {
return document.execCommand('copy');
} catch {
return false;
} finally {
document.body.removeChild(textarea);
}
}
async function copyCodeToClipboard(value) {
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
try {
await navigator.clipboard.writeText(value);
return true;
} catch {
return fallbackCopy(value);
}
}
return fallbackCopy(value);
}
function ensureCopyButton(preElement, codeElement) {
if (!preElement || preElement.querySelector(':scope > .code-copy-button')) {
return;
}
preElement.classList.add('code-block-enhanced');
var button = document.createElement('button');
button.type = 'button';
button.className = 'code-copy-button';
button.setAttribute('aria-hidden', 'true');
var icon = document.createElement('span');
icon.className = 'code-copy-icon';
icon.textContent = '⧉';
button.appendChild(icon);
button.addEventListener('click', async function () {
var codeText = codeElement.textContent || '';
var copied = await copyCodeToClipboard(codeText);
preElement.classList.remove('code-copy-failed');
preElement.classList.remove('code-copy-success');
preElement.classList.add(copied ? 'code-copy-success' : 'code-copy-failed');
if (copied) {
icon.textContent = '✓';
window.setTimeout(function () {
preElement.classList.remove('code-copy-success');
icon.textContent = '⧉';
}, 1200);
return;
}
window.setTimeout(function () {
preElement.classList.remove('code-copy-failed');
}, 1200);
});
preElement.appendChild(button);
}
function highlightCodeBlock(codeElement) {
var highlighter = window.hljs;
if (!highlighter || typeof highlighter.highlightElement !== 'function') {
return;
}
if (codeElement.getAttribute('data-code-highlighted') === 'true') {
return;
}
try {
highlighter.highlightElement(codeElement);
codeElement.setAttribute('data-code-highlighted', 'true');
} catch {
}
}
function initCodeBlocks() {
var codeNodes = document.querySelectorAll('pre > code');
codeNodes.forEach(function (codeElement) {
var preElement = codeElement.parentElement;
if (!preElement || preElement.tagName !== 'PRE') {
return;
}
var language = resolveCodeLanguage(codeElement);
if (language) {
codeElement.setAttribute('data-code-language', language);
preElement.setAttribute('data-code-language', language);
}
ensureCopyButton(preElement, codeElement);
highlightCodeBlock(codeElement);
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initCodeBlocks, { once: true });
} else {
initCodeBlocks();
}
})();

View File

@@ -0,0 +1,505 @@
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.d3||(g.d3 = {}));g=(g.layout||(g.layout = {}));g.cloud = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}return r})()({1:[function(require,module,exports){
// Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/
// Algorithm due to Jonathan Feinberg, https://s3.amazonaws.com/static.mrfeinberg.com/bv_ch03.pdf
const dispatch = require("d3-dispatch").dispatch;
const RADIANS = Math.PI / 180;
const SPIRALS = {
archimedean: archimedeanSpiral,
rectangular: rectangularSpiral
};
const cw = 1 << 11 >> 5;
const ch = 1 << 11;
module.exports = function() {
var size = [256, 256],
text = cloudText,
font = cloudFont,
fontSize = cloudFontSize,
fontStyle = cloudFontNormal,
fontWeight = cloudFontNormal,
rotate = cloudRotate,
padding = cloudPadding,
spiral = archimedeanSpiral,
words = [],
timeInterval = Infinity,
event = dispatch("word", "end"),
timer = null,
random = Math.random,
cloud = {},
canvas = cloudCanvas;
cloud.canvas = function(_) {
return arguments.length ? (canvas = functor(_), cloud) : canvas;
};
cloud.start = function() {
var contextAndRatio = getContext(canvas()),
board = zeroArray((size[0] >> 5) * size[1]),
bounds = null,
n = words.length,
i = -1,
tags = [],
data = words.map(function(d, i) {
d.text = text.call(this, d, i);
d.font = font.call(this, d, i);
d.style = fontStyle.call(this, d, i);
d.weight = fontWeight.call(this, d, i);
d.rotate = rotate.call(this, d, i);
d.size = ~~fontSize.call(this, d, i);
d.padding = padding.call(this, d, i);
return d;
}).sort(function(a, b) { return b.size - a.size; });
if (timer) clearInterval(timer);
timer = setInterval(step, 0);
step();
return cloud;
function step() {
var start = Date.now();
while (Date.now() - start < timeInterval && ++i < n && timer) {
var d = data[i];
d.x = (size[0] * (random() + .5)) >> 1;
d.y = (size[1] * (random() + .5)) >> 1;
cloudSprite(contextAndRatio, d, data, i);
if (d.hasText && place(board, d, bounds)) {
tags.push(d);
event.call("word", cloud, d);
if (bounds) cloudBounds(bounds, d);
else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
// Temporary hack
d.x -= size[0] >> 1;
d.y -= size[1] >> 1;
}
}
if (i >= n) {
cloud.stop();
event.call("end", cloud, tags, bounds);
}
}
}
cloud.stop = function() {
if (timer) {
clearInterval(timer);
timer = null;
}
for (const d of words) {
delete d.sprite;
}
return cloud;
};
function getContext(canvas) {
const context = canvas.getContext("2d", {willReadFrequently: true});
canvas.width = canvas.height = 1;
const ratio = Math.sqrt(context.getImageData(0, 0, 1, 1).data.length >> 2);
canvas.width = (cw << 5) / ratio;
canvas.height = ch / ratio;
context.fillStyle = context.strokeStyle = "red";
return {context, ratio};
}
function place(board, tag, bounds) {
var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
startX = tag.x,
startY = tag.y,
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
s = spiral(size),
dt = random() < .5 ? 1 : -1,
t = -dt,
dxdy,
dx,
dy;
while (dxdy = s(t += dt)) {
dx = ~~dxdy[0];
dy = ~~dxdy[1];
if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
tag.x = startX + dx;
tag.y = startY + dy;
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
// TODO only check for collisions within current bounds.
if (!bounds || collideRects(tag, bounds)) {
if (!cloudCollide(tag, board, size[0])) {
var sprite = tag.sprite,
w = tag.width >> 5,
sw = size[0] >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
}
x += sw;
}
return true;
}
}
}
return false;
}
cloud.timeInterval = function(_) {
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
};
cloud.words = function(_) {
return arguments.length ? (words = _, cloud) : words;
};
cloud.size = function(_) {
return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
};
cloud.font = function(_) {
return arguments.length ? (font = functor(_), cloud) : font;
};
cloud.fontStyle = function(_) {
return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
};
cloud.fontWeight = function(_) {
return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
};
cloud.rotate = function(_) {
return arguments.length ? (rotate = functor(_), cloud) : rotate;
};
cloud.text = function(_) {
return arguments.length ? (text = functor(_), cloud) : text;
};
cloud.spiral = function(_) {
return arguments.length ? (spiral = SPIRALS[_] || _, cloud) : spiral;
};
cloud.fontSize = function(_) {
return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
};
cloud.padding = function(_) {
return arguments.length ? (padding = functor(_), cloud) : padding;
};
cloud.random = function(_) {
return arguments.length ? (random = _, cloud) : random;
};
cloud.on = function() {
var value = event.on.apply(event, arguments);
return value === event ? cloud : value;
};
return cloud;
};
function cloudText(d) {
return d.text;
}
function cloudFont() {
return "serif";
}
function cloudFontNormal() {
return "normal";
}
function cloudFontSize(d) {
return Math.sqrt(d.value);
}
function cloudRotate() {
return (~~(random() * 6) - 3) * 30;
}
function cloudPadding() {
return 1;
}
// Fetches a monochrome sprite bitmap for the specified text.
// Load in batches for speed.
function cloudSprite(contextAndRatio, d, data, di) {
if (d.sprite) return;
var c = contextAndRatio.context,
ratio = contextAndRatio.ratio;
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
var x = 0,
y = 0,
maxh = 0,
n = data.length;
--di;
while (++di < n) {
d = data[di];
c.save();
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
const metrics = c.measureText(d.text);
const anchor = -Math.floor(metrics.width / 2);
let w = (metrics.width + 1) * ratio;
let h = d.size << 1;
if (d.rotate) {
var sr = Math.sin(d.rotate * RADIANS),
cr = Math.cos(d.rotate * RADIANS),
wcr = w * cr,
wsr = w * sr,
hcr = h * cr,
hsr = h * sr;
w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
} else {
w = (w + 0x1f) >> 5 << 5;
}
if (h > maxh) maxh = h;
if (x + w >= (cw << 5)) {
x = 0;
y += maxh;
maxh = 0;
}
if (y + h >= ch) break;
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
if (d.rotate) c.rotate(d.rotate * RADIANS);
c.fillText(d.text, anchor, 0);
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, anchor, 0);
c.restore();
d.width = w;
d.height = h;
d.xoff = x;
d.yoff = y;
d.x1 = w >> 1;
d.y1 = h >> 1;
d.x0 = -d.x1;
d.y0 = -d.y1;
d.hasText = true;
x += w;
}
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
sprite = [];
while (--di >= 0) {
d = data[di];
if (!d.hasText) continue;
var w = d.width,
w32 = w >> 5,
h = d.y1 - d.y0;
// Zero the buffer
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
x = d.xoff;
if (x == null) return;
y = d.yoff;
var seen = 0,
seenRow = -1;
for (var j = 0; j < h; j++) {
for (var i = 0; i < w; i++) {
var k = w32 * j + (i >> 5),
m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
sprite[k] |= m;
seen |= m;
}
if (seen) seenRow = j;
else {
d.y0++;
h--;
j--;
y++;
}
}
d.y1 = d.y0 + seenRow;
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
}
}
// Use mask-based collision detection.
function cloudCollide(tag, board, sw) {
sw >>= 5;
var sprite = tag.sprite,
w = tag.width >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
& board[x + i]) return true;
}
x += sw;
}
return false;
}
function cloudBounds(bounds, d) {
var b0 = bounds[0],
b1 = bounds[1];
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
}
function collideRects(a, b) {
return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
}
function archimedeanSpiral(size) {
var e = size[0] / size[1];
return function(t) {
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
};
}
function rectangularSpiral(size) {
var dy = 4,
dx = dy * size[0] / size[1],
x = 0,
y = 0;
return function(t) {
var sign = t < 0 ? -1 : 1;
// See triangular numbers: T_n = n * (n + 1) / 2.
switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
case 0: x += dx; break;
case 1: y += dy; break;
case 2: x -= dx; break;
default: y -= dy; break;
}
return [x, y];
};
}
// TODO reuse arrays?
function zeroArray(n) {
var a = [],
i = -1;
while (++i < n) a[i] = 0;
return a;
}
function cloudCanvas() {
return document.createElement("canvas");
}
function functor(d) {
return typeof d === "function" ? d : function() { return d; };
}
},{"d3-dispatch":2}],2:[function(require,module,exports){
// https://d3js.org/d3-dispatch/ v1.0.6 Copyright 2019 Mike Bostock
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = global || self, factory(global.d3 = global.d3 || {}));
}(this, function (exports) { 'use strict';
var noop = {value: function() {}};
function dispatch() {
for (var i = 0, n = arguments.length, _ = {}, t; i < n; ++i) {
if (!(t = arguments[i] + "") || (t in _) || /[\s.]/.test(t)) throw new Error("illegal type: " + t);
_[t] = [];
}
return new Dispatch(_);
}
function Dispatch(_) {
this._ = _;
}
function parseTypenames(typenames, types) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
if (t && !types.hasOwnProperty(t)) throw new Error("unknown type: " + t);
return {type: t, name: name};
});
}
Dispatch.prototype = dispatch.prototype = {
constructor: Dispatch,
on: function(typename, callback) {
var _ = this._,
T = parseTypenames(typename + "", _),
t,
i = -1,
n = T.length;
// If no callback was specified, return the callback of the given type and name.
if (arguments.length < 2) {
while (++i < n) if ((t = (typename = T[i]).type) && (t = get(_[t], typename.name))) return t;
return;
}
// If a type was specified, set the callback for the given type and name.
// Otherwise, if a null callback was specified, remove callbacks of the given name.
if (callback != null && typeof callback !== "function") throw new Error("invalid callback: " + callback);
while (++i < n) {
if (t = (typename = T[i]).type) _[t] = set(_[t], typename.name, callback);
else if (callback == null) for (t in _) _[t] = set(_[t], typename.name, null);
}
return this;
},
copy: function() {
var copy = {}, _ = this._;
for (var t in _) copy[t] = _[t].slice();
return new Dispatch(copy);
},
call: function(type, that) {
if ((n = arguments.length - 2) > 0) for (var args = new Array(n), i = 0, n, t; i < n; ++i) args[i] = arguments[i + 2];
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
},
apply: function(type, that, args) {
if (!this._.hasOwnProperty(type)) throw new Error("unknown type: " + type);
for (var t = this._[type], i = 0, n = t.length; i < n; ++i) t[i].value.apply(that, args);
}
};
function get(type, name) {
for (var i = 0, n = type.length, c; i < n; ++i) {
if ((c = type[i]).name === name) {
return c.value;
}
}
}
function set(type, name, callback) {
for (var i = 0, n = type.length; i < n; ++i) {
if (type[i].name === name) {
type[i] = noop, type = type.slice(0, i).concat(type.slice(i + 1));
break;
}
}
if (callback != null) type.push({name: name, value: callback});
return type;
}
exports.dispatch = dispatch;
Object.defineProperty(exports, '__esModule', { value: true });
}));
},{}]},{},[1])(1)
});

View File

@@ -0,0 +1,10 @@
pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*!
Theme: GitHub Dark
Description: Dark theme as seen on github.com
Author: github.com
Maintainer: @Hirse
Updated: 2021-05-15
Outdated base version: https://github.com/primer/github-syntax-dark
Current colors taken from GitHub's CSS
*/.hljs{color:#c9d1d9;background:#0d1117}.hljs-doctag,.hljs-keyword,.hljs-meta .hljs-keyword,.hljs-template-tag,.hljs-template-variable,.hljs-type,.hljs-variable.language_{color:#ff7b72}.hljs-title,.hljs-title.class_,.hljs-title.class_.inherited__,.hljs-title.function_{color:#d2a8ff}.hljs-attr,.hljs-attribute,.hljs-literal,.hljs-meta,.hljs-number,.hljs-operator,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-variable{color:#79c0ff}.hljs-meta .hljs-string,.hljs-regexp,.hljs-string{color:#a5d6ff}.hljs-built_in,.hljs-symbol{color:#ffa657}.hljs-code,.hljs-comment,.hljs-formula{color:#8b949e}.hljs-name,.hljs-quote,.hljs-selector-pseudo,.hljs-selector-tag{color:#7ee787}.hljs-subst{color:#c9d1d9}.hljs-section{color:#1f6feb;font-weight:700}.hljs-bullet{color:#f2cc60}.hljs-emphasis{color:#c9d1d9;font-style:italic}.hljs-strong{color:#c9d1d9;font-weight:700}.hljs-addition{color:#aff5b4;background-color:#033a16}.hljs-deletion{color:#ffdcd7;background-color:#67060c}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.lb-loader,.lightbox{text-align:center;line-height:0}body.lb-disable-scrolling{overflow:hidden}.lightboxOverlay{position:absolute;top:0;left:0;z-index:9999;background-color:#000;opacity:.8;display:none}.lightbox{position:absolute;left:0;width:100%;z-index:10000;font-weight:400;outline:0}.lightbox .lb-image{display:block;height:auto;max-width:inherit;max-height:none;border-radius:3px;border:4px solid #fff}.lightbox a img{border:none}.lb-outerContainer{position:relative;width:250px;height:250px;margin:0 auto;border-radius:4px;background-color:#fff}.lb-loader,.lb-nav{position:absolute;left:0}.lb-outerContainer:after{content:"";display:table;clear:both}.lb-loader{top:43%;height:25%;width:100%}.lb-cancel{display:block;width:32px;height:32px;margin:0 auto;background:url(../images/loading.gif) no-repeat}.lb-nav{top:0;height:100%;width:100%;z-index:10}.lb-container>.nav{left:0}.lb-nav a{outline:0;background-image:url(data:image/gif;base64,R0lGODlhAQABAPAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==)}.lb-next,.lb-prev{height:100%;cursor:pointer;display:block}.lb-nav a.lb-prev{width:34%;left:0;float:left;background:url(../images/prev.png) left 48% no-repeat;opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-prev:hover{opacity:1}.lb-nav a.lb-next{width:64%;right:0;float:right;background:url(../images/next.png) right 48% no-repeat;opacity:0;-webkit-transition:opacity .6s;-moz-transition:opacity .6s;-o-transition:opacity .6s;transition:opacity .6s}.lb-nav a.lb-next:hover{opacity:1}.lb-dataContainer{margin:0 auto;padding-top:5px;width:100%;border-bottom-left-radius:4px;border-bottom-right-radius:4px}.lb-dataContainer:after{content:"";display:table;clear:both}.lb-data{padding:0 4px;color:#ccc}.lb-data .lb-details{width:85%;float:left;text-align:left;line-height:1.1em}.lb-data .lb-caption{font-size:13px;font-weight:700;line-height:1em}.lb-data .lb-caption a{color:#4ae}.lb-data .lb-number{display:block;clear:left;padding-bottom:1em;font-size:12px;color:#999}.lb-data .lb-close{display:block;float:right;width:30px;height:30px;background:url(../images/close.png) top right no-repeat;text-align:right;outline:0;opacity:.7;-webkit-transition:opacity .2s;-moz-transition:opacity .2s;-o-transition:opacity .2s;transition:opacity .2s}.lb-data .lb-close:hover{cursor:pointer;opacity:1}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,52 @@
(() => {
const toggle = document.querySelector('[data-blog-search-toggle]');
const panel = document.querySelector('[data-blog-search-panel]');
const root = document.querySelector('[data-blog-search-root]');
if (!toggle || !panel || !root) {
return;
}
let initialized = false;
function initSearch() {
if (initialized || typeof PagefindUI === 'undefined') {
return;
}
initialized = true;
var placeholder = root.getAttribute('data-search-placeholder') || 'Search...';
new PagefindUI({
element: root,
showSubResults: true,
showImages: false,
translations: { placeholder: placeholder }
});
var input = root.querySelector('input');
if (input) {
input.focus();
}
}
toggle.addEventListener('click', function() {
var isHidden = panel.hasAttribute('hidden');
if (isHidden) {
panel.removeAttribute('hidden');
initSearch();
} else {
panel.setAttribute('hidden', '');
}
});
document.addEventListener('click', function(e) {
if (!panel.hasAttribute('hidden') && !panel.contains(e.target) && !toggle.contains(e.target)) {
panel.setAttribute('hidden', '');
}
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && !panel.hasAttribute('hidden')) {
panel.setAttribute('hidden', '');
toggle.focus();
}
});
})();

View File

@@ -0,0 +1,272 @@
(function () {
function parseWords(rawWords) {
if (!rawWords || typeof rawWords !== 'string') {
return [];
}
try {
const parsed = JSON.parse(rawWords);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
function clamp01(value) {
if (!Number.isFinite(value)) {
return 0;
}
if (value < 0) {
return 0;
}
if (value > 1) {
return 1;
}
return value;
}
function parseCssColor(colorValue) {
if (typeof colorValue !== 'string') {
return null;
}
const value = colorValue.trim();
if (!value) {
return null;
}
const hexMatch = value.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
if (hexMatch) {
const hex = hexMatch[1];
if (hex.length === 3) {
return [
Number.parseInt(hex[0] + hex[0], 16),
Number.parseInt(hex[1] + hex[1], 16),
Number.parseInt(hex[2] + hex[2], 16),
];
}
return [
Number.parseInt(hex.slice(0, 2), 16),
Number.parseInt(hex.slice(2, 4), 16),
Number.parseInt(hex.slice(4, 6), 16),
];
}
const rgbMatch = value.match(/^rgba?\\(([^)]+)\\)$/i);
if (rgbMatch) {
const channels = rgbMatch[1]
.split(',')
.map((channel) => channel.trim())
.slice(0, 3)
.map((channel) => {
if (channel.endsWith('%')) {
return Math.round((Number.parseFloat(channel) / 100) * 255);
}
return Math.round(Number.parseFloat(channel));
});
if (channels.length === 3 && channels.every((channel) => Number.isFinite(channel))) {
return channels.map((channel) => Math.max(0, Math.min(255, channel)));
}
}
return null;
}
function interpolateColor(fromColor, toColor, t) {
return [
Math.round(fromColor[0] + ((toColor[0] - fromColor[0]) * t)),
Math.round(fromColor[1] + ((toColor[1] - fromColor[1]) * t)),
Math.round(fromColor[2] + ((toColor[2] - fromColor[2]) * t)),
];
}
function mixColor(fromColor, toColor, weight) {
return interpolateColor(fromColor, toColor, clamp01(weight));
}
function colorToCss(color) {
return 'rgb(' + color[0] + ',' + color[1] + ',' + color[2] + ')';
}
function getPicoThemeStops() {
const style = window.getComputedStyle(document.documentElement);
const blue = parseCssColor(style.getPropertyValue('--pico-secondary')) || [74, 99, 146];
const green = parseCssColor(style.getPropertyValue('--pico-ins-color')) || [53, 117, 56];
const red = parseCssColor(style.getPropertyValue('--pico-del-color')) || [183, 72, 72];
const yellow = mixColor(green, red, 0.45);
const orange = mixColor(green, red, 0.72);
return [blue, green, yellow, orange, red];
}
function interpolateStops(stops, value) {
if (!Array.isArray(stops) || stops.length === 0) {
return 'currentColor';
}
if (stops.length === 1) {
return colorToCss(stops[0]);
}
const clamped = clamp01(value);
const scaled = clamped * (stops.length - 1);
const lowerIndex = Math.floor(scaled);
const upperIndex = Math.min(stops.length - 1, lowerIndex + 1);
const localT = scaled - lowerIndex;
return colorToCss(interpolateColor(stops[lowerIndex], stops[upperIndex], localT));
}
function resolveQuantileColorMap(words) {
const counts = Array.from(
new Set(words.map((word) => Number(word.count)).filter((count) => Number.isFinite(count)))
).sort((a, b) => a - b);
const quantiles = new Map();
if (counts.length === 0) {
return quantiles;
}
if (counts.length === 1) {
quantiles.set(counts[0], 1);
return quantiles;
}
counts.forEach((count, index) => {
quantiles.set(count, index / (counts.length - 1));
});
return quantiles;
}
function applyThemeAwareColors(words, container) {
const gammaRaw = Number.parseFloat(container.getAttribute('data-color-easing') || '0.7');
const gamma = Number.isFinite(gammaRaw) && gammaRaw > 0 ? gammaRaw : 0.7;
const quantiles = resolveQuantileColorMap(words);
const stops = getPicoThemeStops();
return words.map((word) => {
const count = Number(word.count);
const quantile = quantiles.get(count) ?? 0;
const eased = Math.pow(clamp01(quantile), gamma);
return {
...word,
color: interpolateStops(stops, eased),
};
});
}
function drawTagCloud(container) {
const cloudFactory = window.d3 && window.d3.layout && typeof window.d3.layout.cloud === 'function'
? window.d3.layout.cloud
: null;
if (!cloudFactory) {
return;
}
const langPrefix = document.documentElement.getAttribute('data-language-prefix') || '';
const rawWords = container.getAttribute('data-tag-cloud-words');
const words = parseWords(rawWords);
if (words.length === 0) {
return;
}
const colorDistribution = container.getAttribute('data-color-distribution') || 'quantile';
const colorTheme = container.getAttribute('data-color-theme') || 'pico';
const coloredWords = colorDistribution === 'quantile' && colorTheme === 'pico'
? applyThemeAwareColors(words, container)
: words;
const width = Number.parseInt(container.getAttribute('data-width') || '900', 10) || 900;
const height = Number.parseInt(container.getAttribute('data-height') || '420', 10) || 420;
const orientation = container.getAttribute('data-orientation') || 'horizontal';
const resolveRotation = () => {
if (orientation === 'mixed-hv') {
return Math.random() < 0.5 ? 0 : 90;
}
if (orientation === 'mixed-diagonal') {
const diagonalAngles = [-60, -30, 0, 30, 60, 90];
const index = Math.floor(Math.random() * diagonalAngles.length);
return diagonalAngles[index];
}
return 0;
};
const svgNode = container.querySelector('svg.tag-cloud-canvas');
if (!svgNode) {
return;
}
while (svgNode.firstChild) {
svgNode.removeChild(svgNode.firstChild);
}
cloudFactory()
.size([width, height])
.words(coloredWords.map((word) => ({ ...word })))
.padding(4)
.rotate(() => resolveRotation())
.font('sans-serif')
.fontSize((word) => word.size)
.on('end', (layoutWords) => {
svgNode.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
svgNode.setAttribute('preserveAspectRatio', 'xMidYMid meet');
const group = document.createElementNS('http://www.w3.org/2000/svg', 'g');
group.setAttribute('transform', 'translate(' + (width / 2) + ',' + (height / 2) + ')');
for (const word of layoutWords) {
const textNode = document.createElementNS('http://www.w3.org/2000/svg', 'text');
textNode.textContent = word.text;
textNode.setAttribute('text-anchor', 'middle');
textNode.setAttribute('transform', 'translate(' + word.x + ',' + word.y + ')rotate(' + (word.rotate || 0) + ')');
textNode.style.fontFamily = 'sans-serif';
textNode.style.fontSize = word.size + 'px';
textNode.style.fill = typeof word.color === 'string' && word.color.length > 0
? word.color
: 'currentColor';
textNode.style.cursor = 'pointer';
textNode.style.opacity = '0.9';
const titleNode = document.createElementNS('http://www.w3.org/2000/svg', 'title');
titleNode.textContent = word.text + ' (' + word.count + ')';
textNode.appendChild(titleNode);
textNode.addEventListener('click', () => {
if (word && typeof word.url === 'string' && word.url.length > 0) {
window.location.assign(langPrefix + word.url);
}
});
group.appendChild(textNode);
}
svgNode.appendChild(group);
})
.start();
}
function initTagClouds() {
const containers = document.querySelectorAll('[data-tag-cloud="true"]');
containers.forEach((container) => drawTagCloud(container));
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTagClouds, { once: true });
} else {
initTagClouds();
}
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long