design is now terminal alike

This commit is contained in:
Priec
2026-05-19 14:48:47 +02:00
parent 67b7c8e5ae
commit cbd642c62c
13 changed files with 801 additions and 755 deletions

View File

@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -23,19 +23,23 @@
applyTheme(t);
highlightTheme(t);
}
applyTheme(localStorage.getItem('theme') || 'system');
applyTheme(localStorage.getItem('theme') || 'dark');
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
if ((localStorage.getItem('theme') || 'system') === 'system') applyTheme('system');
if ((localStorage.getItem('theme') || 'dark') === 'system') applyTheme('system');
});
document.addEventListener('DOMContentLoaded', function () {
highlightTheme(localStorage.getItem('theme') || 'system');
highlightTheme(localStorage.getItem('theme') || 'dark');
var path = location.pathname;
document.querySelectorAll('.term-navlinks a[data-nav]').forEach(function (a) {
var h = a.getAttribute('data-nav');
if (h === path || (h !== '/' && path.indexOf(h) === 0)) a.classList.add('is-active');
});
});
</script>
<link href="/static/css/app.css" rel="stylesheet" type="text/css">
<link href="/static/css/theme.css" rel="stylesheet" type="text/css">
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
.btn { --animation-btn: 0; --btn-focus-scale: 1; }
@media (min-width: 768px) {
.nav-menu { flex-direction: row; }
}
@@ -46,12 +50,12 @@
position: fixed;
inset: 0;
z-index: 40;
background-color: rgba(0, 0, 0, 0.25);
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease, visibility 0s linear 0.2s;
}
.navbar:has(.dropdown:focus-within) ~ #nav-backdrop {
.term-titlebar:has(.dropdown:focus-within) ~ #nav-backdrop {
opacity: 1;
visibility: visible;
transition: opacity 0.15s ease, visibility 0s;
@@ -59,24 +63,29 @@
}
</style>
</head>
<body class="min-h-screen bg-base-200 font-sans text-base-content antialiased">
<header class="navbar bg-base-100 shadow-sm">
<nav class="mx-auto flex w-full max-w-6xl items-center justify-between gap-2 px-4">
<a href="/admin/dashboard" class="min-w-0 truncate text-lg font-bold">Admin</a>
<ul class="nav-menu menu menu-sm hidden items-center gap-1 md:flex">
<li><a href="/admin/dashboard">Dashboard</a></li>
<li><a href="/admin/blog/articles">Blog</a></li>
<li><a href="/admin/audio/albums">Audio</a></li>
<li><a href="/admin/images">Images</a></li>
<li><a href="/admin/about">About</a></li>
<li><a href="/">View site</a></li>
<body class="flex min-h-screen flex-col bg-base-100 text-base-content antialiased">
<header class="term-titlebar">
<nav class="term-nav">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<a href="/admin/dashboard" class="term-brand">
<span class="t-red">root</span><span class="t-dim">@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
</a>
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
<li><a href="/admin/dashboard" data-nav="/admin/dashboard">dashboard</a></li>
<li><a href="/admin/blog/articles" data-nav="/admin/blog">blog</a></li>
<li><a href="/admin/audio/albums" data-nav="/admin/audio">audio</a></li>
<li><a href="/admin/images" data-nav="/admin/images">images</a></li>
<li><a href="/admin/about" data-nav="/admin/about">about</a></li>
<li><a href="/" class="t-blue">exit</a></li>
<li>
<form method="post" action="/admin/logout">
<button type="submit" class="w-full">Logout</button>
<button type="submit" class="t-red w-full">logout</button>
</form>
</li>
</ul>
<div class="flex items-center gap-1">
<div class="term-nav-right">
<div class="dropdown dropdown-end md:hidden">
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Menu">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
@@ -85,16 +94,16 @@
</svg>
</div>
<ul tabindex="0"
class="menu dropdown-content z-50 mt-3 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
<li><a href="/admin/dashboard">Dashboard</a></li>
<li><a href="/admin/blog/articles">Blog</a></li>
<li><a href="/admin/audio/albums">Audio</a></li>
<li><a href="/admin/images">Images</a></li>
<li><a href="/admin/about">About</a></li>
<li><a href="/">View site</a></li>
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
<li><a href="/admin/dashboard">dashboard</a></li>
<li><a href="/admin/blog/articles">blog</a></li>
<li><a href="/admin/audio/albums">audio</a></li>
<li><a href="/admin/images">images</a></li>
<li><a href="/admin/about">about</a></li>
<li><a href="/" class="t-blue">exit</a></li>
<li>
<form method="post" action="/admin/logout">
<button type="submit" class="w-full">Logout</button>
<button type="submit" class="t-red w-full">logout</button>
</form>
</li>
</ul>
@@ -108,19 +117,28 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
<li class="menu-title">Theme</li>
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">System <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">Light <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">Dark <span class="opt-check ml-auto hidden"></span></button></li>
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
<li class="menu-title">:set theme</li>
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">system <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">light <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">dark <span class="opt-check ml-auto hidden"></span></button></li>
</ul>
</div>
</div>
</nav>
</header>
<div id="nav-backdrop" aria-hidden="true"></div>
<main class="mx-auto max-w-6xl px-4 py-6">
<main class="term-main">
{% block content %}{% endblock content %}
</main>
<footer class="term-statusline">
<span class="term-seg is-mode"> ADMIN </span>
<span class="term-seg">universal-web</span>
<span class="term-seg is-fill">/admin/{% block crumb %}{% endblock crumb %}</span>
<span class="term-seg">utf-8</span>
<span class="term-seg">root</span>
<span class="term-seg is-alt">gruvbox-dark</span>
<span class="term-seg is-mode">100%</span>
</footer>
</body>
</html>

