Descriptions of unreliable narrator added

This commit is contained in:
hbrain 2026-06-21 12:45:17 +02:00
parent 61b4cad61d
commit 0830dcf6e4
6 changed files with 79 additions and 5 deletions

View file

@ -95,7 +95,7 @@ http_response_code(404);
<meta name="robots" content="noindex,nofollow">
<link rel="icon" type="image/png" href="assets/img/moto_travel.png">
<link rel="stylesheet" href="style.css?v=20260531u">
<link rel="stylesheet" href="style.css?v=20260621a">
<style>
.not-found-card{
max-width:680px;

View file

@ -2,4 +2,59 @@
// Shared header for public pages (index.php, contact.php, etc.)
$brandUrl = $brandUrl ?? '/';
if ($brandUrl === '') $brandUrl = '/';
?><header class="site-header admin-header"><div class="brand-wrap"><a class="header-logo" href="<?=h($brandUrl)?>" aria-label="MVLog home"><img src="/assets/img/moto_travel.png" alt=""></a><a class="brand" href="<?=h($brandUrl)?>"><h1><?=h($siteHeaderTitle)?></h1><p>journal of an<br>unreliable narrator.</p></a></div><div class="header-actions"><button id="push-toggle" class="push-toggle" type="button" hidden aria-label="Notifications" title="Notifications"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22a2.7 2.7 0 0 0 2.6-2h-5.2A2.7 2.7 0 0 0 12 22Zm7-6V11a7 7 0 0 0-5-6.7V3a2 2 0 0 0-4 0v1.3A7 7 0 0 0 5 11v5l-2 2v1h18v-1l-2-2Z"/></svg></button><a class="rss-link" href="/feed.php" aria-label="RSS feed" title="RSS feed"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 17.8a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM2.2 8.7v3.1a10 10 0 0 1 10 10h3.1A13.1 13.1 0 0 0 2.2 8.7Zm0-5.5v3.1a15.5 15.5 0 0 1 15.5 15.5h3.1A18.6 18.6 0 0 0 2.2 3.2Z"/></svg></a><a class="contact-link" href="/contact.php" aria-label="Contact" title="Contact"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z"/></svg></a></div></header>
?><header class="site-header admin-header"><div class="brand-wrap"><a class="header-logo" href="<?=h($brandUrl)?>" aria-label="MVLog home"><img src="/assets/img/moto_travel.png" alt=""></a><a class="brand" href="<?=h($brandUrl)?>"><h1><?=h($siteHeaderTitle)?></h1><p class="narrator-trigger" tabindex="0" role="button" aria-haspopup="dialog" aria-label="Show unreliable narrator description">journal of an<br>unreliable narrator.</p></a><div class="narrator-balloon" role="dialog" aria-live="polite" hidden></div></div><div class="header-actions"><button id="push-toggle" class="push-toggle" type="button" hidden aria-label="Notifications" title="Notifications"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 22a2.7 2.7 0 0 0 2.6-2h-5.2A2.7 2.7 0 0 0 12 22Zm7-6V11a7 7 0 0 0-5-6.7V3a2 2 0 0 0-4 0v1.3A7 7 0 0 0 5 11v5l-2 2v1h18v-1l-2-2Z"/></svg></button><a class="rss-link" href="/feed.php" aria-label="RSS feed" title="RSS feed"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M6.2 17.8a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM2.2 8.7v3.1a10 10 0 0 1 10 10h3.1A13.1 13.1 0 0 0 2.2 8.7Zm0-5.5v3.1a15.5 15.5 0 0 1 15.5 15.5h3.1A18.6 18.6 0 0 0 2.2 3.2Z"/></svg></a><a class="contact-link" href="/contact.php" aria-label="Contact" title="Contact"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M22 6c0-1.1-.9-2-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6zm-2 0-8 5-8-5h16zm0 12H4V8l8 5 8-5v10z"/></svg></a></div></header>
<script>
(function(){
const trigger = document.querySelector('.narrator-trigger');
const balloon = document.querySelector('.narrator-balloon');
if (!trigger || !balloon) return;
let descriptions = null;
let hideTimer = null;
const fallback = [
"Motorcycle traveler with a camera, a sense of humor, and a complicated relationship with GPS instructions."
];
async function loadDescriptions(){
if (descriptions) return descriptions;
try {
const res = await fetch('/unreliable_narrator_descriptions.json', {cache: 'force-cache'});
const data = await res.json();
descriptions = Array.isArray(data) && data.length ? data.filter(v => typeof v === 'string' && v.trim()) : fallback;
} catch(e) {
descriptions = fallback;
}
return descriptions;
}
function pick(list){ return list[Math.floor(Math.random() * list.length)] || fallback[0]; }
async function showBalloon(ev){
if (ev) { ev.preventDefault(); ev.stopPropagation(); }
clearTimeout(hideTimer);
const list = await loadDescriptions();
balloon.textContent = pick(list);
balloon.hidden = false;
balloon.classList.add('is-open');
}
function hideBalloonSoon(){
clearTimeout(hideTimer);
hideTimer = setTimeout(() => {
balloon.classList.remove('is-open');
balloon.hidden = true;
}, 180);
}
trigger.addEventListener('mouseenter', showBalloon);
trigger.addEventListener('focus', showBalloon);
trigger.addEventListener('mouseleave', hideBalloonSoon);
trigger.addEventListener('blur', hideBalloonSoon);
trigger.addEventListener('click', function(ev){
if (!balloon.hidden && balloon.classList.contains('is-open')) {
ev.preventDefault(); ev.stopPropagation(); hideBalloonSoon(); return;
}
showBalloon(ev);
});
balloon.addEventListener('mouseenter', () => clearTimeout(hideTimer));
balloon.addEventListener('mouseleave', hideBalloonSoon);
document.addEventListener('click', function(ev){
if (!balloon.hidden && !balloon.contains(ev.target) && !trigger.contains(ev.target)) hideBalloonSoon();
});
})();
</script>

View file

@ -1539,7 +1539,7 @@ $editDir = null; $editStatus = ''; $editRunning = false; $editData = ['title'=>'
if ($editName !== '' || $editId !== '') { try { $editDir = resolve_input_dir_from_request($config['uploads_dir'], $editId, $editName, $articleIndex); $editStatus = input_dir_status($editDir); $editRunning = input_dir_running($editDir); if (!$editRunning) { $editData = read_data($editDir); $editFiles = media_sort_files($editDir, list_files($editDir)); } } catch (Throwable $e) { $err = $e->getMessage(); } }
$runningJobs = active_worker_jobs($config);
?>
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVLog Admin</title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css?v=20260531u"><style>.admin-search-form{display:flex;flex-wrap:nowrap;align-items:center;gap:.55rem;margin:0 0 .6rem;overflow-x:auto;padding-bottom:2px}.admin-search-wrap{position:relative;min-width:180px;flex:1 1 auto}.admin-search-wrap input{margin:0;padding-right:2.15rem}.admin-search-clear{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:50%;background:#C46A3A;color:#111315;text-decoration:none;font-size:1rem;line-height:1;font-weight:700;box-shadow:0 1px 4px #0007;transition:opacity .15s ease,transform .15s ease}.admin-search-clear:hover{background:#d97b48;color:#111315;transform:translateY(-50%) scale(1.05)}.admin-search-clear.is-empty{opacity:.38;pointer-events:none}.admin-search-submit{display:inline-grid;place-items:center;width:2.35rem;height:2.35rem;padding:0;border-radius:.5rem;background:#C46A3A;color:#111315;flex:0 0 auto}.admin-search-submit .icon{font-size:1.02rem;line-height:1;transform:translateY(.01em)}.admin-search-filters{display:flex;flex-wrap:nowrap;gap:.7rem;align-items:center;flex:0 0 auto;white-space:nowrap}.admin-filter-item{display:inline-flex;align-items:center;gap:.35rem;margin:0;white-space:nowrap;font-size:.92rem}.admin-filter-item input[type=checkbox]{width:.9rem;height:.9rem;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.description-teaser{margin:.2rem 0 .45rem;color:#eadfca}.description-quote{margin:.2rem 0 .45rem;font-style:italic;font-size:.82rem;color:#d4d7dd}.meta a{color:inherit;text-decoration:none}.meta a:hover{text-decoration:underline}.meta-filter{display:inline-flex;align-items:center;gap:.25rem;padding:.12rem .5rem;border-radius:999px;background:rgba(255,255,255,.06);cursor:pointer;user-select:none;transition:background-color .15s ease,transform .15s ease}.meta-filter:hover,.meta-filter:focus-visible{background:rgba(255,255,255,.12);transform:translateY(-1px);outline:none}.meta-filter:focus-visible{box-shadow:0 0 0 2px rgba(202,162,109,.35)}</style></head><body>
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>MVLog Admin</title><link rel="icon" type="image/png" href="assets/img/moto_travel.png"><link rel="stylesheet" href="style.css?v=20260621a"><style>.admin-search-form{display:flex;flex-wrap:nowrap;align-items:center;gap:.55rem;margin:0 0 .6rem;overflow-x:auto;padding-bottom:2px}.admin-search-wrap{position:relative;min-width:180px;flex:1 1 auto}.admin-search-wrap input{margin:0;padding-right:2.15rem}.admin-search-clear{position:absolute;right:.45rem;top:50%;transform:translateY(-50%);display:inline-flex;align-items:center;justify-content:center;width:1.5rem;height:1.5rem;border-radius:50%;background:#C46A3A;color:#111315;text-decoration:none;font-size:1rem;line-height:1;font-weight:700;box-shadow:0 1px 4px #0007;transition:opacity .15s ease,transform .15s ease}.admin-search-clear:hover{background:#d97b48;color:#111315;transform:translateY(-50%) scale(1.05)}.admin-search-clear.is-empty{opacity:.38;pointer-events:none}.admin-search-submit{display:inline-grid;place-items:center;width:2.35rem;height:2.35rem;padding:0;border-radius:.5rem;background:#C46A3A;color:#111315;flex:0 0 auto}.admin-search-submit .icon{font-size:1.02rem;line-height:1;transform:translateY(.01em)}.admin-search-filters{display:flex;flex-wrap:nowrap;gap:.7rem;align-items:center;flex:0 0 auto;white-space:nowrap}.admin-filter-item{display:inline-flex;align-items:center;gap:.35rem;margin:0;white-space:nowrap;font-size:.92rem}.admin-filter-item input[type=checkbox]{width:.9rem;height:.9rem;margin:0}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}.description-teaser{margin:.2rem 0 .45rem;color:#eadfca}.description-quote{margin:.2rem 0 .45rem;font-style:italic;font-size:.82rem;color:#d4d7dd}.meta a{color:inherit;text-decoration:none}.meta a:hover{text-decoration:underline}.meta-filter{display:inline-flex;align-items:center;gap:.25rem;padding:.12rem .5rem;border-radius:999px;background:rgba(255,255,255,.06);cursor:pointer;user-select:none;transition:background-color .15s ease,transform .15s ease}.meta-filter:hover,.meta-filter:focus-visible{background:rgba(255,255,255,.12);transform:translateY(-1px);outline:none}.meta-filter:focus-visible{box-shadow:0 0 0 2px rgba(202,162,109,.35)}</style></head><body>
<header class="site-header admin-header"><div class="brand-wrap"><a class="header-logo" href="/" aria-label="MVLog home" target="_blank"><img src="assets/img/moto_travel.png" alt=""></a><a class="brand" href="index.php"><h1 style="font-size:1.15rem">MVLog <span class="admin-word">Admin</span></h1><p>journal of an<br>unreliable narrator.</p></a></div></header>
<main>
<?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>

View file

@ -275,7 +275,7 @@ function render_blank_page(){
<?php include __DIR__ . '/_pwa_head.php'; ?>
<link rel="icon" type="image/png" href="/assets/img/moto_travel.png">
<link rel="stylesheet" href="/style.css?v=20260531u">
<link rel="stylesheet" href="/style.css?v=20260621a">
<style>html,body{height:100%;overflow:hidden}body{height:100dvh}main{flex:1;min-height:0}</style>
</head>
<body>
@ -547,7 +547,7 @@ $siteHeaderTitle = $headerTitles[array_rand($headerTitles)];
<link rel="alternate" type="application/rss+xml" title="MVLog RSS" href="/feed.php">
<link rel="icon" type="image/png" href="/assets/img/moto_travel.png">
<link rel="stylesheet" href="/style.css?v=20260531u">
<link rel="stylesheet" href="/style.css?v=20260621a">
<style>
.filter-form{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:.55rem;align-items:center;margin:0 0 .4rem}
.filter-input-wrap{position:relative;min-width:0}

View file

@ -216,3 +216,7 @@ body.map-lightbox-open { overflow: hidden; }
.site-header .header-actions .rss-link,
.site-header .header-actions .contact-link{position:static!important}
.site-header .header-actions svg{width:.75rem!important;height:.75rem!important;max-width:.75rem!important;max-height:.75rem!important;display:block}
/* Unreliable narrator header balloon */
.narrator-trigger{cursor:help;outline:none}.narrator-trigger:focus-visible{color:#F3F4F6;text-shadow:0 0 .35rem rgba(59,167,160,.55)}.narrator-balloon{position:absolute;left:50%;top:calc(100% + .85rem);transform:translateX(-50%) translateY(-.25rem);width:min(28rem,calc(100vw - 2rem));box-sizing:border-box;padding:.85rem 1rem;border:1px solid rgba(59,167,160,.55);border-radius:.85rem;background:rgba(17,19,21,.96);box-shadow:0 1rem 2.4rem rgba(0,0,0,.55);color:#F3F4F6;font-family:IBMPlexCaption,system-ui,sans-serif;font-size:.92rem;line-height:1.45;letter-spacing:normal;text-transform:none;text-align:left;z-index:1200;opacity:0;pointer-events:none;transition:opacity .16s ease,transform .16s ease}.narrator-balloon::before{content:"";position:absolute;left:50%;top:-.48rem;width:.85rem;height:.85rem;transform:translateX(-50%) rotate(45deg);background:rgba(17,19,21,.96);border-left:1px solid rgba(59,167,160,.55);border-top:1px solid rgba(59,167,160,.55)}.narrator-balloon.is-open{opacity:1;pointer-events:auto;transform:translateX(-50%) translateY(0)}
@media (max-width:640px){.narrator-balloon{top:calc(100% + .65rem);font-size:.86rem;width:min(23rem,calc(100vw - 1.2rem));}}

View file

@ -0,0 +1,15 @@
[
"Croatian by origin, Danish by address, and perpetually somewhere between a plan and a detour. Most journeys start with a destination in mind and end with a story that wasn't part of the itinerary.",
"Motorcycle traveler with a talent for finding interesting roads and questionable shortcuts. My memory tends to improve stories over time, which is why this is an unreliable journal rather than an official historical record.",
"Moved to Denmark, learned that weather is mostly a state of mind, and started exploring Europe on two wheels. Somewhere along the way discovered that facts are important, but a good story is often more memorable.",
"Software engineer by profession, motorcycle traveler by preference, and accidental storyteller whenever things fail to go according to plan.",
"The unreliable narrator is someone who genuinely intends to tell the truth but occasionally remembers events the way they should have happened rather than the way they actually did.",
"Motorcycle traveler, hockey lover, software engineer, and enthusiastic amateur at most other things. I believe every bad decision deserves at least one good story.",
"The unreliable narrator is someone who starts with the facts, improves them slightly for dramatic effect, and eventually becomes convinced that's how it happened all along.",
"A believer in motorcycles, loud music, spontaneous decisions, and the theory that getting lost is just sightseeing without a schedule.",
"Hockey fan, motorcycle traveler, and collector of roads that looked like excellent ideas on the map.",
"Born in Germany, raised in Croatia, living in Denmark, and still not entirely sure where I'm going. Fortunately, that's rarely stopped me.",
"Equal parts curiosity, stubbornness, and fuel. The exact proportions vary by country.",
"Motorcycle traveler with a camera, a sense of humor, and a complicated relationship with GPS instructions.",
"Liking long roads, loud guitars, hockey, pizza, and adventures that sound slightly irresponsible when explained afterward."
]