all the website mainly done

This commit is contained in:
0xa0
2026-07-21 20:45:47 +02:00
parent 20f9aca26b
commit 30609c3bb3
15 changed files with 2127 additions and 25 deletions
+1712
View File
File diff suppressed because it is too large Load Diff
+79
View File
@@ -0,0 +1,79 @@
<?php
require_once __DIR__ . '/../config.php';
/**
* 88x31 buttons shown in the footer.
* To add one, just append a line: ['href' => 'https://...', 'src' => 'https://.../banner.png', 'alt' => 'name'].
* The image is fetched + cached server-side, so nothing hotlinks the remote host on every visit.
*/
const BUTTONS_88x31 = [
['href' => 'https://patate.dev', 'src' => 'https://patate.dev/images/patate_dev_banner.jpeg', 'alt' => 'patate.dev'],
['href' => 'https://blog.counter-strike.net/', 'src' => 'https://matdoes.dev/buttons/i/8678a5dfa58ce412f7d8f978b14e2f64.gif', 'alt' => 'https://blog.counter-strike.net/'],
['href' => 'caddyserver.com', 'src' => 'https://caddyserver.com/resources/images/nostalgia/caddy.png', 'alt' => 'caddyserver.com'],
['href' => '', 'src' => 'https://matdoes.dev/buttons/i/9025eae1851e8f32ff70f43f2c3393f0.png', 'alt' => 'ownpaws'],
['href' => 'https://ssi.fyi/', 'src'=>'https://ssi.fyi/88x31.gif', 'alt'=>'https://ssi.fyi'],
['href' => 'https://vmfunc.re/', 'src' => 'https://vmfunc.re/button.png', 'alt' => 'vmfunc.re'],
['href' => 'https://0xa0.dev/', 'src' => '/img/0xa0.png', 'alt' => '0xA0.dev']
];
const BUTTON_CACHE_TTL = 7 * 86400; // re-fetch each button at most once a week
/**
* Fetch a remote image, cache it locally, and return it as a base64 data: URI.
* Returns null on failure so the caller can fall back to hotlinking.
*/
function button_data_uri(string $url): ?string
{
$dir = DATA_DIR . '/buttons';
@mkdir($dir, 0775, true);
$file = $dir . '/' . md5($url); // stores "content-type\n<raw bytes>"
if (!is_file($file) || (time() - filemtime($file)) > BUTTON_CACHE_TTL) {
$ctx = stream_context_create(['http' => [
'timeout' => 5,
'user_agent' => 'Mozilla/5.0 (0xa0.dev button fetcher)',
]]);
$bytes = @file_get_contents($url, false, $ctx);
if ($bytes !== false && $bytes !== '') {
$type = 'image/png';
foreach ($http_response_header ?? [] as $h) {
if (stripos($h, 'content-type:') === 0) {
$type = trim(substr($h, strlen('content-type:')));
break;
}
}
file_put_contents($file, $type . "\n" . $bytes);
}
}
if (!is_file($file)) {
return null;
}
$raw = (string) file_get_contents($file);
$nl = strpos($raw, "\n");
if ($nl === false) {
return null;
}
$type = substr($raw, 0, $nl);
$data = substr($raw, $nl + 1);
return 'data:' . $type . ';base64,' . base64_encode($data);
}
/** Render the whole 88x31 button row as HTML. */
function render_buttons_88x31(): string
{
$html = '';
foreach (BUTTONS_88x31 as $b) {
$uri = button_data_uri($b['src']) ?? $b['src']; // fall back to hotlink if fetch failed
$img = '<img src="' . htmlspecialchars($uri) . '" alt="' . htmlspecialchars($b['alt']) . '" width="88" height="31">';
$href = $b['href'] ?? '';
if ($href !== '') {
$html .= '<a href="' . htmlspecialchars($href) . '" target="_blank" rel="noopener">' . $img . '</a>' . "\n";
} else {
$html .= $img . "\n"; // no href → just the image, not clickable
}
}
return $html;
}
+4
View File
@@ -1,6 +1,10 @@
<?php $build = trim((string) @file_get_contents(__DIR__ . '/../build.txt')) ?: 'dev'; ?>
<?php require_once __DIR__ . '/buttons.php'; ?>
</main>
<footer>
<div class="buttons88x31">
<?= render_buttons_88x31() ?>
</div>
<small>&copy; <?= date('Y') ?> · <a href="https://git.0xa0.dev">source</a> · build: <?= htmlspecialchars($build) ?></small>
</footer>
</body>
+3 -1
View File
@@ -4,14 +4,16 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?= htmlspecialchars(SITE_TITLE) ?></title>
<title><?= htmlspecialchars($page_title ?? SITE_TITLE) ?></title>
<link rel="stylesheet" href="/css/style.css">
<script src="/js/htmx.min.js" defer></script>
<script src="/js/idiomorph-ext.min.js" defer></script>
</head>
<body>
<header>
<nav>
<a href="/">home</a>
<a href="/blog.php">blog</a>
<a href="/guestbook.php">guestbook</a>
</nav>
</header>
+87
View File
@@ -0,0 +1,87 @@
<?php
require_once __DIR__ . '/../config.php';
$__er = error_reporting();
error_reporting($__er & ~E_DEPRECATED);
require_once __DIR__ . '/Parsedown.php';
error_reporting($__er);
const POSTS_DIR = __DIR__ . '/../posts';
function post_slug_valid(string $slug): bool {
return (bool) preg_match('/^[a-z0-9][a-z0-9-]*$/', $slug);
}
function parse_front_matter(string $raw): array {
$meta = [];
$body = $raw;
if (preg_match('/^(?:\xEF\xBB\xBF)?---\r?\n(.*?)\r?\n---\r?\n?(.*)$/s', $raw, $m)) {
foreach (preg_split('/\r?\n/', $m[1]) as $line) {
if (preg_match('/^(\w+):\s*(.*)$/', $line, $kv)) {
$meta[strtolower($kv[1])] = trim($kv[2]);
}
}
$body = $m[2];
}
return [$meta, $body];
}
function load_post(string $slug): ?array {
if (!post_slug_valid($slug)) {
return null;
}
$file = POSTS_DIR . '/' . $slug . '.md';
if (!is_file($file)) {
return null;
}
$raw = (string) file_get_contents($file);
[$meta, $body] = parse_front_matter($raw);
return [
'slug' => $slug,
'title' => $meta['title'] ?? $slug,
'date' => $meta['date'] ?? date('Y-m-d', filemtime($file)),
'body' => $body,
];
}
function all_posts(): array {
$posts = [];
foreach (glob(POSTS_DIR . '/*.md') ?: [] as $file) {
if ($p = load_post(basename($file, '.md'))) {
$posts[] = $p;
}
}
usort($posts, fn($a, $b) => strcmp($b['date'], $a['date']));
return $posts;
}
function render_markdown(string $md): string {
static $pd = null;
if ($pd === null) {
$pd = new Parsedown();
$pd->setSafeMode(true);
}
return $pd->text($md);
}
function post_date_display(string $date): string {
$ts = strtotime($date);
return $ts ? date('m/d/Y', $ts) : htmlspecialchars($date);
}
function render_post_list(array $posts): void {
if (!$posts) {
echo '<p class="muted">no posts yet.</p>';
return;
}
echo '<ul class="post-list">';
foreach ($posts as $p) {
printf(
'<li><a href="/post.php?slug=%s">%s</a><span class="date">%s</span></li>',
urlencode($p['slug']),
htmlspecialchars($p['title']),
post_date_display($p['date'])
);
}
echo '</ul>';
}