using webp for blogs now

This commit is contained in:
Ludwig Lehnert 2025-04-21 17:01:06 +00:00
parent 6c62eedd94
commit 886e022d23
53 changed files with 780 additions and 86 deletions

125
scripts/convert-blog Executable file
View File

@ -0,0 +1,125 @@
#!/usr/bin/env python3
import re
import os
import gzip
import json
import shutil
import argparse
import tempfile
import subprocess
from datetime import date
from lxml import etree as ET
def open_blog(path: str) -> ET._Element:
with gzip.open(path, 'rt', encoding='utf-8') as f:
raw_svg = f.read()
return ET.fromstring(raw_svg)
def remove_by_xpath(el: ET._Element, xpath: str):
for node in el.xpath(xpath):
parent = node.getparent()
parent.remove(node)
def tag_xpath(tag: str):
return f".//*[local-name()='{tag}']"
def class_xpath(class_: str):
return f".//*[contains(concat(' ', normalize-space(@class), ' '), '{class_}')]"
def save_blog(dir: str, root: ET._Element):
os.makedirs(dir, exist_ok=True)
frame: ET._Element = root.xpath(tag_xpath('svg'))[0]
width = int(frame.attrib['width'][:-2])
height = int(frame.attrib['height'][:-2])
root.attrib['viewBox'] = f'0 0 {width} {height}'
content: ET._Element = frame.getchildren()[0]
root.remove(frame)
root.append(content)
remove_by_xpath(root, ".//*[@id='write-document']")
remove_by_xpath(root, ".//*[@id='write-defs']")
remove_by_xpath(root, ".//*[@id='write-doc-background']")
remove_by_xpath(root, class_xpath('ruleline'))
bookmarks: list[tuple[str, float]] = []
for node in root.xpath(class_xpath('bookmark')):
match = re.search(r"translate\([^\d\-]*([-+]?\d*\.?\d+),\s*([-+]?\d*\.?\d+)\)", node.attrib['transform'])
bookmarks.append((node.attrib['id'], float(match.group(2))))
node.getparent().remove(node)
hyperrefs: list[tuple[ET._Element, str, tuple[float, float], tuple[float, float]]] = []
for node in root.xpath(class_xpath('hyperref')):
anchor = node.xpath(tag_xpath('a'))[0]
rect = anchor.xpath(tag_xpath('rect'))[0]
x, y = float(rect.attrib['x']), float(rect.attrib['y'])
w, h = float(rect.attrib['width']), float(rect.attrib['height'])
url = anchor.attrib['{http://www.w3.org/1999/xlink}href']
hyperrefs.append((node, url, (x, y), (w, h)))
anchor.getparent().remove(anchor)
node.getparent().remove(node)
tempdir = tempfile.mkdtemp()
try:
with open(f'{tempdir}/main.svg', 'wb') as f:
f.write(ET.tostring(root, xml_declaration=True, encoding="utf-8"))
subprocess.call(['magick', '-density', '300', '-background', 'none', f'{tempdir}/main.svg', f'{tempdir}/main.png'])
subprocess.call(['magick', f'{tempdir}/main.png', '-crop', 'x16383', '+repage', f'{tempdir}/main_%03d.webp'])
main_files: list[str] = []
for file in os.listdir(tempdir):
if file.endswith('webp'): main_files.append(file)
url_defs: list[tuple[str, str, tuple[float, float], tuple[float, float]]] = []
for node, url, (x, y), (w, h) in hyperrefs:
name = os.urandom(15).hex()[:7]
svg = ET.Element('svg', attrib={'viewBox': f'{x} {y} {w} {h}'}, nsmap={None: 'http://www.w3.org/2000/svg'})
svg.append(node)
with open(f'{tempdir}/{name}.svg', 'wb') as f:
f.write(ET.tostring(svg, xml_declaration=True, encoding="utf-8"))
subprocess.call(['magick', '-density', '300', '-background', 'none', f'{tempdir}/{name}.svg',f'{tempdir}/{name}.png'])
subprocess.call(['magick', f'{tempdir}/{name}.png', f'{tempdir}/{name}.webp'])
url_defs.append((f'{name}.webp', url, (x, y), (w, h)))
for file in os.listdir(tempdir):
if file.endswith('webp'):
shutil.move(f'{tempdir}/{file}', f'{dir}/{file}')
with open(f'{dir}/_definition.json', 'w') as f:
json.dump({
'date': date.today().strftime("%Y-%m-%d"),
'title': 'My Cool Blog Entry',
'keywords': ['Cool', 'Blog'],
'bookmarks': list(map(lambda b: {'id': b[0], 'offset': b[1]}, bookmarks)),
'urls': list(map(lambda d: {'src': d[0], 'href': d[1], 'offset': d[2], 'dimensions': d[3]}, url_defs)),
'files': sorted(main_files),
'dimensions': [width, height],
}, f, indent=4)
finally:
shutil.rmtree(tempdir, ignore_errors=True)
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--svgz', required=True, type=str)
parser.add_argument('--dir', required=True, type=str)
return parser.parse_args()
def main():
args = parse_args()
save_blog(args.dir, open_blog(args.svgz))
if __name__ == '__main__':
main()

