1#! /usr/bin/env python3
2
3import datetime
4import os
5import shutil
6import subprocess
7
8
9def run(*args):
10 subprocess.call(args)
11
12
13def main():
14 time = datetime.datetime.now()
15 time_id = time.strftime('%Y%m%d%H%M%S')
16 style = 'css'
17 script = 'js'
18 root = os.path.dirname(os.path.realpath(__file__))
19 input_directory = os.path.join(root, 'in')
20 out = os.path.join(root, 'out')
21 web = os.path.join(out, 'web')
22 if os.path.exists(web):
23 shutil.rmtree(web)
24 os.makedirs(web)
25 gen = os.path.join(web, time_id)
26 css = os.path.join(gen, style)
27 js = os.path.join(gen, script)
28 #
29 run('rsync', '--archive', f'{input_directory}/', f'{web}/')
30 for directory in [css, js]:
31 os.makedirs(directory)
32 #
33 link_gv = os.path.join(root, 'link.gv')
34 link_svg = os.path.join(gen, 'link.svg')
35 run('dot', link_gv, '-Tsvg', '-o', link_svg)
36 with open(link_svg, 'br') as f:
37 link_text = f.read().decode('u8')
38 page_file = os.path.join(web, 'index.html')
39 page_text = f'''\
40<!DOCTYPE html>
41
42<html>
43
44<head>
45<meta charset="UTF-8" />
46<meta name="viewport" content="initial-scale=1,width=device-width" />
47<link rel="stylesheet" href="{time_id}/{style}/index.css" />
48<script src="{time_id}/{script}/index.js"></script>
49<title>Marc Beninca</title>
50</head>
51
52<body onload="main()">
53
54<main>
55
56<div class="tabs">
57 <input type="radio" onclick="update(id)"
58 name="tab" id="tab/my" />
59 <label for="tab/my">My/</label>
60 <div><div class="tabs">
61<!--
62 <input type="radio" onclick="update(id)"
63 name="tab/my" id="tab/my/about" />
64 <label for="tab/my/about">About</label>
65 <div>{tabs['about']}</div>
66 <input type="radio" onclick="update(id)"
67 name="tab/my" id="tab/my/bio" />
68 <label for="tab/my/bio">Bio</label>
69 <div>{tabs['bio']}</div>
70-->
71 <input type="radio" onclick="update(id)"
72 name="tab/my" id="tab/my/cv" />
73 <label for="tab/my/cv">CV</label>
74 <div>{tabs['cv']}</div>
75 <input type="radio" onclick="update(id)"
76 name="tab/my" id="tab/my/profiles" />
77 <label for="tab/my/profiles">Profiles</label>
78 <div>{tabs['profiles']}</div>
79 <input type="radio" onclick="update(id)"
80 name="tab/my" id="tab/my/projects" />
81 <label for="tab/my/projects">Projects</label>
82 <div><div class="tabs">
83 <input type="radio" onclick="update(id)"
84 name="tab/my/projects" id="tab/my/projects/lsgm" />
85 <label for="tab/my/projects/lsgm">LSGM</label>
86 <div>{tabs['lsgm']}</div>
87 <input type="radio" onclick="update(id)"
88 name="tab/my/projects" id="tab/my/projects/ofsp" />
89 <label for="tab/my/projects/ofsp">OFSP</label>
90 <div>{tabs['ofsp']}</div>
91 </div></div>
92 <input type="radio" onclick="update(id)"
93 name="tab/my" id="tab/my/tasks" />
94 <label for="tab/my/tasks">Tasks</label>
95 <div>{tabs['tasks']}</div>
96 <input type="radio" onclick="update(id)"
97 name="tab/my" id="tab/my/trips" />
98 <label for="tab/my/trips">Trips</label>
99 <div>{tabs['trips']}</div>
100 <input type="radio" onclick="update(id)"
101 name="tab/my" id="tab/my/sub" />
102 <label for="tab/my/sub">…/</label>
103 <div><div class="tabs">
104 <input type="radio" onclick="update(id)"
105 name="tab/my/sub" id="tab/my/sub/id" />
106 <label for="tab/my/sub/id">Identity</label>
107 <div>{tabs['id']}</div>
108 <input type="radio" onclick="update(id)"
109 name="tab/my/sub" id="tab/my/sub/links" />
110 <label for="tab/my/sub/links">Links</label>
111 <div>{tabs['links']}</div>
112 <input type="radio" onclick="update(id)"
113 name="tab/my/sub" id="tab/my/sub/repos" />
114 <label for="tab/my/sub/repos">Repos</label>
115 <div>{tabs['repos']}</div>
116 <input type="radio" onclick="update(id)"
117 name="tab/my/sub" id="tab/my/sub/others" />
118 <label for="tab/my/sub/others">…</label>
119 <div>{tabs['others']}</div>
120 </div></div>
121 </div></div>
122 <input type="radio" onclick="update(id)"
123 name="tab" id="tab/learn" />
124 <label for="tab/learn">Learn</label>
125 <div>{tabs['learn']}</div>
126 <input type="radio" onclick="update(id)"
127 name="tab" id="tab/music" />
128 <label for="tab/music">Music</label>
129 <div>{tabs['music']}</div>
130 <input type="radio" onclick="update(id)"
131 name="tab" id="tab/sites" />
132 <label for="tab/sites">Sites/</label>
133 <div><div class="tabs">
134 <input type="radio" onclick="update(id)"
135 name="tab/sites" id="tab/sites/books" />
136 <label for="tab/sites/books">Books</label>
137 <div>{tabs['books']}</div>
138 <input type="radio" onclick="update(id)"
139 name="tab/sites" id="tab/sites/buy" />
140 <label for="tab/sites/buy">Buy</label>
141 <div>{tabs['buy']}</div>
142 <input type="radio" onclick="update(id)"
143 name="tab/sites" id="tab/sites/social" />
144 <label for="tab/sites/social">Social</label>
145 <div>{tabs['social']}</div>
146 <input type="radio" onclick="update(id)"
147 name="tab/sites" id="tab/sites/software" />
148 <label for="tab/sites/software">Software</label>
149 <div>{tabs['software']}</div>
150 </div></div>
151 <input type="radio" onclick="update(id)"
152 name="tab" id="tab/videos" />
153 <label for="tab/videos">Videos/</label>
154 <div><div class="tabs">
155 <input type="radio" onclick="update(id)"
156 name="tab/videos" id="tab/videos/comments" />
157 <label for="tab/videos/comments">Comments</label>
158 <div>{tabs['comments']}</div>
159 <input type="radio" onclick="update(id)"
160 name="tab/videos" id="tab/videos/health" />
161 <label for="tab/videos/health">Health</label>
162 <div>{tabs['health']}</div>
163 <input type="radio" onclick="update(id)"
164 name="tab/videos" id="tab/videos/vegan" />
165 <label for="tab/videos/vegan">Vegan</label>
166 <div>{tabs['vegan']}</div>
167 </div></div>
168 <input type="radio" onclick="update(id)"
169 name="tab" id="tab/sub" />
170 <label for="tab/sub">…/</label>
171 <div><div class="tabs">
172 <input type="radio" onclick="update(id)"
173 name="tab/sub" id="tab/sub/style" />
174 <label for="tab/sub/style">Style</label>
175 <div>{tabs['style']}</div>
176 <input type="radio" onclick="update(id)"
177 name="tab/sub" id="tab/sub/unsorted" />
178 <label for="tab/sub/unsorted">…</label>
179 <div>{tabs['unsorted']}</div>
180 </div></div>
181</div>
182
183</main>
184
185<footer>
186<hr />
187Last update: {time.strftime('%Y/%m/%d %H:%M:%S')}
188</footer>
189
190</body>
191
192</html>
193'''
194 css_file = os.path.join(css, 'index.css')
195 css_text = f'''\
196@media screen and (max-aspect-ratio: 10/16) {{
197
198body {{
199font-size: 2em;
200}}
201
202}}
203
204body {{
205background: rgb(255,255,255);
206color: rgb(0,0,0);
207font-family: sans;
208margin: .5em;
209}}
210body.dark {{
211background: rgb(0,0,0);
212color: rgb(160,160,160);
213}}
214
215header {{
216background-image: url("../img/debian.jpeg");
217background-position: center;
218background-size: cover;
219display: flex;
220flex-wrap: wrap;
221}}
222
223.tabs {{
224display: flex;
225flex-wrap: wrap;
226}}
227.tabs .tabs {{
228padding: .5em 0 0 0;
229}}
230.tabs > input {{
231display: none;
232}}
233.tabs > input:checked + label + div {{
234display: block;
235}}
236.tabs > label {{
237order: 1;
238}}
239.tabs > div {{
240display: none;
241flex-basis: 100%;
242order: 2;
243}}
244
245.tabs {{
246margin: 0;
247}}
248.tabs > input:checked + label {{
249background: linear-gradient(rgba(128,128,128,1), rgba(128,128,128,0));
250}}
251.tabs > label {{
252background: linear-gradient(rgba(64,64,64,1), rgba(64,64,64,0));
253border-color: rgb(128,128,128);
254border-radius: .5em;
255border-style: solid;
256border-width: 1px 1px 0 1px;
257font-weight: bold;
258margin: 0;
259padding: .5em 1.5em;
260}}
261.tabs > label:hover {{
262background: linear-gradient(rgba(64,64,64,0), rgba(64,64,64,1));
263color: rgb(192,0,0);
264}}
265.tabs > div {{
266margin: 0;
267}}
268
269a {{
270color: rgb(0,192,192);
271text-decoration: none;
272}}
273a:hover {{
274color: rgb(192,0,0);
275}}
276a:visited {{
277color: rgb(0,160,160);
278}}
279
280img {{
281border: 1px solid;
282border-color: rgb(192,192,192);
283border-radius: 1em;
284height: 8em;
285}}
286
287table {{
288empty-cells: hide;
289}}
290th,td {{
291border-radius: .2em;
292}}
293th:hover,td:hover {{
294background: rgb(48,0,0);
295}}
296th {{
297background: rgb(64,64,64);
298color: rgb(128,128,0);
299}}
300td {{
301background: rgb(48,48,48);
302border: 1px solid;
303border-color: rgb(192,192,192);
304text-align: center;
305}}
306
307.cards {{
308display: flex;
309}}
310.card {{
311list-style: none;
312margin: 0 1em;
313text-align: center;
314}}
315.card img {{
316border: none;
317height: 3em;
318}}
319
320.debug {{
321border-color: rgb(255,0,255);
322border-style: solid;
323border-width: 1px;
324}}
325'''
326 js_file = os.path.join(js, 'index.js')
327 js_text = f'''\
328function check(tab) {{
329 const tabs = tab.split('/')
330 let id = 'tab'
331 let element
332 for (tab of tabs) {{
333 id = `${{id}}/${{tab}}`
334 element = document.getElementById(id)
335 if (element) {{
336 element.checked = true
337 }}
338 }}
339}}
340
341function debug() {{
342 for (element of document.getElementsByTagName('*')) {{
343 element.classList.toggle('debug')
344 }}
345}}
346
347function push(tab) {{
348 window.history.pushState(null, null, `?tab=${{tab}}`)
349}}
350
351function update(id) {{
352 const tab = id.split('/').slice(1).join('/')
353 push(tab)
354}}
355
356function main() {{
357 let tab = (new URL(document.location)).searchParams.get('tab')
358 if (tab) {{
359 check(tab)
360 }} else {{
361 tab = 'my/profiles'
362 check(tab)
363 push(tab)
364 }}
365 const dark = document.getElementById('dark')
366 if (dark) {{
367 dark.click()
368 }}
369}}
370
371function swap() {{
372 document.body.classList.toggle('dark')
373}}
374'''
375# {link_text}
376 with open(page_file, 'bw') as f:
377 f.write(page_text.encode('u8'))
378 with open(css_file, 'bw') as f:
379 f.write(css_text.encode('u8'))
380 with open(js_file, 'bw') as f:
381 f.write(js_text.encode('u8'))
382
383
384tabs = {
385#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
386 'about': f'''\
387About…
388''',
389#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
390 'bio': f'''\
391<h1>Marc Beninca</h1>
392Welcome to a recap attempt of my IT life!
393
394<h2>The early days</h2>
395In my primary school days I started to interact with computers at home,
396mainly Amstrad CPC 464 & 6128 machines.<br />
397It was the occasion for me to:
398<ul>
399<li>discover computer architectures</li>
400<li>learn how to program in BASIC</li>
401<li>suffer the noise and speed of tape recorders</li>
402<li>experience the revolution of floppy disks transfer rates</li>
403</ul>
404
405<h2>Diving in</h2>
406Right before beginning high school, I was introduced to the PC platform.<br />
407The “system programming PC bible” was my reference for a long time.<br />
408I had the occasion to develop projects in:
409<ul>
410<li>Pascal (for example my own hexadecimal file editor)</li>
411<li>Assembly (mainly for critical subroutines performance)</li>
412<li>AutoLISP (automation in LISP of AutoCAD elements processing)</li>
413</ul>
414
415To be continued…
416''',
417#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
418 'books': f'''\
419<ul>
420<li><a href="https://www.tradepub.com">TradePub</a></li>
421</ul>
422''',
423#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
424 'buy': f'''\
425<ul>
426<li><a href="https://www.i-comparateur.com">i-comparateur</a></li>
427</ul>
428<hr />
429<ul>
430<li><a href="https://www.amazon.fr">Amazon</a><ul>
431 <li><a href="https://www.amazon.fr/SanDisk-Extreme-externe-000-Mo-mousqueton/dp/B08RX3343D">SanDisk Extreme Pro</a></li>
432</ul></li>
433</ul>
434<hr />
435<ul>
436<li><a href="https://www.gl-inet.com/products">GL-iNet</a></li>
437<li>OVH<ul>
438 <li><a href="https://checkservers.ovh">check servers</a></li>
439 <li><a href="https://eco.ovhcloud.com/fr/#filterValue=kimsufi,so">eco</a></li>
440</ul></li>
441<li><a href="https://www.thinkpenguin.com">ThinkPenguin</a></li>
442<li><a href="https://www.tuxedocomputers.com">Tuxedo Computers</a></li>
443<li><a href="https://vollebak.com/collections">Vollebak</a></li>
444</ul>
445<hr />
446<ul>
447<li><a href="https://www.leboncoin.fr">Le bon coin</a></li>
448</ul>
449''',
450#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
451 'comments': f'''\
452<ul>
453<li><a href="https://www.youtube.com/@LogicallyAnswered/videos">Logically Answered</a></li>
454</ul>
455''',
456#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
457 'cv': f'''\
458<ul class="cards">
459<li class="card"><a href="https://cv.marc.beninca.link/en">
460<img src="img/en.svg" /><br />English</a></li>
461<li class="card"><a href="https://cv.marc.beninca.link/fr">
462<img src="img/fr.svg" /><br />Français</a></li>
463</ul>
464''',
465#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
466 'repos': f'''\
467<ul>
468<li><a href="https://forge.rwx.work/marc.beninca">RWX</a></li>
469</ul><ul>
470<li><a href="https://codeberg.org/marc.beninca">CodeBerg</a></li>
471<li><a href="https://git.disroot.org/marc.beninca">DisRoot</a></li>
472<li><a href="https://git.froggi.es/marc.beninca">Froggies</a></li>
473<li><a href="https://gitdab.com/marc.beninca">GitDab</a></li>
474<li><a href="https://gitnet.fr/marc.beninca">GitNet</a></li>
475<li><a href="https://git.nixnet.services/marc.beninca">NixNet</a></li>
476</ul><hr /><ul>
477<li><a href="https://framagit.org/marc.beninca">FramaGit</a></li>
478<li><a href="https://gitgud.io/marc.beninca">GitGud</a></li>
479<li><a href="https://git.insomnia247.nl/marc.beninca">Insomnia</a></li>
480<li><a href="https://forge.tedomum.net/marc.beninca">TeDomum</a></li>
481</ul><hr /><ul>
482<li><a href="https://git.42l.fr/marc.beninca">42l</a></li>
483<li><a href="https://git.afpy.org/marc.beninca">AFPy</a></li>
484<li><a href="https://forge.chapril.org/marc.beninca">Chapril</a></li>
485<li><a href="https://git.envs.net/marc.beninca">Envs</a></li>
486<li><a href="https://git.fsfe.org/marc.beninca">FSFE</a></li>
487<li><a href="https://git.projectsegfau.lt/marc.beninca">ProjectSegfault</a></li>
488<!--<li><a href="https://silica.codes/marc.beninca">SilicaCodes</a></li>-->
489<li><a href="https://tildegit.org/marc.beninca">TildeGit</a></li>
490</ul>
491
492<hr />
493
494<ul>
495<li><a href="https://notabug.org/marc.beninca">NotABug</a></li>
496<li><a href="https://pagure.io/user/marc-beninca">Pagure</a></li>
497<li><a href="https://rocketgit.com/marc.beninca">RocketGit</a></li>
498<li><a href="https://sr.ht/~marc_beninca/rwx.work/sources">SourceHut</a></li>
499<li><a href="https://git.tilde.institute/mspe">TildeInstitute</a></li>
500</ul>
501
502<hr />
503
504<ul>
505<li>Gforge</li>
506</ul>
507''',
508#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
509 'health': f'''\
510<ul>
511<li><a href="https://www.youtube.com/@chubbyemu/videos">Chubby Emu</a></li>
512<li><a href="https://www.youtube.com/@drekberg/videos">Sten Ekberg</a></li>
513</ul>
514<ul>
515<li><a href="https://www.youtube.com/@KianaDocherty/videos">Kiana Docherty</a></li>
516<li><a href="https://www.youtube.com/@ThomasDeLauerOfficial/videos">Thomas DeLauer</a></li>
517</ul>
518<hr />
519<ul>
520<li><a href="https://www.youtube.com/@ivredevie/videos">Ivre de vie</a></li>
521</ul>
522''',
523#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
524 'id': f'''\
525<table>
526
527<tr><th colspan="2">OpenPGP</th><td colspan="3" rowspan="4"><img src="img/marc.jpeg" /></td></tr>
528<tr><td colspan="2">marc.beninca.link<br><a href="marc.beninca.pgp">binary</a> | <a href="marc.beninca.asc">textual</a></td></tr>
529<tr><td colspan="2"><a href="https://meta.sr.ht/~marc_beninca.pgp">meta.sr.ht</a></td></tr>
530<tr><td colspan="2"><a href="https://keys.openpgp.org/search?q=marc@beninca.link">keys.openpgp.org</a></td></tr>
531
532<tr>
533<th><a href="https://keyoxide.org/08EDA7006234A0EB29A3A8471DBD5EC4BADA5579">KeyOxide<br>PGP</a></th>
534<th><a href="https://keyoxide.org/aspe:keyoxide.org:WUD5YVN52J3RJ6CD4ZCWYL6S54">KeyOxide<br>ASP</a></th>
535<th><a href="https://liberapay.com/marc.beninca">Libera<br>Pay</a></th>
536<th><a href="https://patreon.com/marc_beninca">Patreon</a></th>
537<th><a href="https://tipeee.com/marc-beninca">Tip<br>eee</a></th>
538</tr>
539
540<tr>
541<td colspan="3"><a href="https://mastodon.cloud/@marc_beninca">Mastodon</a></td>
542<td colspan="2"><a href="https://discord.gg/v6p7CtZ4Zh">Discord</a></td>
543</tr><tr>
544<td colspan="3"><a href="https://devs.live/users/marc_beninca">Pleroma</a></td>
545<td><a href="https://vimeo.com/marcbeninca">Vimeo</a></td>
546</tr><tr>
547<td colspan="2"><a href="https://videos.trom.tf/@marc_beninca">PeerTube</a></td>
548<td><a href="https://openstreetmap.org/user/Marc Beninca">OpenStreetMap</a></td>
549<td><a href="https://youtube.com/@marc.beninca">YouTube</a></td>
550</tr><tr>
551<td colspan="2"><a href="https://pixelfed.social/marc.beninca">PixelFed</a></td>
552<td><a href="https://linuxfr.org/users/marc-beninca">LinuxFR</a></td>
553<td><a href="https://instagram.com/marc.beninca">Instagram</a></td>
554</tr><tr>
555<td colspan="2"><a href="https://rant.li/marc-beninca">WriteFreely</a></td>
556</tr><tr>
557<td colspan="2"><a href="https://programming.dev/u/marc_beninca">Lemmy</a></td>
558</tr><tr>
559<td colspan="2"><a href="https://lobste.rs/u/marc_beninca">Lobsters</a></td>
560</tr><tr>
561<td colspan="2"><a href="https://news.ycombinator.com/user?id=marc_beninca">HackerNews</a></td>
562</tr>
563
564<tr><th colspan="2">DNS</th></tr>
565
566<tr><td colspan="2"><a href="https://beninca.link">beninca.link</a></td></tr>
567<tr><td colspan="2">computing.land</td></tr>
568<tr><td colspan="2">marc-beninca.fr</td></tr>
569<tr><td colspan="2"><a href="https://rwx.work">rwx.work</a></td></tr>
570<tr><td colspan="2"><a href="https://tilde.link">tilde.link</a></td></tr>
571
572<tr><th colspan="2">Git / Forgejo</th></tr>
573
574<tr><td colspan="2"><a href="https://forge.rwx.work/marc.beninca">forge.rwx.work</a></td></tr>
575
576</table>
577''',
578#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
579 'learn': f'''\
580<ul>
581<li><a href="https://roadmap.sh">Roadmap</a></li>
582<li><a href="https://teachyourselfcs.com">Teach Yourself Computer Science</a></li>
583</ul>
584<hr />
585<ul>
586<li><a href="https://www.youtube.com/@ExternCode/videos">Extern Code</a></li>
587<li><a href="https://www.youtube.com/@freecodecamp/videos">FreeCodeCamp</a></li>
588<li><a href="https://debian-facile.org/projets/lescahiersdudebutant">Les cahiers du débutant sur Debian</a></li>
589<li><a href="https://www.youtube.com/@SimplicodeOfficial/videos">SimpliCode</a></li>
590</ul>
591<hr />
592<ul>
593<li><a href="https://overthewire.org">Over The Wire</a></li>
594</ul>
595''',
596#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
597 'links': f'''\
598<ul>
599<li><a href="https://marc-beninca.8b.io">8bio</a></li>
600<li><a href="https://allmylinks.com/marc-beninca">AllMyLinks</a></li>
601<li><a href="https://beacons.ai/marc.beninca">Beacons</a></li>
602<li><a href="https://biglink.to/marc_beninca">BigLinkTo</a></li>
603<li><a href="https://bioin.link/marc.beninca">BioInLink</a></li>
604<li><a href="https://marc_beninca.bio.link">Bio.Link</a></li>
605<li><a href="https://biolinky.co/marcbeninca">BioLinky</a></li>
606<li><a href="https://marc_beninca.c8ke.com">C8ke</a></li>
607<li><a href="https://campsite.bio/marc.beninca">CampSite</a></li>
608<li><a href="https://marc-beninca.carrd.co">Carrd</a></li>
609<li><a href="https://direct.me/marc_beninca">DirectMe</a></li>
610<li><a href="https://everlink.tools/marc.beninca">EverLink</a></li>
611<li><a href="https://feedlink.io/marcbeninca">FeedLink</a></li>
612<li><a href="https://flow.page/marc.beninca">FlowPage</a></li>
613<li><a href="https://heylink.me/marc.beninca">HeyLinkMe</a></li>
614<li><a href="https://hy.page/marcbeninca">HyPage</a></li>
615<li><a href="https://ichi.gg/marc.beninca">Ichi.GG</a></li>
616<li><a href="https://linkfly.to/marc-beninca">LinkFly</a></li>
617<li><a href="https://instabio.cc/marc-beninca">LinkInBio</a></li>
618<li><a href="https://linkpluto.com/marc-beninca">LinkPluto</a></li>
619<li><a href="https://linkpop.com/marc-beninca">LinkPop</a></li>
620<li><a href="https://linkr.bio/marc.beninca">Linkr.Bio</a></li>
621<li><a href="https://linksight.me/marc_beninca">LinkSight</a></li>
622<li><a href="https://link.space/@marc_beninca">LinkSpace</a></li>
623<li><a href="https://linkstack.fr/@marc.beninca">LinkStack</a></li>
624<li><a href="https://linktr.ee/marc.beninca">LinkTree</a></li>
625<li><a href="https://lnk.bio/marc.beninca">Lnk.Bio</a></li>
626<li><a href="https://lu.ma/marc.beninca">Luma</a></li>
627<li><a href="https://nano.site/marc.beninca">NanoSite</a></li>
628<li><a href="https://marcbeninca.podia.com">Podia</a></li>
629<li><a href="https://solo.to/marc.beninca">SoloTo</a></li>
630<li><a href="https://marc-beninca.start.page">StartPage</a></li>
631<li><a href="https://tap.bio/marc.beninca">Tap.Bio</a></li>
632<li><a href="https://marc-beninca.taplink.ws">TapLink</a></li>
633<li><a href="https://url.bio/marc_beninca">URL.Bio</a></li>
634<li><a href="https://vu.fr/marc-beninca">Vu.Fr</a></li>
635<li><a href="https://zaap.bio/marc.beninca">Zaap</a></li>
636</ul>
637''',
638#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
639 'music': f'''\
640<ul>
641<li><a href="https://www.youtube.com/watch?v=_ITiwPMUzho">3 AM Coding Session</a></li>
642</ul>
643<hr />
644<ul>
645<li><a href="https://www.youtube.com/@Daniellabjarnhof/videos">Daniella Bjarnhof</a></li>
646<li><a href="https://www.youtube.com/@DJGroove/videos">DJ Groove</a></li>
647<li><a href="https://www.youtube.com/@flavourtrip/videos">Flavour Trip</a></li>
648<li><a href="https://www.youtube.com/@garsimusic/videos">Garsi</a></li>
649<li><a href="https://www.youtube.com/@jabig/videos">Ja Big</a></li>
650<li><a href="https://www.youtube.com/@johnny_m/videos">Johnny M (GR)</a></li>
651<li><a href="https://www.youtube.com/@johnnymgrchannelc3145/videos">Johnny M (GR) (Channel C')</a></li>
652<li><a href="https://www.youtube.com/@djmissmonique/videos">Miss Monique</a></li>
653<li><a href="https://www.youtube.com/@DJRaphaelPPRV/videos">Raphael</a></li>
654</ul>
655<hr />
656<ul>
657<li><a href="https://www.youtube.com/watch?v=lP26UCnoH9s">Coffee Shop Radio</a></li>
658<li><a href="https://www.youtube.com/watch?v=8wHjuR2gagI">Sad songs for sad people</a></li>
659</ul>
660<hr />
661<ul>
662<li><a href="http://radio.garden/visit/pessac">Radio Garden</a></li>
663</ul>
664''',
665#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
666 'profiles': f'''\
667<ul class="cards">
668<li class="card"><a href="https://forge.rwx.work/marc.beninca">
669<img src="img/ForgeJo.svg" /><br />ForgeJo</a></li>
670<li class="card"><a href="https://keyoxide.org/08EDA7006234A0EB29A3A8471DBD5EC4BADA5579">
671<img src="img/KeyOxide.png" /><br />KeyOxide / PGP</a></li>
672<li class="card"><a href="https://keyoxide.org/aspe:keyoxide.org:WUD5YVN52J3RJ6CD4ZCWYL6S54">
673<img src="img/KeyOxide.png" /><br />KeyOxide / ASP</a></li>
674</ul>
675<hr />
676<ul class="cards">
677<li class="card"><a href="https://videos.trom.tf/@marc.beninca">
678<img src="img/PeerTube.png" /><br />PeerTube</a></li>
679<li class="card"><a href="https://pixelfed.social/marc.beninca">
680<img src="img/PixelFed.svg" /><br />PixelFed</a></li>
681</ul>
682<hr />
683<ul class="cards">
684<li class="card"><a href="https://linkedin.com/in/marc-beninca">
685<img src="img/LinkedIn.png" /><br />LinkedIn</a></li>
686<li class="card"><a href="https://youtube.com/@marc.beninca">
687<img src="img/YouTube.png" /><br />YouTube</a></li>
688<li class="card"><a href="https://instagram.com/marc.beninca">
689<img src="img/InstaGram.png" /><br />InstaGram</a></li>
690</ul>
691''',
692#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
693 'lsgm': f'''\
694<h1>Live Scan Grub Menu</h1>
695Setup a whole EFI System Partition:
696<ul>
697<li>generate customized GRUB EFI & BIOS images</li>
698<li>scan data partition(s) for SquashFS image files</li>
699<li>display generated boot menu</li>
700<li>live boot selected kernel, initial ramdisk and file system image</li>
701</ul>
702BASH experimenting:
703<ul>
704<li><a href="https://youtu.be/mx2lhm7qClc">2023/05/04: 1st boot into QCOW storage with encrypted data partition</a></li>
705</ul>
706''',
707 'ofsp': f'''\
708<h1>Operating File System Profile</h1>
709Build from mirror a full operating system bootable file image:
710<ul>
711<li>bootstrap base file system</li>
712<li>configure common core basics</li>
713<li>install TUI packages</li>
714<li>install GUI packages</li>
715<li>copy kernel and initial ramdisk for bootloader</li>
716<li>archive final file system as SquashFS file</li>
717</ul>
718BASH experimenting:
719<ul>
720<li><a href="https://youtu.be/phArr81weUw">2023/07/27: 3rd party repos, repoless .deb, tarball archives</a></li>
721</ul>
722<hr />
723<ul>
724<li><a href="https://youtu.be/5Isf02MKFgM">2023/07/20: both TUI and GUI images</a></li>
725<li><a href="https://youtu.be/YhoY2gisXg4">2023/06/20: Graphical User Interface too</a></li>
726<li><a href="https://youtu.be/PSHswxc9oU8">2023/06/13: Textual User Interface only</a></li>
727</ul>
728''',
729#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
730 'social': f'''\
731<ul>
732<li><a href="https://calckey.org">Calckey</a></li>
733<li><a href="https://joinmastodon.org">Mastodon</a></li>
734<li><a href="https://pixelfed.org">Pixelfed</a></li>
735</ul>
736<hr />
737<ul>
738<li><a href="https://bsky.app">Bluesky</a></li>
739<li><a href="https://revolt.chat">Revolt</a></li>
740</ul>
741<hr />
742<ul>
743<li><a href="https://discord.com">Discord</a></li>
744<li><a href="https://www.facebook.com">FaceBook</a></li>
745<li><a href="https://www.instagram.com">InstaGram</a></li>
746<li><a href="https://www.linkedin.com">LinkedIn</a></li>
747<li><a href="https://www.reddit.com">Reddit</a></li>
748<li><a href="https://www.tiktok.com">TikTok</a></li>
749<li><a href="https://www.twitch.tv">Twitch</a></li>
750<li><a href="https://twitter.com">Twitter</a></li>
751<li><a href="https://www.youtube.com">YouTube</a></li>
752</ul>
753<hr />
754<ul>
755<li><a href="https://nextdoor.com">NextDoor</a></li>
756</ul>
757''',
758#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
759 'software': f'''\
760<ul>
761<li><a href="https://developer.puri.sm/Librem5">Librem 5</a></li>
762</ul>
763<hr />
764<ul>
765<li><a href="https://docs.zfsbootmenu.org">ZFS Boot Menu</a></li>
766</ul>
767<hr />
768<ul>
769<li><a href="https://carbon.sh">CarbonOS</a></li>
770<li><a href="https://fedoraproject.org/silverblue">Fedora Silverblue</a></li>
771<li><a href="https://www.flatcar.org">Flatcar Container Linux</a></li>
772<li><a href="https://nixos.org">NixOS</a></li>
773<li><a href="https://guix.gnu.org">Guix</a></li>
774<li><a href="https://microos.opensuse.org">OpenSUSE MicroOS</a></li>
775<li><a href="https://vanillaos.org">Vanilla OS</a></li>
776<li><a href="https://github.com/bottlerocket-os/bottlerocket">Bottlerocket OS</a></li>
777<li><a href="https://blendos.co">BlendOS</a></li>
778<li><a href="https://www.talos.dev">Talos Linux</a></li>
779<li><a href="https://www.endlessos.org/os">Endless OS</a></li>
780</ul>
781''',
782#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
783 'style': f'''\
784<ul>
785<li><input type="checkbox" id="dark" onclick="swap()">Dark</input></li>
786<li><input type="checkbox" id="debug" onclick="debug()">Debug</input></li>
787</ul>
788''',
789#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
790 'tasks': f'''\
791<ul>
792<li>categorize music</li>
793</ul>
794<ul>
795<li>fix dark switch</li>
796<li>style list items</li>
797<li>switch fonts</li>
798<li>use CSS variables</li>
799</ul>
800<ul>
801<li>implement search</li>
802</ul>
803''',
804#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
805 'trips': f'''\
806<ul>
807<li>Belgium<ul>
808 <li>Brussels</li>
809</ul></li>
810<li>Estonia<ul>
811 <li>Lahemaa</li>
812 <li>Narva</li>
813 <li>Pärnu</li>
814 <li>Saaremaa</li>
815 <li>Tallinn</li>
816 <li>Tartu</li>
817</ul></li>
818<li>Germany<ul>
819 <li>Berlin</li>
820</ul></li>
821<li>Japan<ul>
822 <li>Hiroshima</li>
823 <li>Kyoto</li>
824 <li>Okinawa</li>
825 <li>Osaka</li>
826 <li>Tokyo</li>
827</ul></li>
828<li>Netherlands<ul>
829 <li>Amsterdam</li>
830 <li>Rotterdam</li>
831</ul></li>
832<li>Switzerland</li>
833<li>United States of America<ul>
834 <li>California<ul>
835 <li>Los Angeles</li>
836 <li>San Francisco</li>
837 </ul></li>
838 <li>Georgia</li>
839 <li>Hawaii</li>
840 <li>Indiana</li>
841 <li>Kentucky</li>
842 <li>New York</li>
843 <li>Ohio</li>
844 <li>Tennessee</li>
845</ul></li>
846</ul>
847''',
848#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
849 'vegan': f'''\
850<ul>
851<li><a href="https://www.youtube.com/@CheapLazyVegan/videos">Cheap Lazy Vegan</a></li>
852<li><a href="https://www.youtube.com/@gazoakleychef/videos">Gaz Oakley</a></li>
853<li><a href="https://www.youtube.com/@MerleONeal/videos">Merle O'Neal</a></li>
854<li><a href="https://www.youtube.com/@peacefulcuisine/videos">Peaceful Cuisine</a></li>
855<li><a href="https://www.youtube.com/@PickUpLimes/videos">Pick Up Limes</a></li>
856<li><a href="https://www.youtube.com/@RainbowPlantLife/videos">Rainbow Plant Life</a></li>
857<li><a href="https://www.youtube.com/@TheKoreanVegan/videos">The Korean Vegan</a></li>
858<li><a href="https://www.youtube.com/@Thevietvegan/videos">The Viet Vegan</a></li>
859<li><a href="https://www.youtube.com/@YEUNGMANCOOKING/videos">Yeung Man Cooking</a></li>
860</ul>
861<ul>
862<li><a href="https://www.youtube.com/@SauceStache/videos">Sauce Stache</a></li>
863<li><a href="https://www.youtube.com/@TheeBurgerDude/videos">Thee Burger Dude</a></li>
864</ul>
865<hr />
866<ul>
867<li><a href="https://www.youtube.com/@doucefrugalite/videos">Douce frugalité</a></li>
868<li><a href="https://www.youtube.com/@Evalespetitsplats/videos">Eva les petits plats</a></li>
869<li><a href="https://www.youtube.com/@LapetiteOkara/videos">La petite Okara</a></li>
870<li><a href="https://www.youtube.com/@MarieSweetandSour/videos">Marie Sweet and Sour</a></li>
871<li><a href="https://www.youtube.com/@SebastienKardinal/videos">Sébastien Kardinal</a></li>
872</ul>
873''',
874#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
875 'others': f'''\
876<ul>
877<li><a href="https://bsky.app/profile/marc-beninca.bsky.social">Blue Sky</a></li>
878<li><a href="https://buymeacoffee.com/marc.beninca">Buy Me A Coffee</a></li>
879<li><a href="https://cohost.org/marc-beninca">CoHost</a></li>
880<li><a href="https://dev.to/marc_beninca">Dev</a></li>
881<li><a href="https://diaspora-fr.org/people/00f4f8e04b11013b46b20025900e4586">Diaspora</a></li>
882<li><a href="https://hashnode.com/@marc-beninca">HashNode</a></li>
883<li><a href="https://medium.com/@marc-beninca">Medium</a></li>
884<li><a href="https://marc-beninca.micro.blog/about">MicroBlog</a></li>
885<li><a href="https://minds.com/marc_beninca">Minds</a></li>
886<li><a href="https://odysee.com/@marc.beninca">Odysee</a></li>
887<li><a href="https://paypal.me/MarcBeninca">PayPal</a></li>
888<li><a href="https://pinterest.com/marc_beninca">Pinterest</a></li>
889<li><a href="https://marcbeninca.podia.com">Podia</a></li>
890<li><a href="https://app.revolt.chat/invite/01FREKCG3P2P0YMAHQSCPSW4GD">Revolt</a></li>
891<li><a href="https://tiktok.com/@marc.beninca">TikTok</a></li>
892<li><a href="https://twtxt.net/user/marc_beninca">YarnSocial</a></li>
893</ul>
894<hr />
895<ul>
896<li><a href="https://dlive.tv/marc.beninca">Dlive</a></li>
897<li><a href="https://kick.com/marc_beninca">Kick</a></li>
898</ul>
899<hr />
900<ul>
901<li><a href="">KissBank</a></li>
902</ul>
903''',
904#⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅
905 'unsorted': f'''\
906<ul>
907<li><a href="https://foss.events">FOSS Events</a></li>
908</ul>
909''',
910}
911
912
913if __name__ == '__main__':
914 main()