Files
weircon-front/templates/base.html
T
agw 94e5a0ca01
CD / deploy (push) Successful in 15s
CI / check (push) Successful in 23s
feat(podcast): fælles familielink uden login
Et valgfrit fælles link, /podcast/f/<nøgle>/, til fx familiens Messenger-
gruppe: samme afspillerside som den personlige, med abonnér-knapper og feed
under samme sti, men uden login og uden admin-ting. Kladder vises aldrig der.
I /admin/podcast laves linket, nøglen kan skiftes (det gamle dør med det
samme), det kan slås fra, og hentningerne tælles.

Siden er noindex (header og meta) og sender ingen referrer. Forkerte nøgler
giver 404 ligesom ukendte stier, og en klient der rammer 20 forkerte nøgler
på ti minutter, får 404 på alt under /podcast/<nøgle> i et stykke tid.
Det gælder også de personlige nøgler.

Claude-Session: https://claude.ai/code/session_012LCTmU2ewo8tnM1dByYAoG
2026-10-04 20:11:28 +02:00

120 lines
6.7 KiB
HTML

<!doctype html>
<html lang="da" data-theme="dark">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#0b0f17" media="(prefers-color-scheme: dark)" />
<meta name="theme-color" content="#f6f8fb" media="(prefers-color-scheme: light)" />
<link rel="icon" href="/static/icons/icon.svg" type="image/svg+xml" />
<title>{% block title %}Weircon{% endblock %}</title>
<!-- no-flash: set theme before any stylesheet paints (key 'theme', shared with the theme JS) -->
<script>(()=>{try{var t=localStorage.getItem('theme')||(matchMedia('(prefers-color-scheme: light)').matches?'light':'dark');document.documentElement.dataset.theme=t}catch(e){document.documentElement.dataset.theme='dark'}})()</script>
<!-- shared design system (SSOT: agw/Weircon-wind) hosted on assets.weircon.dk -->
<link rel="stylesheet" href="https://assets.weircon.dk/css/weirsoe-theme.css" />
<link rel="stylesheet" href="/static/portal.css" />
<script defer src="https://assets.weircon.dk/js/weirsoe-theme.js"></script>
{% block head %}{% endblock %}
</head>
<body>
<header class="wt-header">
<nav class="wt-nav">
<a class="wt-brand" href="/">weircon<span class="wt-brand-dot">.</span></a>
<div class="wt-nav-links">
<a class="wt-nav-link" href="/">Forside</a>
{% if is_admin %}<a class="wt-nav-link" href="/admin">Admin</a>{% endif %}
{% if user %}<span class="who">{{ user.name }}</span> <a class="wt-nav-link" href="/auth/logout">Log ud</a>{% endif %}
<button type="button" data-theme-toggle class="theme-toggle" title="Skift mellem lyst og mørkt tema" aria-label="Skift tema">
<svg class="sun-and-moon" aria-hidden="true" width="22" height="22" viewBox="0 0 24 24">
<mask class="moon" id="moon-mask"><rect x="0" y="0" width="100%" height="100%" fill="white" /><circle cx="24" cy="10" r="6" fill="black" /></mask>
<circle class="sun" cx="12" cy="12" r="6" mask="url(#moon-mask)" fill="currentColor" />
<g class="sun-beams" stroke="currentColor">
<line x1="12" y1="1" x2="12" y2="3" /><line x1="12" y1="21" x2="12" y2="23" />
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64" /><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" />
<line x1="1" y1="12" x2="3" y2="12" /><line x1="21" y1="12" x2="23" y2="12" />
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36" /><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" />
</g>
</svg>
</button>
</div>
</nav>
</header>
<main class="wt-main">
{% block body %}{% endblock %}
</main>
<footer class="wt-footer">
<div class="wt-footer-inner">
Driftet af Asger Weirsøe · self-hosted på Proxmox · <a href="mailto:asger@weirsoe.dk">asger@weirsoe.dk</a>
</div>
</footer>
<!-- ⌘K / Ctrl+K kommandopalette -->
<div id="palette" class="wt-palette" hidden>
<div class="wt-palette-box" role="dialog" aria-modal="true" aria-label="Kommandopalette">
<input id="palette-input" class="wt-palette-input" type="text" placeholder="Søg tjenester, sider eller kommandoer…" autocomplete="off" spellcheck="false" />
<ul id="palette-list" class="wt-palette-list" role="listbox"></ul>
<div class="wt-palette-hint">↑↓ navigér · ↵ vælg · esc luk · ⌘/Ctrl K åbn</div>
</div>
</div>
<script>
(function () {
if (window.__palette) return; window.__palette = true;
var index = null, items = [], sel = 0;
var staticActions = [
{ type:'action', title:'Tema: mørkt', run:function(){ setTheme('dark'); } },
{ type:'action', title:'Tema: lyst', run:function(){ setTheme('light'); } },
{ type:'action', title:'Log ud', url:'/auth/logout' },
];
function setTheme(t){ document.documentElement.dataset.theme = t; localStorage.setItem('theme', t); }
function el(id){ return document.getElementById(id); }
function ensureIndex(){
if (index) return Promise.resolve();
return fetch('/api/search-index').then(function(r){ return r.json(); })
.then(function(j){ index = j; }).catch(function(){ index = []; });
}
function score(q, it){
var hay = (it.title + ' ' + (it.extra || '')).toLowerCase();
if (!q) return (it.type === 'page' || it.type === 'action') ? 1 : 0.5;
if (hay.indexOf(q) === 0) return 3;
if (hay.indexOf(q) > -1) return 2;
var i = 0; for (var k = 0; k < hay.length; k++) if (hay[k] === q[i]) i++;
return i === q.length ? 1 : 0;
}
function refresh(){
var q = el('palette-input').value.trim().toLowerCase();
items = (index || []).concat(staticActions)
.map(function(it){ return { it: it, s: score(q, it) }; })
.filter(function(x){ return x.s > 0; })
.sort(function(a, b){ return b.s - a.s; })
.slice(0, 9).map(function(x){ return x.it; });
sel = 0;
el('palette-list').innerHTML = items.map(function(it, i){
var badge = it.type === 'app' ? 'app' : it.type === 'action' ? 'kommando' : it.ext ? 'link' : 'side';
return '<li class="wt-palette-item' + (i === sel ? ' is-selected' : '') + '" data-i="' + i + '" role="option">' +
'<span>' + String(it.title).replace(/</g, '&lt;') + '</span><span class="wt-palette-badge">' + badge + '</span></li>';
}).join('') || '<li class="wt-palette-item wt-palette-empty">Ingen resultater</li>';
}
function paint(){ var ls = el('palette-list').querySelectorAll('.wt-palette-item');
for (var i = 0; i < ls.length; i++) ls[i].classList.toggle('is-selected', i === sel); }
function choose(i){ var it = items[i]; if (!it) return; close();
if (it.run) it.run(); else if (it.ext) window.open(it.url, '_blank', 'noopener'); else window.location.href = it.url; }
function open(){ ensureIndex().then(function(){ el('palette').hidden = false; el('palette-input').value = ''; refresh(); el('palette-input').focus(); }); }
function close(){ var p = el('palette'); if (p) p.hidden = true; }
document.addEventListener('keydown', function(e){
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'k'){ e.preventDefault(); el('palette').hidden ? open() : close(); return; }
var p = el('palette'); if (!p || p.hidden) return;
if (e.key === 'Escape') close();
else if (e.key === 'ArrowDown'){ e.preventDefault(); sel = Math.min(sel + 1, items.length - 1); paint(); }
else if (e.key === 'ArrowUp'){ e.preventDefault(); sel = Math.max(sel - 1, 0); paint(); }
else if (e.key === 'Enter'){ e.preventDefault(); choose(sel); }
});
document.addEventListener('input', function(e){ if (e.target && e.target.id === 'palette-input') refresh(); });
document.addEventListener('click', function(e){
if (e.target === el('palette')) close();
var li = e.target.closest && e.target.closest('.wt-palette-item[data-i]');
if (li) choose(+li.dataset.i);
});
})();
</script>
</body>
</html>