View File

@@ -1,69 +1,95 @@
{% extends "admin/base.html" %}
{% block title %}Admin{% endblock title %}
{% block crumb %}dashboard{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">Dashboard</h1>
<p class="text-sm opacity-70">Logged in as {{ admin.email }}</p>
</div>
<a href="/" class="btn btn-ghost btn-sm">View site</a>
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-red">root@universal-web</span><span class="t-dim">:</span><span class="t-yellow">/admin</span><span class="t-dim">#</span>
ls -la
</p>
<h1 class="term-title">dashboard</h1>
<p class="term-sub">// session: {{ admin.email }}</p>
</div>
<div class="grid grid-cols-2 gap-4 pt-4">
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex items-center justify-between gap-2">
<h2 class="card-title text-base">Blog</h2>
<span class="badge">Content</span>
</div>
<p class="text-sm opacity-70">Create and update blog articles.</p>
<div class="pt-2">
<a href="/admin/blog/articles" class="btn btn-neutral btn-sm">Manage blog</a>
</div>
</div>
</div>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex items-center justify-between gap-2">
<h2 class="card-title text-base">About page</h2>
<span class="badge">Page</span>
</div>
<p class="text-sm opacity-70">Edit the public about page content.</p>
<div class="pt-2">
<a href="/admin/about" class="btn btn-neutral btn-sm">Edit about</a>
</div>
</div>
</div>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex items-center justify-between gap-2">
<h2 class="card-title text-base">Audio</h2>
<span class="badge">Media</span>
</div>
<p class="text-sm opacity-70">Create albums and upload tracks.</p>
<div class="pt-2">
<a href="/admin/audio/albums" class="btn btn-neutral btn-sm">Manage audio</a>
</div>
</div>
</div>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex items-center justify-between gap-2">
<h2 class="card-title text-base">Images</h2>
<span class="badge">Uploads</span>
</div>
<p class="text-sm opacity-70">Upload images for covers and articles.</p>
<div class="pt-2">
<a href="/admin/images" class="btn btn-neutral btn-sm">Upload image</a>
</div>
</div>
</div>
<div class="term-cmd-actions">
<a href="/" class="btn btn-outline btn-sm">[ view site ]</a>
</div>
</header>
<div class="term-screen mb-6">
<p class="line" data-p="root@universal-web:/admin#">ls</p>
<p class="line out">about/ audio/ blog/ images/</p>
</div>
<div class="term-grid">
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">/admin/blog</span>
<span class="term-head-meta term-tag">content</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">blog</h2>
<p class="text-sm opacity-70">create and update blog articles.</p>
<div class="pt-2">
<a href="/admin/blog/articles" class="btn btn-primary btn-sm">[ manage → ]</a>
</div>
</div>
</article>
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">/admin/about</span>
<span class="term-head-meta term-tag is-blue">page</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">about page</h2>
<p class="text-sm opacity-70">edit the public about page content.</p>
<div class="pt-2">
<a href="/admin/about" class="btn btn-primary btn-sm">[ edit → ]</a>
</div>
</div>
</article>
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">/admin/audio</span>
<span class="term-head-meta term-tag is-purple">media</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">audio</h2>
<p class="text-sm opacity-70">create albums and upload tracks.</p>
<div class="pt-2">
<a href="/admin/audio/albums" class="btn btn-primary btn-sm">[ manage → ]</a>
</div>
</div>
</article>
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">/admin/images</span>
<span class="term-head-meta term-tag is-green">uploads</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">images</h2>
<p class="text-sm opacity-70">upload images for covers and articles.</p>
<div class="pt-2">
<a href="/admin/images" class="btn btn-primary btn-sm">[ upload → ]</a>
</div>
</div>
</article>
</div>
{% endblock content %}

View File

@@ -1,27 +1,38 @@
{% extends "base.html" %}
{% block title %}Admin login{% endblock title %}
{% block crumb %}admin/login{% endblock crumb %}
{% block content %}
<div class="mx-auto mt-8 max-w-sm">
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">/bin/login — tty1</span>
<span class="term-head-meta term-tag is-red">auth</span>
</div>
<div class="card-body">
<h1 class="card-title">Admin login</h1>
<p class="term-cmd-line">
<span class="t-dim">universal-web login:</span> <span class="t-red">root</span>
</p>
<h1 class="term-title">authenticate</h1>
{% if error %}
<div class="alert alert-error">
<span>Invalid email or password.</span>
<div class="alert alert-error mt-2">
<span>✗ access denied — invalid email or password.</span>
</div>
{% endif %}
<form method="post" action="/admin/login" class="space-y-2">
<div class="form-control">
<label class="label"><span class="label-text">Email</span></label>
<input type="email" name="email" required class="input input-bordered w-full">
<label class="label"><span class="label-text t-green">email:</span></label>
<input type="email" name="email" required autofocus class="input input-bordered w-full">
</div>
<div class="form-control">
<label class="label"><span class="label-text">Password</span></label>
<label class="label"><span class="label-text t-green">password:</span></label>
<input type="password" name="password" required class="input input-bordered w-full">
</div>
<button class="btn btn-neutral mt-2 w-full">Login</button>
<button class="btn btn-primary mt-2 w-full">[ authenticate ]</button>
</form>
</div>
</div>

View File

@@ -1,48 +1,77 @@
{% extends "base.html" %}
{% block title %}{{ album.title }}{% endblock title %}
{% block crumb %}audio/{{ album.slug }}{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">{{ album.title }}</h1>
{% if album.artist %}
<p class="text-sm opacity-70">{{ album.artist }}</p>
{% endif %}
</div>
<a href="/audio/albums" class="btn btn-ghost btn-sm">Back to albums</a>
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
cd {{ album.slug }}/ && ls
</p>
<h1 class="term-title">{{ album.title }}</h1>
{% if album.artist %}
<p class="term-sub">// by {{ album.artist }}</p>
{% endif %}
</div>
{% if album.cover_image_id %}
<img src="/images/{{ album.cover_image_id }}" alt="" class="mb-4 rounded">
{% endif %}
{% if album.description %}
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<p class="whitespace-pre-line">{{ album.description }}</p>
</div>
<div class="term-cmd-actions">
<a href="/audio/albums" class="btn btn-outline btn-sm">[ cd .. ]</a>
</div>
{% endif %}
</header>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
{% if tracks | length > 0 %}
<div class="space-y-2">
{% for track in tracks %}
<div class="border-t border-base-300 pt-2">
<p class="font-medium">{% if track.track_number %}{{ track.track_number }}. {% endif %}{{ track.title }}</p>
<audio controls preload="metadata" class="mt-2 w-full">
<source src="/audio/tracks/{{ track.id }}/stream">
</audio>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-center font-medium">No tracks yet.</p>
{% endif %}
</div>
{% if album.cover_image_id %}
<div class="card mb-6">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/audio/{{ album.slug }}/cover.png</span>
</div>
<div class="card-body">
<img src="/images/{{ album.cover_image_id }}" alt="">
</div>
</div>
{% endif %}
{% if album.description %}
<div class="card mb-6">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/audio/{{ album.slug }}/notes.txt</span>
</div>
<div class="card-body">
<p class="term-prose whitespace-pre-line">{{ album.description }}</p>
</div>
</div>
{% endif %}
<div class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/audio/{{ album.slug }}/tracklist</span>
<span class="term-head-meta term-tag is-green">{{ tracks | length }} tracks</span>
</div>
<div class="card-body">
{% if tracks | length > 0 %}
{% for track in tracks %}
<div class="term-track">
<p class="term-track-name">
<span class="t-dim">{% if track.track_number %}{{ track.track_number }}{% else %}-{% endif %}</span>
<span class="t-green"></span> {{ track.title }}
</p>
<audio controls preload="metadata">
<source src="/audio/tracks/{{ track.id }}/stream">
</audio>
</div>
{% endfor %}
{% else %}
<p class="term-empty-cmd">$ ls → no tracks yet</p>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -1,42 +1,56 @@
{% extends "base.html" %}
{% block title %}Audio{% endblock title %}
{% block crumb %}audio{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<header class="term-cmd">
<div>
<h1 class="text-2xl font-bold">Audio</h1>
<p class="text-sm opacity-70">Published albums.</p>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
ls -d */
</p>
<h1 class="term-title">audio</h1>
<p class="term-sub">// {{ albums | length }} published album(s).</p>
</div>
<div class="term-cmd-actions">
<a href="/audio/tracks" class="btn btn-outline btn-sm">[ all songs ]</a>
</div>
</header>
{% if albums | length > 0 %}
<div class="grid grid-cols-2 gap-4 pt-4">
{% for album in albums %}
<article class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
{% if album.cover_image_id %}
<img src="/images/{{ album.cover_image_id }}" alt="" class="mb-3 rounded">
{% endif %}
<h2 class="card-title text-base">{{ album.title }}</h2>
{% if album.artist %}
<p class="text-sm opacity-70">{{ album.artist }}</p>
{% endif %}
{% if album.description %}
<p class="text-sm opacity-80">{{ album.description }}</p>
{% endif %}
<div class="pt-2">
<a href="/audio/albums/{{ album.slug }}" class="btn btn-neutral btn-sm">Open album</a>
</div>
{% if albums | length > 0 %}
<div class="term-grid">
{% for album in albums %}
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/audio/{{ album.slug }}/</span>
<span class="term-head-meta term-tag is-purple">album</span>
</div>
<div class="card-body">
{% if album.cover_image_id %}
<img src="/images/{{ album.cover_image_id }}" alt="" class="mb-3">
{% endif %}
<h2 class="card-title text-base">{{ album.title }}</h2>
{% if album.artist %}
<p class="text-sm t-aqua">{{ album.artist }}</p>
{% endif %}
{% if album.description %}
<p class="term-prose text-sm opacity-80">{{ album.description }}</p>
{% endif %}
<div class="pt-2">
<a href="/audio/albums/{{ album.slug }}" class="btn btn-primary btn-sm">[ open → ]</a>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body text-center">
<p class="font-medium">No published albums yet.</p>
</div>
</div>
{% endif %}
</div>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="term-empty">
<p class="font-medium">no published albums yet</p>
<p class="term-empty-cmd">$ ls ~/audio → 0 results</p>
</div>
{% endif %}
{% endblock content %}

View File

@@ -1,34 +1,44 @@
{% extends "base.html" %}
{% block title %}Songs{% endblock title %}
{% block crumb %}audio/tracks{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">Songs</h1>
<p class="text-sm opacity-70">Published songs from every album and ungrouped uploads.</p>
</div>
<a href="/audio/albums" class="btn btn-ghost btn-sm">Albums</a>
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/audio</span><span class="t-dim">$</span>
find . -name '*.mp3'
</p>
<h1 class="term-title">songs</h1>
<p class="term-sub">// {{ tracks | length }} track(s) across every album.</p>
</div>
<div class="term-cmd-actions">
<a href="/audio/albums" class="btn btn-outline btn-sm">[ albums ]</a>
</div>
</header>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
{% if tracks | length > 0 %}
<div class="space-y-2">
{% for track in tracks %}
<div class="border-t border-base-300 pt-2">
<p class="font-medium">{{ track.title }}</p>
<audio controls preload="metadata" class="mt-2 w-full">
<source src="/audio/tracks/{{ track.id }}/stream">
</audio>
</div>
{% endfor %}
</div>
{% else %}
<p class="text-center font-medium">No published songs yet.</p>
{% endif %}
</div>
<div class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/audio/playlist.m3u</span>
<span class="term-head-meta term-tag is-green">{{ tracks | length }} tracks</span>
</div>
<div class="card-body">
{% if tracks | length > 0 %}
{% for track in tracks %}
<div class="term-track">
<p class="term-track-name"><span class="t-green"></span> {{ track.title }}</p>
<audio controls preload="metadata">
<source src="/audio/tracks/{{ track.id }}/stream">
</audio>
</div>
{% endfor %}
{% else %}
<p class="term-empty-cmd">$ find ~/audio -name '*.mp3' → 0 results</p>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@@ -1,5 +1,5 @@
<!doctype html>
<html lang="en" data-theme="light">
<html lang="en" data-theme="dark">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -23,19 +23,23 @@
applyTheme(t);
highlightTheme(t);
}
applyTheme(localStorage.getItem('theme') || 'system');
applyTheme(localStorage.getItem('theme') || 'dark');
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function () {
if ((localStorage.getItem('theme') || 'system') === 'system') applyTheme('system');
if ((localStorage.getItem('theme') || 'dark') === 'system') applyTheme('system');
});
document.addEventListener('DOMContentLoaded', function () {
highlightTheme(localStorage.getItem('theme') || 'system');
highlightTheme(localStorage.getItem('theme') || 'dark');
var path = location.pathname;
document.querySelectorAll('.term-navlinks a[data-nav]').forEach(function (a) {
var h = a.getAttribute('data-nav');
if (h === path || (h !== '/' && path.indexOf(h) === 0)) a.classList.add('is-active');
});
});
</script>
<link href="/static/css/app.css" rel="stylesheet" type="text/css">
<link href="/static/css/theme.css" rel="stylesheet" type="text/css">
<script src="https://unpkg.com/htmx.org@1.9.12"></script>
<style>
.btn { --animation-btn: 0; --btn-focus-scale: 1; }
@media (min-width: 768px) {
.nav-menu { flex-direction: row; }
}
@@ -46,12 +50,12 @@
position: fixed;
inset: 0;
z-index: 40;
background-color: rgba(0, 0, 0, 0.25);
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
visibility: hidden;
transition: opacity 0.15s ease, visibility 0s linear 0.2s;
}
.navbar:has(.dropdown:focus-within) ~ #nav-backdrop {
.term-titlebar:has(.dropdown:focus-within) ~ #nav-backdrop {
opacity: 1;
visibility: visible;
transition: opacity 0.15s ease, visibility 0s;
@@ -59,28 +63,33 @@
}
</style>
</head>
<body class="min-h-screen bg-base-200 font-sans text-base-content antialiased">
<header class="navbar bg-base-100 shadow-sm">
<nav class="mx-auto flex w-full max-w-6xl items-center justify-between gap-2 px-4">
<a href="/" class="min-w-0 truncate text-lg font-bold">Universal Web</a>
<ul class="nav-menu menu menu-sm hidden items-center gap-1 md:flex">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/audio/albums">Audio</a></li>
<li><a href="/audio/tracks">Songs</a></li>
<body class="flex min-h-screen flex-col bg-base-100 text-base-content antialiased">
<header class="term-titlebar">
<nav class="term-nav">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<a href="/" class="term-brand">
<span class="t-green">{% if logged_in_admin %}root{% else %}visitor{% endif %}</span><span class="t-dim">@universal-web</span><span class="t-dim">:~$</span>
</a>
<ul class="nav-menu term-navlinks menu menu-sm hidden items-center md:flex">
<li><a href="/" data-nav="/">home</a></li>
<li><a href="/about" data-nav="/about">about</a></li>
<li><a href="/blog" data-nav="/blog">blog</a></li>
<li><a href="/audio/albums" data-nav="/audio/albums">audio</a></li>
<li><a href="/audio/tracks" data-nav="/audio/tracks">songs</a></li>
{% if logged_in_admin %}
<li><a href="/admin/dashboard">Dashboard</a></li>
<li><a href="/admin/dashboard" class="t-yellow" data-nav="/admin">admin</a></li>
<li>
<form method="post" action="/admin/logout">
<button type="submit" class="w-full">Logout</button>
<button type="submit" class="t-red w-full">logout</button>
</form>
</li>
{% else %}
<li><a href="/admin/login">Admin</a></li>
<li><a href="/admin/login" data-nav="/admin/login">login</a></li>
{% endif %}
</ul>
<div class="flex items-center gap-1">
<div class="term-nav-right">
<div class="dropdown dropdown-end md:hidden">
<div tabindex="0" role="button" class="btn btn-ghost btn-sm btn-circle" aria-label="Menu">
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5"
@@ -89,21 +98,21 @@
</svg>
</div>
<ul tabindex="0"
class="menu dropdown-content z-50 mt-3 w-52 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
<li><a href="/">Home</a></li>
<li><a href="/about">About</a></li>
<li><a href="/blog">Blog</a></li>
<li><a href="/audio/albums">Audio</a></li>
<li><a href="/audio/tracks">Songs</a></li>
class="menu dropdown-content z-50 mt-3 w-52 border border-base-300 bg-base-200 p-2 shadow-lg">
<li><a href="/">home</a></li>
<li><a href="/about">about</a></li>
<li><a href="/blog">blog</a></li>
<li><a href="/audio/albums">audio</a></li>
<li><a href="/audio/tracks">songs</a></li>
{% if logged_in_admin %}
<li><a href="/admin/dashboard">Dashboard</a></li>
<li><a href="/admin/dashboard" class="t-yellow">admin</a></li>
<li>
<form method="post" action="/admin/logout">
<button type="submit" class="w-full">Logout</button>
<button type="submit" class="t-red w-full">logout</button>
</form>
</li>
{% else %}
<li><a href="/admin/login">Admin</a></li>
<li><a href="/admin/login">login</a></li>
{% endif %}
</ul>
</div>
@@ -116,19 +125,28 @@
<path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
</svg>
</div>
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 rounded-box border border-base-300 bg-base-100 p-2 shadow-lg">
<li class="menu-title">Theme</li>
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">System <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">Light <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">Dark <span class="opt-check ml-auto hidden"></span></button></li>
<ul tabindex="0" class="menu dropdown-content z-50 mt-3 w-56 border border-base-300 bg-base-200 p-2 shadow-lg">
<li class="menu-title">:set theme</li>
<li><button type="button" data-theme-opt="system" onclick="setTheme('system')">system <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="light" onclick="setTheme('light')">light <span class="opt-check ml-auto hidden"></span></button></li>
<li><button type="button" data-theme-opt="dark" onclick="setTheme('dark')">dark <span class="opt-check ml-auto hidden"></span></button></li>
</ul>
</div>
</div>
</nav>
</header>
<div id="nav-backdrop" aria-hidden="true"></div>
<main class="mx-auto max-w-6xl px-4 py-6">
<main class="term-main">
{% block content %}{% endblock content %}
</main>
<footer class="term-statusline">
<span class="term-seg is-mode">{% if logged_in_admin %} ADMIN {% else %} NORMAL {% endif %}</span>
<span class="term-seg">universal-web</span>
<span class="term-seg is-fill">~/{% block crumb %}{% endblock crumb %}</span>
<span class="term-seg">utf-8</span>
<span class="term-seg">EADGBE</span>
<span class="term-seg is-alt">gruvbox-dark</span>
<span class="term-seg is-mode">100%</span>
</footer>
</body>
</html>