View File

@ -31,7 +31,7 @@ RewriteRule ^(typo|admin|login|dashboard).*$ /dummy/kaffee.php [L]
# Rewrite /lib/* or /blog/* to /error.php?code=404 # Rewrite /lib/* or /blog/* to /error.php?code=404
# with 404 response code (internal rewriting) # with 404 response code (internal rewriting)
# ---------------------------------------------------- # ----------------------------------------------------
RewriteCond %{REQUEST_URI} ^/(lib|blog)(/.*)?$ RewriteCond %{REQUEST_URI} ^/(lib)(/.*)?$
RewriteRule ^ - [R=404,L] RewriteRule ^ - [R=404,L]
ErrorDocument 404 /error.php?code=404 ErrorDocument 404 /error.php?code=404

View File

@ -2,22 +2,25 @@
require_once __DIR__ . '/lib/_index.php'; require_once __DIR__ . '/lib/_index.php';
$id = (int) $_GET['id']; $id = $_GET['id'];
$blog = Blog::get($id); $blog = Blog::get($id);
if ($blog === null) { if ($blog === null) {
http_response_code(404); $_GET['code'] = 404;
require __DIR__ . '/404.php'; require __DIR__ . '/error.php';
exit; exit;
} }
$svgs = $blog->getSVGs();
$rand_imprint = mt_rand() % 6; $rand_imprint = mt_rand() % 6;
$rand_privacy = mt_rand() % 6; $rand_privacy = mt_rand() % 6;
$rand_comments = mt_rand() % 6; $rand_comments = mt_rand() % 6;
$rand_send_comment = mt_rand() % 5; $rand_send_comment = mt_rand() % 5;
function is_external_url(string $url): bool
{
return !str_starts_with($url, '#');
}
?><!DOCTYPE html> ?><!DOCTYPE html>
<html lang="en"> <html lang="en">
@ -27,11 +30,9 @@ $rand_send_comment = mt_rand() % 5;
<link rel="shortcut icon" href="/favicon.svg" type="image/x-icon"> <link rel="shortcut icon" href="/favicon.svg" type="image/x-icon">
<base target="_blank"> <meta name="keywords" content="ludwig lehnert,blog,<?= join(",", $blog->keywords()) ?>">
<meta name="keywords" content="ludwig lehnert,blog,<?= join(",", $blog->keywords) ?>"> <title><?= $blog->title() ?></title>
<title><?= $blog->title ?></title>
<style> <style>
html, html,
@ -40,6 +41,11 @@ $rand_send_comment = mt_rand() % 5;
background: white; background: white;
} }
body {
width: min(100%, 800px);
margin: auto;
}
* { * {
margin: 0; margin: 0;
padding: 0; padding: 0;
@ -47,22 +53,47 @@ $rand_send_comment = mt_rand() % 5;
} }
main { main {
display: flex; position: relative;
flex-direction: column; width: 100%;
align-items: center; aspect-ratio:
<?= $blog->width() ?>
/
<?= $blog->height() ?>
;
} }
main>* { .content img {
display: block; display: block;
width: min(100vw, 1000px); width: 100%;
height: auto; height: auto;
} }
svg .hyperref { .bookmarks {
cursor: pointer; z-index: -1;
position: absolute;
top: 0;
width: 100%;
height: 100%;
} }
svg .hyperref:hover { .bookmarks * {
position: absolute;
height: 10px;
width: 100%;
}
.urls {
position: absolute;
top: 0;
width: 100%;
height: 100%;
}
.urls a {
position: absolute;
}
.urls a:hover {
opacity: 0.6; opacity: 0.6;
} }
@ -79,12 +110,12 @@ $rand_send_comment = mt_rand() % 5;
width: auto; width: auto;
} }
.bottom-links { .legal-links {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
.bottom-links svg { .legal-links svg {
max-height: 4rem; max-height: 4rem;
height: 10vw; height: 10vw;
min-height: 2rem; min-height: 2rem;
@ -96,24 +127,47 @@ $rand_send_comment = mt_rand() % 5;
<body> <body>
<main> <main>
<?php foreach ($svgs as $svg): ?> <div class="content">
<?= $svg ?> <?php foreach ($blog->files() as $file): ?>
<img src="/blog/<?= $blog->id ?>/<?= $file ?>">
<?php endforeach; ?> <?php endforeach; ?>
</div>
<div class="bookmarks">
<?php foreach ($blog->bookmarks() as $bm): ?>
<div id="<?= $bm['id'] ?>" style="top: <?= 100 * $bm['offset'] / $blog->height() ?>%;"></div>
<?php endforeach; ?>
</div>
<div class="urls">
<?php foreach ($blog->urls() as $url): ?>
<a href="<?= $url['href'] ?>" <?php if (is_external_url($url['href']))
echo 'target="_blank"'; ?> style="
top: <?= 100 * $url['offset'][1] / $blog->height() ?>%;
left: <?= 100 * $url['offset'][0] / $blog->width() ?>%;
width: <?= 100 * $url['dimensions'][0] / $blog->width() ?>%;">
<img src="/blog/<?= $blog->id ?>/<?= $url['src'] ?>" style="width: 100%;">
</a>
<?php endforeach; ?>
</div>
</main>
<div style="height: 100px"></div>
<footer>
<div class="send-comment"> <div class="send-comment">
<?= get_svg(__DIR__ . "/assets/blog/send-comment$rand_send_comment.svg") ?> <?= get_svg(__DIR__ . "/assets/blog/send-comment$rand_send_comment.svg") ?>
</div> </div>
<div style="height: 100px"></div> <div style="height: 100px"></div>
<div class="bottom-links"> <div class="legal-links">
<?= get_svg(__DIR__ . "/assets/blog/imprint$rand_imprint.svg") ?> <?= get_svg(__DIR__ . "/assets/blog/imprint$rand_imprint.svg") ?>
<?= get_svg(__DIR__ . "/assets/blog/privacy$rand_privacy.svg") ?> <?= get_svg(__DIR__ . "/assets/blog/privacy$rand_privacy.svg") ?>
</div> </div>
</footer>
<div style="height: 100px"></div> <div style="height: 100px"></div>
</main>
</body> </body>
</html> </html>

View File

@ -1,11 +0,0 @@
{
"title": "Test: First Blog Entry",
"keywords": [
"test",
"first blog entry"
],
"date": "2025-04-15",
"files": [
"0.svg"
]
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.1 MiB

View File

@ -1,12 +0,0 @@
{
"title": "Thoughts about edge computation",
"keywords": [
"edge",
"edge computation",
"thoughts about edge computation"
],
"date": "2025-04-17",
"files": [
"1.svg"
]
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.3 MiB

BIN
www/blog/one/07a08c4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

BIN
www/blog/one/09eef40.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
www/blog/one/0aea930.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
www/blog/one/15704c1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 678 B

BIN
www/blog/one/17e65d5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

BIN
www/blog/one/1cc4eb8.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

BIN
www/blog/one/2618a28.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
www/blog/one/2f7fe30.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 608 B

BIN
www/blog/one/30d5de3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
www/blog/one/35ca008.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

BIN
www/blog/one/4d55914.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
www/blog/one/4e13630.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 598 B

BIN
www/blog/one/53145df.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
www/blog/one/590f333.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
www/blog/one/5ad1430.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
www/blog/one/64a799d.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 606 B

BIN
www/blog/one/717c078.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
www/blog/one/7e7a7e4.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

BIN
www/blog/one/908a399.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
www/blog/one/9c229c9.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,417 @@
{
"date": "2025-04-17",
"title": "Thoughts about edge computation",
"keywords": [
"edge",
"edge computation",
"thoughts about"
],
"bookmarks": [
{
"id": "b-d5I21c9U7J",
"offset": 1914.461
},
{
"id": "b-fLRN0SS32U",
"offset": 4834.461
},
{
"id": "b-sXQ0BxXCpj",
"offset": 5754.461
},
{
"id": "b-yFt7MQSghl",
"offset": 6014.461
},
{
"id": "b-yR35Qi4Msf",
"offset": 6774.461
},
{
"id": "b-qxKx5OdrIT",
"offset": 7954.461
}
],
"urls": [
{
"src": "5ad1430.webp",
"href": "https://en.wikipedia.org/wiki/Edge_computing",
"offset": [
291.575,
369.178
],
"dimensions": [
22.265,
17.722
]
},
{
"src": "908a399.webp",
"href": "https://en.wikipedia.org/wiki/Time_to_live",
"offset": [
163.033,
1462.429
],
"dimensions": [
49.831,
17.907
]
},
{
"src": "b285770.webp",
"href": "https://en.wikipedia.org/wiki/Active_users",
"offset": [
20.623,
1579.52
],
"dimensions": [
42.045,
18.93
]
},
{
"src": "9c229c9.webp",
"href": "#b-d5I21c9U7J",
"offset": [
323.993,
2981.505
],
"dimensions": [
75.638,
30.079
]
},
{
"src": "717c078.webp",
"href": "https://cloud.google.com/products/calculator",
"offset": [
180.869,
3060.241
],
"dimensions": [
184.16,
28.221
]
},
{
"src": "b55185f.webp",
"href": "https://cloud.google.com/run/pricing#pricing_for_regions_in_tier_1",
"offset": [
302.069,
3384.668
],
"dimensions": [
40.145,
12.978
]
},
{
"src": "c5aea0a.webp",
"href": "https://cloud.google.com/run/pricing#pricing_for_regions_in_tier_2",
"offset": [
394.121,
3406.002
],
"dimensions": [
45.343,
13.91
]
},
{
"src": "fe86e6f.webp",
"href": "https://en.wikipedia.org/wiki/Virtual_private_server",
"offset": [
65.769,
3762.201
],
"dimensions": [
33.444,
18.997
]
},
{
"src": "53145df.webp",
"href": "https://www.hetzner.com/cloud/",
"offset": [
153.962,
3760.823
],
"dimensions": [
127.856,
20.199
]
},
{
"src": "17e65d5.webp",
"href": "#b-fLRN0SS32U",
"offset": [
328.668,
5220.784
],
"dimensions": [
99.508,
30.447
]
},
{
"src": "a0be263.webp",
"href": "https://en.wikipedia.org/wiki/JSON_Web_Token",
"offset": [
414.148,
5500.374
],
"dimensions": [
39.779,
26.594
]
},
{
"src": "30d5de3.webp",
"href": "https://en.wikipedia.org/wiki/Round-trip_delay",
"offset": [
78.417,
6561.832
],
"dimensions": [
40.835,
16.013
]
},
{
"src": "2f7fe30.webp",
"href": "#b-sXQ0BxXCpj",
"offset": [
132.554,
6562.702
],
"dimensions": [
9.351,
14.592
]
},
{
"src": "4e13630.webp",
"href": "#b-yFt7MQSghl",
"offset": [
227.85,
6564.368
],
"dimensions": [
13.333,
9.814
]
},
{
"src": "c1e1de3.webp",
"href": "#b-sXQ0BxXCpj",
"offset": [
108.014,
6621.458
],
"dimensions": [
10.852,
14.101
]
},
{
"src": "09eef40.webp",
"href": "#b-fLRN0SS32U",
"offset": [
359.605,
7101.638
],
"dimensions": [
88.394,
29.214
]
},
{
"src": "15704c1.webp",
"href": "#b-sXQ0BxXCpj",
"offset": [
397.962,
7261.618
],
"dimensions": [
8.913,
18.387
]
},
{
"src": "f4b2484.webp",
"href": "#b-yFt7MQSghl",
"offset": [
440.001,
7261.85
],
"dimensions": [
14.139,
17.629
]
},
{
"src": "f44e23c.webp",
"href": "#b-sXQ0BxXCpj",
"offset": [
182.096,
7583.017
],
"dimensions": [
10.694,
13.19
]
},
{
"src": "64a799d.webp",
"href": "#b-yFt7MQSghl",
"offset": [
278.685,
7586.278
],
"dimensions": [
14.377,
10.387
]
},
{
"src": "ea08577.webp",
"href": "#b-sXQ0BxXCpj",
"offset": [
109.759,
7643.622
],
"dimensions": [
9.297,
13.103
]
},
{
"src": "0aea930.webp",
"href": "#b-fLRN0SS32U",
"offset": [
123.703,
7736.656
],
"dimensions": [
115.125,
34.642
]
},
{
"src": "7e7a7e4.webp",
"href": "https://en.wikipedia.org/wiki/ACID",
"offset": [
84.38,
8640.77
],
"dimensions": [
48.438,
20.131
]
},
{
"src": "07a08c4.webp",
"href": "#b-fLRN0SS32U",
"offset": [
361.351,
8940.184
],
"dimensions": [
91.42,
33.02
]
},
{
"src": "35ca008.webp",
"href": "https://en.wikipedia.org/wiki/NoSQL",
"offset": [
216.592,
9041.812
],
"dimensions": [
68.499,
18.973
]
},
{
"src": "1cc4eb8.webp",
"href": "https://en.wikipedia.org/wiki/Relational_database",
"offset": [
381.531,
8540.439
],
"dimensions": [
77.199,
20.197
]
},
{
"src": "590f333.webp",
"href": "https://en.wikipedia.org/wiki/Graph_database",
"offset": [
307.123,
9079.422
],
"dimensions": [
51.318,
26.761
]
},
{
"src": "4d55914.webp",
"href": "https://en.wikipedia.org/wiki/Content_delivery_network",
"offset": [
87.833,
9358.795
],
"dimensions": [
250.808,
30.198
]
},
{
"src": "f072857.webp",
"href": "https://en.wikipedia.org/wiki/Denial-of-service_attack#Distributed_DoS",
"offset": [
82.307,
9398.894
],
"dimensions": [
54.456,
19.93
]
},
{
"src": "f63dffe.webp",
"href": "https://en.wikipedia.org/wiki/TLS_termination_proxy",
"offset": [
80.339,
9479.985
],
"dimensions": [
163.634,
19.53
]
},
{
"src": "2618a28.webp",
"href": "https://en.wikipedia.org/wiki/Load_balancing_(computing)",
"offset": [
169.377,
9440.959
],
"dimensions": [
155.393,
32.558
]
}
],
"files": [
"main_000.webp",
"main_001.webp"
],
"dimensions": [
480,
9700
]
}

BIN
www/blog/one/a0be263.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

BIN
www/blog/one/b285770.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
www/blog/one/b55185f.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
www/blog/one/c1e1de3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
www/blog/one/c5aea0a.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

BIN
www/blog/one/ea08577.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

BIN
www/blog/one/f072857.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
www/blog/one/f44e23c.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

BIN
www/blog/one/f4b2484.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

BIN
www/blog/one/f63dffe.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

BIN
www/blog/one/fe86e6f.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
www/blog/one/main_000.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
www/blog/one/main_001.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 963 KiB

BIN
www/blog/zero/18bddb5.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
www/blog/zero/2db2951.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
www/blog/zero/60d1274.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
www/blog/zero/6a1748c.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
www/blog/zero/76c4bca.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

BIN
www/blog/zero/913fe45.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,90 @@
{
"date": "2025-04-15",
"title": "Test: First Blog Entry",
"keywords": [
"test",
"first blog entry"
],
"bookmarks": [],
"urls": [
{
"src": "18bddb5.webp",
"href": "https://google.com",
"offset": [
176.315,
378.853
],
"dimensions": [
42.536,
25.653
]
},
{
"src": "76c4bca.webp",
"href": "https://handwritten.blog",
"offset": [
271.101,
511.231
],
"dimensions": [
142.634,
40.839
]
},
{
"src": "913fe45.webp",
"href": "mailto:blog@lehnert.dev",
"offset": [
60.28,
868.451
],
"dimensions": [
188.266,
44.067
]
},
{
"src": "60d1274.webp",
"href": "https://en.wikipedia.org/wiki/SVG",
"offset": [
454.485,
1012.028
],
"dimensions": [
35.147,
20.576
]
},
{
"src": "6a1748c.webp",
"href": "https://en.wikipedia.org/wiki/PNG",
"offset": [
320.493,
1057.862
],
"dimensions": [
44.903,
19.773
]
},
{
"src": "2db2951.webp",
"href": "https://inkscape.org/",
"offset": [
64.774,
1231.579
],
"dimensions": [
87.11,
37.658
]
}
],
"files": [
"main_000.webp"
],
"dimensions": [
650,
1400
]
}

BIN
www/blog/zero/main_000.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

View File

@ -8,6 +8,8 @@ if (isset($_GET['code'])) {
$code = (int) $_GET['code']; $code = (int) $_GET['code'];
} }
http_response_code($code);
$msg = '🪐 Unknown error! Something went wrong in the fabric of spacetime.'; $msg = '🪐 Unknown error! Something went wrong in the fabric of spacetime.';
$action = 'Stabilize systems'; $action = 'Stabilize systems';

View File

@ -136,6 +136,10 @@ usort($blogs, function ($a, $b) {
pointer-events: auto; pointer-events: auto;
} }
.none {
display: none;
}
@keyframes glow { @keyframes glow {
from { from {
@ -188,15 +192,20 @@ usort($blogs, function ($a, $b) {
</div> </div>
<div class="blog-entries"> <div class="blog-entries">
<h2 class="none">Handwritten Blog</h2>
<?php foreach ($blogs as $blog): ?> <?php foreach ($blogs as $blog): ?>
<div class="blog-entry"> <div class="blog-entry">
<h2><a href="/blog/<?= $blog->id ?>"><?= $blog->title ?></a></h2> <h2><a href="/blog/<?= $blog->id ?>"><?= $blog->title() ?></a></h2>
<p><?= $blog->formatDate() ?></p> <p><?= $blog->formatDate() ?></p>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div style="height: 350px;"></div> <div style="height: 350px;"></div>
<!-- SEO Tags -->
<h1 class="none">Ludwig Lehnert</h1>
</main> </main>
</body> </body>

View File

@ -3,41 +3,68 @@
class Blog class Blog
{ {
public readonly string $id; public readonly string $id;
public readonly string $title; public readonly array $json;
public readonly array $keywords;
public readonly string $date;
public readonly array $files;
public function __construct(string $id, string $title, array $keywords, string $date, array $files) public function __construct(string $id, array $json)
{ {
$this->id = $id; $this->id = $id;
$this->title = $title; $this->json = $json;
$this->keywords = $keywords; }
$this->date = $date;
$this->files = $files; public function date(): string
{
return $this->json['date'];
}
public function title(): string
{
return $this->json['title'];
}
public function keywords(): array
{
return $this->json['keywords'];
}
public function bookmarks(): array
{
return $this->json['bookmarks'];
}
public function urls(): array
{
return $this->json['urls'];
}
public function files(): array
{
return $this->json['files'];
}
public function width(): float
{
return $this->json['dimensions'][0];
}
public function height(): float
{
return $this->json['dimensions'][1];
} }
public function formatDate(): string public function formatDate(): string
{ {
return date("F jS, Y", strtotime($this->date)); return date("F jS, Y", strtotime($this->date()));
} }
public function getSVGs(): array public static function get(string $id): Blog|null
{ {
return array_map(function ($file) { $path = __DIR__ . "/../blog/$id/_definition.json";
return get_svg(__DIR__ . '/../blog/' . $file);
}, $this->files);
}
public static function get(string|int $id): Blog|null
{
$path = __DIR__ . "/../blog/$id.json";
if (!file_exists($path)) if (!file_exists($path))
return null; return null;
try { try {
$json = json_decode(file_get_contents($path), true); $json = json_decode(file_get_contents($path), true);
return new Blog($id, $json['title'], $json['keywords'], $json['date'], $json['files']); return new Blog($id, $json);
} catch (Exception $e) { } catch (Exception $e) {
return null; return null;
} }
@ -48,18 +75,12 @@ class Blog
*/ */
public static function getAll() public static function getAll()
{ {
$files = scandir(__DIR__ . '/../blog'); $dirs = scandir(__DIR__ . '/../blog');
$files = array_filter($files, function ($file) { $dirs = array_filter($dirs, fn($dir) => is_dir(__DIR__ . "/../blog/$dir") && $dir !== '.' && $dir !== '..');
return str_ends_with($file, '.json');
});
$ids = array_map(function ($file) {
return str_replace('.json', '', $file);
}, $files);
$blogs = []; $blogs = [];
foreach ($ids as $id) { foreach ($dirs as $id) {
$blog = Blog::get($id); $blog = Blog::get($id);
if ($blog !== null) if ($blog !== null)
array_push($blogs, $blog); array_push($blogs, $blog);

View File

@ -4,6 +4,8 @@ require_once __DIR__ . "/lib/_index.php";
$blogs = Blog::getAll(); $blogs = Blog::getAll();
header("Content-Type: application/xml");
echo '<?'; echo '<?';
?>xml version="1.0" encoding="UTF-8"?> ?>xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"> <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
@ -18,20 +20,20 @@ echo '<?';
<?php foreach ($blogs as $blog): ?> <?php foreach ($blogs as $blog): ?>
<url> <url>
<loc>https://lehnert.dev/blog/<?= $blog->id ?></loc> <loc>https://lehnert.dev/blog/<?= $blog->id ?></loc>
<lastmod><?= $blog->date ?></lastmod> <!-- <lastmod><?= $blog->date() ?></lastmod> -->
<priority>0.8</priority> <priority>0.8</priority>
</url> </url>
<?php endforeach; ?> <?php endforeach; ?>
<url> <url>
<loc>https://lehnert.dev/imprint</loc> <loc>https://lehnert.dev/imprint/</loc>
<lastmod>2025-04-16</lastmod> <lastmod>2025-04-16</lastmod>
<changefreq>yearly</changefreq> <changefreq>yearly</changefreq>
<priority>0.5</priority> <priority>0.5</priority>
</url> </url>
<url> <url>
<loc>https://lehnert.dev/privacy</loc> <loc>https://lehnert.dev/privacy/</loc>
<lastmod>2025-04-16</lastmod> <lastmod>2025-04-16</lastmod>
<changefreq>yearly</changefreq> <changefreq>yearly</changefreq>
<priority>0.5</priority> <priority>0.5</priority>