View File

@@ -1,47 +1,54 @@
{% extends "base.html" %}
{% block title %}Blog{% endblock title %}
{% block crumb %}blog{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">Blog</h1>
<p class="text-sm opacity-70">Published articles.</p>
</div>
{% if logged_in_admin %}
<a href="/admin/blog/articles" class="btn btn-ghost btn-sm">Manage blog</a>
{% endif %}
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/blog</span><span class="t-dim">$</span>
ls -la
</p>
<h1 class="term-title">blog</h1>
<p class="term-sub">// {{ articles | length }} published article(s).</p>
</div>
{% if logged_in_admin %}
<div class="term-cmd-actions">
<a href="/admin/blog/articles" class="btn btn-outline btn-sm">[ manage ]</a>
</div>
{% if articles | length > 0 %}
<div class="grid gap-4 pt-4">
{% for article in articles %}
<article class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex flex-wrap items-center justify-between gap-2">
<h2 class="card-title text-base">
<a href="/blog/{{ article.slug }}">{{ article.title }}</a>
</h2>
<span class="badge">Post</span>
</div>
{% if article.excerpt %}
<p class="text-sm leading-relaxed opacity-80">{{ article.excerpt }}</p>
{% endif %}
<div class="pt-2">
<a href="/blog/{{ article.slug }}" class="btn btn-neutral btn-sm">Read</a>
</div>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body text-center">
<p class="font-medium">No published posts yet.</p>
<p class="text-sm opacity-70">Published articles will appear here.</p>
</div>
</div>
{% endif %}
</div>
</header>
{% if articles | length > 0 %}
<div class="term-stack">
{% for article in articles %}
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
<span class="term-head-meta term-tag">post</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">
<a href="/blog/{{ article.slug }}">{{ article.title }}</a>
</h2>
{% if article.excerpt %}
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
{% endif %}
<div class="pt-2">
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">[ cat → ]</a>
</div>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="term-empty">
<p class="font-medium">no published posts yet</p>
<p class="term-empty-cmd">$ ls ~/blog → 0 results</p>
</div>
{% endif %}
{% endblock content %}

View File

@@ -1,25 +1,37 @@
{% extends "base.html" %}
{% block title %}{{ article.title }}{% endblock title %}
{% block crumb %}blog/{{ article.slug }}{% endblock crumb %}
{% block content %}
<article class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">{{ article.title }}</h1>
<p class="text-sm opacity-70">Views: {{ article.view_count }}</p>
</div>
<a href="/blog" class="btn btn-ghost btn-sm">Back to blog</a>
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~/blog</span><span class="t-dim">$</span>
cat {{ article.slug }}.txt
</p>
<h1 class="term-title">{{ article.title }}</h1>
<p class="term-sub">// {{ article.view_count }} view(s) logged.</p>
</div>
<div class="term-cmd-actions">
<a href="/blog" class="btn btn-outline btn-sm">[ cd .. ]</a>
</div>
</header>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
{% if article.excerpt %}
<p class="text-base leading-relaxed opacity-80">{{ article.excerpt }}</p>
<div class="border-t border-base-300 pt-4"></div>
{% endif %}
<div class="leading-relaxed whitespace-pre-line">{{ article.content }}</div>
</div>
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
<span class="term-head-meta term-tag is-blue">readonly</span>
</div>
<div class="card-body">
{% if article.excerpt %}
<p class="term-prose t-yellow"># {{ article.excerpt }}</p>
<div class="border-t border-base-300 pt-4"></div>
{% endif %}
<div class="term-prose whitespace-pre-line">{{ article.content }}</div>
</div>
</article>
{% endblock content %}

View File

@@ -1,47 +1,62 @@
{% extends "base.html" %}
{% block title %}Home{% endblock title %}
{% block crumb %}{% endblock crumb %}
{% block content %}
<div class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">Universal Web</h1>
<p class="text-sm opacity-70">Latest updates from the site.</p>
</div>
<a href="/blog" class="btn btn-ghost btn-sm">All posts</a>
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~</span><span class="t-dim">$</span>
ls -la
</p>
<h1 class="term-title">home</h1>
<p class="term-sub">// latest news and updates.</p>
</div>
<div class="term-cmd-actions">
<a href="/blog" class="btn btn-outline btn-sm">[ all posts ]</a>
</div>
</header>
<section class="pt-4">
{% if articles | length > 0 %}
<div class="grid gap-4">
{% for article in articles %}
<article class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="flex flex-wrap items-center justify-between gap-2">
<h2 class="card-title text-base">
<a href="/blog/{{ article.slug }}">{{ article.title }}</a>
</h2>
<span class="badge">Post</span>
</div>
{% if article.excerpt %}
<p class="text-sm leading-relaxed opacity-80">{{ article.excerpt }}</p>
{% endif %}
<div class="pt-2">
<a href="/blog/{{ article.slug }}" class="btn btn-neutral btn-sm">Read</a>
</div>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body text-center">
<p class="font-medium">No published posts yet.</p>
<p class="text-sm opacity-70">Check back later.</p>
</div>
</div>
{% endif %}
</section>
<div class="term-screen mb-6">
<p class="line" data-p="visitor@universal-web:~$">whoami</p>
<p class="line out">→ guitar player - original songs, albums and notes</p>
<p class="line" data-p="visitor@universal-web:~$">ls ~/sections</p>
<p class="line out">about/ blog/ audio/ songs/</p>
</div>
<section>
<p class="term-cmd-line mb-6"><span class="t-dim"># </span>recent posts <span class="t-dim">({{ articles | length }})</span></p>
{% if articles | length > 0 %}
<div class="term-stack">
{% for article in articles %}
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/blog/{{ article.slug }}.txt</span>
<span class="term-head-meta term-tag">post</span>
</div>
<div class="card-body">
<h2 class="card-title text-base">
<a href="/blog/{{ article.slug }}">{{ article.title }}</a>
</h2>
{% if article.excerpt %}
<p class="term-prose text-sm opacity-80">{{ article.excerpt }}</p>
{% endif %}
<div class="pt-2">
<a href="/blog/{{ article.slug }}" class="btn btn-primary btn-sm">[ cat → ]</a>
</div>
</div>
</article>
{% endfor %}
</div>
{% else %}
<div class="term-empty">
<p class="font-medium">no published posts yet</p>
<p class="term-empty-cmd">$ ls ~/blog → 0 results</p>
</div>
{% endif %}
</section>
{% endblock content %}

View File

@@ -1,23 +1,35 @@
{% extends "base.html" %}
{% block title %}{{ page.title }}{% endblock title %}
{% block crumb %}about{% endblock crumb %}
{% block content %}
<article class="space-y-2">
<div class="flex flex-wrap items-center justify-between gap-3">
<div>
<h1 class="text-2xl font-bold">{{ page.title }}</h1>
<p class="text-sm opacity-70">About this site.</p>
</div>
{% if logged_in_admin %}
<a href="/admin/about" class="btn btn-ghost btn-sm">Edit page</a>
{% endif %}
<header class="term-cmd">
<div>
<p class="term-cmd-line">
<span class="t-green">visitor@universal-web</span><span class="t-dim">:</span><span class="t-blue">~</span><span class="t-dim">$</span>
cat about.txt
</p>
<h1 class="term-title">{{ page.title }}</h1>
<p class="term-sub">// about this site.</p>
</div>
{% if logged_in_admin %}
<div class="term-cmd-actions">
<a href="/admin/about" class="btn btn-outline btn-sm">[ edit ]</a>
</div>
{% endif %}
</header>
<div class="card border border-base-300 bg-base-100 shadow-sm">
<div class="card-body">
<div class="leading-relaxed whitespace-pre-line">{{ page.content }}</div>
</div>
<article class="card">
<div class="term-head">
<span class="term-dots" aria-hidden="true">
<span class="term-dot r"></span><span class="term-dot y"></span><span class="term-dot g"></span>
</span>
<span class="term-head-name">~/about.txt</span>
<span class="term-head-meta term-tag is-blue">readonly</span>
</div>
<div class="card-body">
<div class="term-prose whitespace-pre-line">{{ page.content }}</div>
</div>
</article>
{% endblock content %}