diff options
| author | soaos <soaos@soaos.dev> | 2025-11-21 21:14:12 -0500 |
|---|---|---|
| committer | soaos <soaos@soaos.dev> | 2025-11-21 21:14:12 -0500 |
| commit | ba76e77d935998e4b128053dcc61d2ed4884cdda (patch) | |
| tree | 5464dccd475404b509f048f4525193a8ff36a715 | |
| parent | 6e3a7252608197b6571a56c9b07be09f254e8bae (diff) | |
zola migration
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .kateproject.build | 23 | ||||
| -rw-r--r-- | .radicle/native.yaml | 2 | ||||
| -rw-r--r-- | assets/UnifontExMono.woff | bin | 3345136 -> 0 bytes | |||
| -rw-r--r-- | assets/badges/html.gif | bin | 8500 -> 0 bytes | |||
| -rw-r--r-- | assets/badges/javascript.gif | bin | 1694 -> 0 bytes | |||
| -rw-r--r-- | assets/badges/neovim.gif | bin | 695 -> 0 bytes | |||
| -rw-r--r-- | assets/unifont.woff2 | bin | 1605860 -> 0 bytes | |||
| -rw-r--r-- | assets/unifont_upper.woff2 | bin | 283288 -> 0 bytes | |||
| -rw-r--r-- | blog/rockbox_stats/index.html | 560 | ||||
| -rw-r--r-- | blog/terminal_renderer_mkii/index.html | 189 | ||||
| -rw-r--r-- | config.toml | 18 | ||||
| -rw-r--r-- | content/_index.md | 39 | ||||
| -rw-r--r-- | content/blog/_index.md | 6 | ||||
| -rw-r--r-- | content/blog/blog.css (renamed from blog/blog.css) | 0 | ||||
| -rw-r--r-- | content/blog/rockbox_stats/index.md | 547 | ||||
| -rw-r--r-- | content/blog/rockbox_stats/log-setting.bmp (renamed from blog/rockbox_stats/log-setting.bmp) | bin | 153666 -> 153666 bytes | |||
| -rw-r--r-- | content/blog/rockbox_stats/playback-settings.bmp (renamed from blog/rockbox_stats/playback-settings.bmp) | bin | 153666 -> 153666 bytes | |||
| -rw-r--r-- | content/blog/rockbox_stats/player.bmp (renamed from blog/rockbox_stats/player.bmp) | bin | 153666 -> 153666 bytes | |||
| -rw-r--r-- | content/blog/terminal_renderer_mkii/cover.png (renamed from blog/terminal_renderer_mkii/cover.png) | bin | 6038269 -> 6038269 bytes | |||
| -rw-r--r-- | content/blog/terminal_renderer_mkii/david.png (renamed from blog/terminal_renderer_mkii/david.png) | bin | 27218 -> 27218 bytes | |||
| -rw-r--r-- | content/blog/terminal_renderer_mkii/davidbayer.png (renamed from blog/terminal_renderer_mkii/davidbayer.png) | bin | 4213 -> 4213 bytes | |||
| -rw-r--r-- | content/blog/terminal_renderer_mkii/davidthreshold.png (renamed from blog/terminal_renderer_mkii/davidthreshold.png) | bin | 3324 -> 3324 bytes | |||
| -rw-r--r-- | content/blog/terminal_renderer_mkii/index.md | 167 | ||||
| -rw-r--r-- | content/heaven/_index.md | 4 | ||||
| -rw-r--r-- | content/heaven/angel.gif (renamed from heaven/angel.gif) | bin | 6866 -> 6866 bytes | |||
| -rw-r--r-- | content/heaven/angel2.gif (renamed from heaven/angel2.gif) | bin | 89484 -> 89484 bytes | |||
| -rw-r--r-- | content/heaven/angel3.gif (renamed from heaven/angel3.gif) | bin | 8610 -> 8610 bytes | |||
| -rw-r--r-- | content/heaven/bg.jpg (renamed from heaven/bg.jpg) | bin | 53321 -> 53321 bytes | |||
| -rw-r--r-- | content/heaven/everytime_we_touch_nightcore.ogg (renamed from heaven/everytime_we_touch_nightcore.ogg) | bin | 1946392 -> 1946392 bytes | |||
| -rw-r--r-- | content/heaven/heaven.css (renamed from heaven/heaven.css) | 0 | ||||
| -rw-r--r-- | content/hell/Flying_Skeleton_Hell.gif (renamed from hell/Flying_Skeleton_Hell.gif) | bin | 2004165 -> 2004165 bytes | |||
| -rw-r--r-- | content/hell/_index.md | 70 | ||||
| -rw-r--r-- | content/hell/bg.jpg (renamed from hell/bg.jpg) | bin | 48691 -> 48691 bytes | |||
| -rw-r--r-- | content/hell/bigguy.gif (renamed from hell/bigguy.gif) | bin | 11228 -> 11228 bytes | |||
| -rw-r--r-- | content/hell/comunismo.gif (renamed from hell/comunismo.gif) | bin | 105570 -> 105570 bytes | |||
| -rw-r--r-- | content/hell/demon.gif (renamed from hell/demon.gif) | bin | 8734 -> 8734 bytes | |||
| -rw-r--r-- | content/hell/demon2.gif (renamed from hell/demon2.gif) | bin | 37169 -> 37169 bytes | |||
| -rw-r--r-- | content/hell/demon3.gif (renamed from hell/demon3.gif) | bin | 27765 -> 27765 bytes | |||
| -rw-r--r-- | content/hell/demon4.gif (renamed from hell/demon4.gif) | bin | 29208 -> 29208 bytes | |||
| -rw-r--r-- | content/hell/demon_face.gif (renamed from hell/demon_face.gif) | bin | 22848 -> 22848 bytes | |||
| -rw-r--r-- | content/hell/evilmind.gif (renamed from hell/evilmind.gif) | bin | 101192 -> 101192 bytes | |||
| -rw-r--r-- | content/hell/evilorb.gif (renamed from hell/evilorb.gif) | bin | 6287 -> 6287 bytes | |||
| -rw-r--r-- | content/hell/firebreak.gif (renamed from hell/firebreak.gif) | bin | 40755 -> 40755 bytes | |||
| -rw-r--r-- | content/hell/gay.gif (renamed from hell/gay.gif) | bin | 41784 -> 41784 bytes | |||
| -rw-r--r-- | content/hell/gay2.gif (renamed from hell/gay2.gif) | bin | 5601 -> 5601 bytes | |||
| -rw-r--r-- | content/hell/gaydudes.gif (renamed from hell/gaydudes.gif) | bin | 101953 -> 101953 bytes | |||
| -rw-r--r-- | content/hell/hell.ogg (renamed from hell/hell.ogg) | bin | 924991 -> 924991 bytes | |||
| -rw-r--r-- | content/hell/hellisreal.gif (renamed from hell/hellisreal.gif) | bin | 59555 -> 59555 bytes | |||
| -rw-r--r-- | content/hell/hitler.gif (renamed from hell/hitler.gif) | bin | 46349 -> 46349 bytes | |||
| -rw-r--r-- | content/hell/hitler2.gif (renamed from hell/hitler2.gif) | bin | 45803 -> 45803 bytes | |||
| -rw-r--r-- | content/hell/hitler3.gif (renamed from hell/hitler3.gif) | bin | 33592 -> 33592 bytes | |||
| -rw-r--r-- | content/hell/hot.gif (renamed from hell/hot.gif) | bin | 4071 -> 4071 bytes | |||
| -rw-r--r-- | content/hell/kissing.jpg (renamed from hell/kissing.jpg) | bin | 14011 -> 14011 bytes | |||
| -rw-r--r-- | content/hell/obama.gif (renamed from hell/obama.gif) | bin | 1532992 -> 1532992 bytes | |||
| -rw-r--r-- | content/hell/pitchfork.gif (renamed from hell/pitchfork.gif) | bin | 16727 -> 16727 bytes | |||
| -rw-r--r-- | content/hell/redfire.gif (renamed from hell/redfire.gif) | bin | 21997 -> 21997 bytes | |||
| -rw-r--r-- | content/hell/skull.gif (renamed from hell/skull.gif) | bin | 13021 -> 13021 bytes | |||
| -rw-r--r-- | content/hell/smallfire.gif (renamed from hell/smallfire.gif) | bin | 5698 -> 5698 bytes | |||
| -rw-r--r-- | content/hell/torch.gif (renamed from hell/torch.gif) | bin | 7960 -> 7960 bytes | |||
| -rw-r--r-- | content/projects/bevy_plugins/index.html (renamed from projects/bevy_plugins/index.html) | 0 | ||||
| -rw-r--r-- | content/projects/games/NIX_AVREA/index.html (renamed from projects/games/NIX_AVREA/index.html) | 0 | ||||
| -rw-r--r-- | content/projects/piss_daemon/index.html (renamed from projects/piss_daemon/index.html) | 0 | ||||
| -rw-r--r-- | content/projects/piss_daemon/statusbar.png (renamed from projects/piss_daemon/statusbar.png) | bin | 4546 -> 4546 bytes | |||
| -rw-r--r-- | content/projects/project.css (renamed from projects/project.css) | 0 | ||||
| -rw-r--r-- | content/rockstats/index.html (renamed from rockstats/index.html) | 0 | ||||
| -rw-r--r-- | content/things_i_like/music/867.png (renamed from things_i_like/music/867.png) | bin | 88542 -> 88542 bytes | |||
| -rw-r--r-- | content/things_i_like/music/act_ii.jpg (renamed from things_i_like/music/act_ii.jpg) | bin | 33123 -> 33123 bytes | |||
| -rw-r--r-- | content/things_i_like/music/apollo.jpg (renamed from things_i_like/music/apollo.jpg) | bin | 5380 -> 5380 bytes | |||
| -rw-r--r-- | content/things_i_like/music/atebts.jpg (renamed from things_i_like/music/atebts.jpg) | bin | 12938 -> 12938 bytes | |||
| -rw-r--r-- | content/things_i_like/music/departure_songs.jpg (renamed from things_i_like/music/departure_songs.jpg) | bin | 49939 -> 49939 bytes | |||
| -rw-r--r-- | content/things_i_like/music/index.html (renamed from things_i_like/music/index.html) | 0 | ||||
| -rw-r--r-- | content/things_i_like/music/jiminy.jpg (renamed from things_i_like/music/jiminy.jpg) | bin | 135977 -> 135977 bytes | |||
| -rw-r--r-- | content/things_i_like/music/lysf.jpg (renamed from things_i_like/music/lysf.jpg) | bin | 86650 -> 86650 bytes | |||
| -rw-r--r-- | content/things_i_like/music/twin_fantasy.jpg (renamed from things_i_like/music/twin_fantasy.jpg) | bin | 20076 -> 20076 bytes | |||
| -rw-r--r-- | content/things_i_like/music/wetdream.png (renamed from things_i_like/music/wetdream.png) | bin | 72338 -> 72338 bytes | |||
| -rw-r--r-- | hell/hell.css | 66 | ||||
| -rw-r--r-- | hell/index.html | 75 | ||||
| -rw-r--r-- | index.html | 105 | ||||
| -rw-r--r-- | static/98.css | 1040 | ||||
| -rw-r--r-- | static/assets/UnifontExMono.woff2 (renamed from assets/UnifontExMono.woff2) | bin | 2002128 -> 2002128 bytes | |||
| -rw-r--r-- | static/assets/badges/cookies.png | bin | 0 -> 515 bytes | |||
| -rw-r--r-- | static/assets/badges/go2hell.gif (renamed from assets/badges/go2hell.gif) | bin | 1577 -> 1577 bytes | |||
| -rw-r--r-- | static/assets/badges/indieweb.png | bin | 0 -> 524 bytes | |||
| -rw-r--r-- | static/assets/badges/javascript.png | bin | 0 -> 412 bytes | |||
| -rw-r--r-- | static/assets/badges/lynx.gif (renamed from assets/badges/lynx.gif) | bin | 3303 -> 3303 bytes | |||
| -rw-r--r-- | static/assets/badges/midi_files_now.gif (renamed from assets/badges/midi_files_now.gif) | bin | 1395 -> 1395 bytes | |||
| -rw-r--r-- | static/assets/badges/powered-by-debian.gif (renamed from assets/badges/powered-by-debian.gif) | bin | 904 -> 904 bytes | |||
| -rw-r--r-- | static/assets/badges/soaos.png | bin | 0 -> 2563 bytes | |||
| -rw-r--r-- | static/assets/bg.jpg (renamed from assets/bg.jpg) | bin | 31580 -> 31580 bytes | |||
| -rw-r--r-- | static/assets/construction.gif (renamed from assets/construction.gif) | bin | 9933 -> 9933 bytes | |||
| -rw-r--r-- | static/assets/icon/button-down-active.svg | 5 | ||||
| -rw-r--r-- | static/assets/icon/button-down.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/button-left.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/button-right.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/button-up.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/checkmark-disabled.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/checkmark.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/close.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/groupbox-border.svg | 4 | ||||
| -rw-r--r-- | static/assets/icon/help.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/indicator-horizontal.svg | 6 | ||||
| -rw-r--r-- | static/assets/icon/indicator-rectangle-horizontal.svg | 6 | ||||
| -rw-r--r-- | static/assets/icon/maximize-disabled.svg | 4 | ||||
| -rw-r--r-- | static/assets/icon/maximize.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/minimize.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/radio-border-disabled.svg | 7 | ||||
| -rw-r--r-- | static/assets/icon/radio-border.svg | 8 | ||||
| -rw-r--r-- | static/assets/icon/radio-dot-disabled.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/radio-dot.svg | 3 | ||||
| -rw-r--r-- | static/assets/icon/restore.svg | 10 | ||||
| -rw-r--r-- | static/assets/icon/scrollbar-background.svg | 4 | ||||
| -rw-r--r-- | static/assets/icon/sunken-panel-border.svg | 11 | ||||
| -rw-r--r-- | static/favicon.png (renamed from favicon.png) | bin | 4287 -> 4287 bytes | |||
| -rw-r--r-- | static/hell.css | 102 | ||||
| -rw-r--r-- | static/index.css (renamed from index.css) | 0 | ||||
| -rw-r--r-- | static/style.css | 184 | ||||
| -rw-r--r-- | static/style.css.bak | 224 | ||||
| -rw-r--r-- | style.css | 215 | ||||
| -rw-r--r-- | templates/base.html | 95 | ||||
| -rw-r--r-- | templates/blog.html | 38 | ||||
| -rw-r--r-- | templates/heaven.html (renamed from heaven/index.html) | 10 | ||||
| -rw-r--r-- | templates/hell.html | 22 | ||||
| -rw-r--r-- | templates/index.html | 2 | ||||
| -rw-r--r-- | templates/macros.html | 3 | ||||
| -rw-r--r-- | templates/page.html | 2 | ||||
| -rw-r--r-- | templates/post.html | 19 | ||||
| -rw-r--r-- | templates/section.html | 2 | ||||
| -rw-r--r-- | templates/shortcodes/soaosed.html | 3 | ||||
| -rw-r--r-- | templates/shortcodes/subtree.html | 8 | ||||
| -rw-r--r-- | templates/shortcodes/title_bar.html | 8 | ||||
| -rw-r--r-- | templates/shortcodes/tree_view.html | 5 | ||||
| -rw-r--r-- | templates/shortcodes/treelink.html | 8 | ||||
| -rw-r--r-- | templates/shortcodes/window.html | 3 | ||||
| -rw-r--r-- | templates/shortcodes/window_body.html | 1 |
135 files changed, 2775 insertions, 1217 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d70ebaa --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +public
\ No newline at end of file diff --git a/.kateproject.build b/.kateproject.build new file mode 100644 index 0000000..ac117bf --- /dev/null +++ b/.kateproject.build @@ -0,0 +1,23 @@ +{ + "Auto_generated": "This file is auto-generated. Any extra tags or formatting will be lost", + "target_sets": [ + { + "cmake_config": "", + "directory": "/home/soaos/repo/soaos.dev", + "loaded_via_cmake": false, + "name": "Target Set", + "targets": [ + { + "build_cmd": "cd %w && zola build", + "name": "Build", + "run_cmd": "" + }, + { + "build_cmd": "", + "name": "Run Live Server", + "run_cmd": "cd %w && zola serve" + } + ] + } + ] +} diff --git a/.radicle/native.yaml b/.radicle/native.yaml index 2b826b5..6ff3eb6 100644 --- a/.radicle/native.yaml +++ b/.radicle/native.yaml @@ -1 +1 @@ -shell: rm -rf /var/www/soaos.dev && mkdir /var/www/soaos.dev && cp * -r /var/www/soaos.dev +shell: rm -rf /var/www/soaos.dev && mkdir /var/www/soaos.dev && zola build && cp public/* -r /var/www/soaos.dev diff --git a/assets/UnifontExMono.woff b/assets/UnifontExMono.woff Binary files differdeleted file mode 100644 index 89bc299..0000000 --- a/assets/UnifontExMono.woff +++ /dev/null diff --git a/assets/badges/html.gif b/assets/badges/html.gif Binary files differdeleted file mode 100644 index 106af2d..0000000 --- a/assets/badges/html.gif +++ /dev/null diff --git a/assets/badges/javascript.gif b/assets/badges/javascript.gif Binary files differdeleted file mode 100644 index 7937f54..0000000 --- a/assets/badges/javascript.gif +++ /dev/null diff --git a/assets/badges/neovim.gif b/assets/badges/neovim.gif Binary files differdeleted file mode 100644 index c3670e2..0000000 --- a/assets/badges/neovim.gif +++ /dev/null diff --git a/assets/unifont.woff2 b/assets/unifont.woff2 Binary files differdeleted file mode 100644 index d6e201a..0000000 --- a/assets/unifont.woff2 +++ /dev/null diff --git a/assets/unifont_upper.woff2 b/assets/unifont_upper.woff2 Binary files differdeleted file mode 100644 index 47be929..0000000 --- a/assets/unifont_upper.woff2 +++ /dev/null diff --git a/blog/rockbox_stats/index.html b/blog/rockbox_stats/index.html deleted file mode 100644 index 4aca821..0000000 --- a/blog/rockbox_stats/index.html +++ /dev/null @@ -1,560 +0,0 @@ -<!DOCTYPE html> - -<html> - -<head> - <title></title> - <link rel="stylesheet" href="/style.css"> - <link rel="stylesheet" href="/blog/blog.css"> -</head> - -<body> - <div class="text-section"> - <a href="..">↰ Back</a> - <a href="/">⌂ Home</a> - </div> - <article> - <section> - <div class="text-section"> - <!-- Header Section --> - <h1>Rockbox Stat Tracking</h1> - <p>September 22, 2025</p> - <p>In this post I talk about how I went about setting up a <a href="/rockstats" target="_blank">stat visualization page</a> for my rockbox mp3 player.</p> - </div> - <figure class="cover-image"> -<svg width="100%" height="360" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" viewBox="0 0 640 360"> -<rect width="640" height="360" x="0" y="0" fill="rgb(0,0,0)" fill-opacity="0"></rect> -<polyline points="427.2 233.9 440.6 240.7 455.6 240.7" fill="transparent" stroke="#e67e80" class="zr0-cls-0"></polyline> -<polyline points="202.3 203.4 192.6 206.3 177.6 206.3" fill="transparent" stroke="#e69875" class="zr0-cls-0"></polyline> -<polyline points="220 113.6 197.5 135.5 182.5 135.5" fill="transparent" stroke="#dbbc7f" class="zr0-cls-0"></polyline> -<polyline points="273.2 69.5 190.3 117.4 190.3 117.4" fill="transparent" stroke="#a7c080" class="zr0-cls-0"></polyline> -<polyline points="304.6 61 201.7 99.3 201.7 99.3" fill="transparent" stroke="#83c092" class="zr0-cls-0"></polyline> -<polyline points="310.7 60.4 217.9 81.2 217.9 81.2" fill="transparent" stroke="#7fbbb3" class="zr0-cls-0"></polyline> -<polyline points="315.4 60.1 242.2 63.1 242.2 63.1" fill="transparent" stroke="#d699b6" class="zr0-cls-0"></polyline> -<polyline points="318.6 60 318.4 45 303.4 45" fill="transparent" stroke="#f85552" class="zr0-cls-0"></polyline> -<path d="M320 60A120 120 0 1 1 223.6 251.5L320 180Z" fill="#e67e80" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="0" ecmeta_ssr_type="chart" class="zr0-cls-1"></path> -<path d="M223.6 251.5A120 120 0 0 1 203.6 150.7L320 180Z" fill="#e69875" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="1" ecmeta_ssr_type="chart" class="zr0-cls-2"></path> -<path d="M203.6 150.7A120 120 0 0 1 247.9 84.1L320 180Z" fill="#dbbc7f" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="2" ecmeta_ssr_type="chart" class="zr0-cls-3"></path> -<path d="M247.9 84.1A120 120 0 0 1 301.3 61.5L320 180Z" fill="#a7c080" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="3" ecmeta_ssr_type="chart" class="zr0-cls-4"></path> -<path d="M301.3 61.5A120 120 0 0 1 307.8 60.6L320 180Z" fill="#83c092" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="4" ecmeta_ssr_type="chart" class="zr0-cls-5"></path> -<path d="M307.8 60.6A120 120 0 0 1 313.6 60.2L320 180Z" fill="#7fbbb3" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="5" ecmeta_ssr_type="chart" class="zr0-cls-6"></path> -<path d="M313.6 60.2A120 120 0 0 1 317.2 60L320 180Z" fill="#d699b6" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="6" ecmeta_ssr_type="chart" class="zr0-cls-7"></path> -<path d="M317.2 60A120 120 0 0 1 320 60L320 180Z" fill="#f85552" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="7" ecmeta_ssr_type="chart" class="zr0-cls-8"></path> -<text dominant-baseline="central" text-anchor="start" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(460.5907 240.6868)" fill="var(--fg)">Progressive Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(172.5853 206.2935)" fill="var(--fg)">Alternative</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(177.5244 135.5094)" fill="var(--fg)">Post-Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(185.3496 117.4094)" fill="var(--fg)">Post-Hardcore</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(196.7011 99.3094)" fill="var(--fg)">Post-Metal</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(212.8733 81.2094)" fill="var(--fg)">Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(237.2337 63.1094)" fill="var(--fg)">Shoegaze</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(298.4082 45.0094)" fill="var(--fg)">Progressive Metal</text> -<style> -.zr0-cls-0:hover { -cursor:pointer; -} -.zr0-cls-1:hover { -cursor:pointer; -fill:rgba(253,138,140,1); -} -.zr0-cls-2:hover { -cursor:pointer; -fill:rgba(253,167,128,1); -} -.zr0-cls-3:hover { -cursor:pointer; -fill:rgba(240,206,139,1); -} -.zr0-cls-4:hover { -cursor:pointer; -fill:rgba(183,211,140,1); -} -.zr0-cls-5:hover { -cursor:pointer; -fill:rgba(144,211,160,1); -} -.zr0-cls-6:hover { -cursor:pointer; -fill:rgba(139,205,196,1); -} -.zr0-cls-7:hover { -cursor:pointer; -fill:rgba(235,168,200,1); -} -.zr0-cls-8:hover { -cursor:pointer; -fill:rgba(255,93,90,1); -} - - - -</style> -</svg> - <figcaption>A static site generation experiment</figcaption> - </figure> - </section> - <section class="text-section"> - <h2>Preamble: Digital Sovereignity & Rockbox</h2> - <p> - I've been building up a pretty sizeable collection of digital music - over the last couple of years. I think there's a lot of value in owning - the music I pay for and being able to choose how I listen to it. - Purchasing music also allows me to support artists in a more direct - and substantial way than the fractions of cents for using streaming services, - but that's more of a happy consequence than some moral obligation I feel. - </p> - <p> - Over the years, I've enjoyed listening to my music in a variety of ways. - For years I kept all of my music files on all of my devices and used - various local music clients depending on the platform, most notably mpd - and ncmpcpp on linux. Eventually, as I charged headlong into the glorious - world of self-hosting, I began using a central Jellyfin media server that - I stream music and video from. It's super convenient, and works on all of - my devices (including my TV!). - </p> - <p> - My media server is great, and it's been the primary way I listen to music - for a while now. But it has limitations. For example, I don't expose my media - server to the internet, so I'm unable to stream from it while I'm out and - about. And even if I could, the bandwidth requirements would be pretty high. - I figured I would need a dedicated music player if I wanted to take my music - library on the go, and settled on the HIFI Walker H2 after reading some - online recommendations. The ability to install <a href="https://rockbox.org" target="_blank">Rockbox</a>, an open-source firmware, - was a big factor in my decision. I couldn't tell you how the device works - out of the box, since I flashed the firmware pretty much immediately once I got it, - but I've been super impressed with how the device works while running Rockbox. - </p> - <p> - <figure class="fig fig-right"> - <img src="player.bmp" alt="Screenshot of Rockbox player showing cool theme."> - <figcaption>I'm using a modified version of the <a - href="https://themes.rockbox.org/index.php?themeid=3266&target=aigoerosq" - target="_blank">InfoMatrix-v2</a> theme, which looks great.</figcaption> - </figure> - Rockbox comes with many codecs for common audio formats including FLAC and MP3. The - device boots extremely quickly, and the interface is snappy. Virtually every aspect - of the user experience is tweakable and customizable to a crazy degree. I've even begun - listening to music on my player even at home, since a device specifically for the - purpose provides less distraction while I'm trying to be productive. - </p> - <p> - All this to say I'm pretty much sold on Rockbox. But there's certain things I - still miss from my days of being a user of popular services like Spotify with - fancy APIs and data tracking. Things like Spotify wrapped or third-party apps - for visualizing playback statistics are a fun way to see what my listening history - looks like and could potentially be used to help find more music that I'd enjoy. - This is why when I noticed that Rockbock has a playback logging feature, a little - lightbulb lit up over my head. - </p> - </section> - <section class="text-section"> - <h2>Generating and Parsing Logs</h2> - <p> - <figure class="fig fig-right"> - <img src="log-setting.bmp" alt="Logging"> - <figcaption>The logging feature can be accessed through the settings menu.</figcaption> - </figure> - Rockbox has a feature that logs playback information to a text file. This feature can - be enabled by setting <b>Playback Settings > Logging</b> to "On". With this setting enabled, a - new line gets added to the end of the <b>.rockbox/playback.log</b> file every time you play a track, - containing info about what you played and when. - </p> - <p> - The logging feature is actually already used by the LastFM scrobbler plugin that comes preloaded with - Rockbox, which is probably the simplest way to get insights into your playback. However, - I personally want to avoid using third-party services as much as possible, because it's more fun. - </p> - <p> - If I take a look at a logfile generated after a bit of listening, I'll see that I've wound up with - a series of lines that each look something like this: - <figure class="fig"> - <pre><samp>1758478258:336689:336718:/<microSD0>/Music/This Is The Glasshouse/This Is The Glasshouse - 867/This Is The Glasshouse - 867 - 01 Streetlight By Streetlight.flac</samp></pre> - <figcaption>An example of a log entry for "Streetlight by Streetlight" by This is the Glasshouse. - </figcaption> - </figure> - </p> - <p> - I wasn't really able to find any information online about the format of these logs, but they appear - to be simple enough to figure out. From what I can tell, each event is broken up into 4 pieces: - <ol> - <li><b>Timestamp:</b> The number of milliseconds since the UNIX epoch. - <li><b>Playback Duration:</b> The amount of the song that was played, in milliseconds. - <li><b>Total Track Length:</b> The length of the played track, in milliseconds. - <li><b>File Path:</b> An absolute path to the file containing the track on the filesystem. - </ol> - All of this is enough to know what I was listening to and when. I can use the file path to check for - audio tags which can help glean even more information about my listening habits. - </p> - <p>Now that I have this information and know how to interpret it, I'm ready to start processing it!</p> - </section> - <section class="text-section"> - <h2>Analyzing Playback History</h2> - <p> - In order to get some useful information out of my playback history, I think it's a good idea to start by - building - a database. I created a sqlite database with the following tables: - <table class="schema-table"> - <thead> - <tr> - <th colspan="3">songs</th> - </tr> - </thead> - <tbody> - <tr> - <td>id</td> - <td>i64</td> - <td>PK</td> - </tr> - <tr> - <td>title</td> - <td>String</td> - <td></td> - </tr> - <tr> - <td>artists</td> - <td>JSON</td> - <td></td> - </tr> - <tr> - <td>album_id</td> - <td>i64?</td> - <td></td> - </tr> - <tr> - <td>genre</td> - <td>String?</td> - <td></td> - </tr> - </tbody> - </table> - <table class="schema-table"> - <thead> - <tr> - <th colspan="3">albums</th> - </tr> - </thead> - <tbody> - <tr> - <td>id</td> - <td>i64</td> - <td>PK</td> - </tr> - <tr> - <td>title</td> - <td>String</td> - <td></td> - </tr> - <tr> - <td>artist</td> - <td>String</td> - <td></td> - </tr> - <tr> - <td>cover_art</td> - <td>Blob?</td> - <td></td> - </tr> - </tbody> - </table> - <table class="schema-table"> - <thead> - <tr> - <th colspan="3">history</th> - </tr> - </thead> - <tbody> - <tr> - <td>id</td> - <td>i64</td> - <td>PK</td> - </tr> - <tr> - <td>timestamp</td> - <td>Datetime</td> - <td></td> - </tr> - <tr> - <td>duration</td> - <td>i64</td> - <td></td> - </tr> - <tr> - <td>song_id</td> - <td>i64</td> - <td></td> - </tr> - </tbody> - </table> - <br> - I can add more columns later, but this is a good place to start. - </p> - <p> - Now, as I read through the logfile line-by-line, I can check if each album exists before - inserting it into the database: - <figure class="fig"> - <pre><code>for line in log_file.lines().flatten() { - println!("{line}"); - // Skip comments - if line.starts_with("#") { - continue; - } - let chunks = line.split(":").collect::<Vec<_>>(); - - let timestamp = DateTime::from_timestamp_secs( - i64::from_str_radix(chunks[0], 10).context("Failed to parse timestamp")?, - ) - .context("Failed to convert timestamp")?; - - // Load tags from file on device - let file_path = chunks[chunks.len() - 1][1..] - .split_once("/") - .context("Missing file")? - .1; - let tags = Tag::new() - .read_from_path(args.mount_point.join(file_path)) - .context("Failed to read audio tags")?; - - //... -}</code></pre> - <figcaption>Parsing log entry and loading audio metadata.</figcaption> - </figure> - <figure class="fig"> - <pre><code>if let Some(existing_album) = - sqlx::query("SELECT id FROM albums WHERE title=$1 AND artist=$2") - .bind(album_title) - .bind(album_artist) - .fetch_optional(&mut *db) - .await - .context("Failed to execute query to find existing album")? -{ - let album_id: i64 = existing_album.get("id"); - info!("Album already exists, id {album_id}"); - //... -} else { - info!("Inserting new album: {album_title} by {album_artist}"); - //... - let result = sqlx::query( - "INSERT INTO albums (title, artist, cover_art) VALUES ($1, $2, $3);", - ) - .bind(album_title) - .bind(album_artist) - .bind(cover) - .execute(&mut *db) - .await - .context("Failed to execute query to insert album into database")?; - - //... -}</code></pre> - <figcaption>Checking for an album with matching artist and title before creating a new row in the - database.</figcaption> - </figure> - I did something similar with the <b>songs</b> and <b>history</b> tables, basically building up a cache - of history information and skipping anything that's already in the database on repeat runs. - </p> - <p> - With this database constructed, it's pretty easy to get a bunch of different information - about my listening. For example (forgive me if my SQL skills are kind of ass lol): - <figure class="fig"> - <pre><code>SELECT - songs.title AS song_title, - songs.artists AS song_artists, - songs.genre AS song_genre, - albums.title AS album_title, - albums.artist AS album_artist, - history.timestamp AS timestamp, - history.duration AS duration -FROM history -CROSS JOIN songs ON songs.id = history.song_id -CROSS JOIN albums ON albums.id = songs.album_id -ORDER BY timestamp DESC;</code></pre> - <figcaption>Querying for a list of each history entry along with track metadata, sorted from most to - least recent.</figcaption> - </figure> - <figure class="fig"> - <pre><code>SELECT - songs.genre, - SUM(history.duration) AS total_duration -FROM history -CROSS JOIN songs ON history.song_id = songs.id -GROUP BY genre -ORDER BY total_duration DESC -LIMIT 10; </code></pre> - <figcaption>Querying for the top 10 most listened genres by playtime.</figcaption> - </figure> - </p> - <p> - It's all well and good to be able to view this information using a database client, - but it would be really cool if I could visualize this data somehow. - </p> - </section> - <section class="text-section"> - <h2>Visualizing this Data Somehow</h2> - <p> - I wanted to make this data available on my website for people to view, and for a bunch of mostly trivial - reasons I won't get into here, I have a couple of requirements for pages on this site: - <ol> - <li>Pages need to be static. - <li>Pages need to be JavaScript-free. - </ol> - This means any chart rendering needs to be done automatically at build time before - deploying. I don't currently use a static site generator for my site (just for fun), - so I'm basically going to need to write one specifically to generate this page. - </p> - <p> - I won't get too deep into the specifics of how I queried the database and generated each visualization - on - the page, but I can explain the visualizations I created using the queries from the previous section. - For the - listening history I wanted to generate a table displaying the information. To accomplish this, I first - used a combination of <a href="https://crates.io/crates/sqlx" target="_blank">sqlx</a>'s ability to convert a row to a struct and <a href="https://crates.io/crates/serde" target="_blank">serde</a> to serialize - the rows as JSON values. - <figure class="fig"> - <pre><code>#[derive(Serialize, Deserialize, FromRow)] -struct HistoryEntry { - song_title: String, - song_artists: Value, - timestamp: DateTime<Utc>, - duration: i64, - album_title: String, - album_artist: Option<String>, - song_genre: Option<String>, -} - -//...later -let history = sqlx::query_as::<_, HistoryEntry>( - /* SELECT... */ -).fetch_all(&mut *db).await; - -//...later still, tera context accepts -let mut context = tera::Context::new(); -context.insert("history", &history); -</code></pre> - <figcaption>Struct definition for a history entry, allowing conversion from a sqlx row and - de/serialization from/to JSON.</figcaption> - </figure> - </p> - <p> - In order to keep the generation as painless as possible, I decided to use the <a href="https://keats.github.io/tera" target="_blank">Tera</a> template - engine, which allows me to define a template HTML file and substitute in values from - a context which I can define before rendering. In the case of the table, I can just generate a <code><tr></code> - matching the data for each item: - <figure class="fig"> - <pre><code>{% macro history_table(history) %} -<h3>Playback History</h3> -<div class="table-container"> - <table> - <thead> - <tr> - <th>Timestamp</th> - <th>Played Duration</th> - <th>Title</th> - <th>Artists</th> - <th>Album</th> - <th>Genre</th> - </tr> - </thead> - <tbody> - {% for item in history %}<tr> - <td>{{ item.timestamp | date(format="%Y-%m-%d %H:%M:%S") }}</td> - <td>{{ item.duration | hms }}</td> - <td>{{ item.song_title }}</td> - <td>{{ item.song_artists }}</td> - <td>{{ item.album_title }}</td> - <td>{{ item.song_genre }}</td> - </tr> - {% endfor %} - </tbody> - </table> -</div> -{% endmacro history_table %}</code></pre> - <figcaption> - A Tera macro for generating a table from a list of playback history items. - I used a macro so I can re-use this later if I want to add time range views. - (last month, year, etc.) - </figcaption> - </figure> - </p> - <p> - I wrote similar macros for each of the visualizations I wanted to create. Most are - easy, but for my top 10 genres I wanted to display a pie chart. I found a pretty decent - data visualization crate called <a href="https://crates.io/crates/charming" target="_blank">charming</a> that's able to render to html, however - the output contains javascript so it's a no-go for me. Luckily, it can also render to - an SVG which I can embed nicely within the page. - <figure class="fig"> -<svg width="100%" height="360" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" viewBox="0 0 640 360"> -<rect width="640" height="360" x="0" y="0" fill="rgb(0,0,0)" fill-opacity="0"></rect> -<polyline points="427.2 233.9 440.6 240.7 455.6 240.7" fill="transparent" stroke="#e67e80" class="zr0-cls-0"></polyline> -<polyline points="202.3 203.4 192.6 206.3 177.6 206.3" fill="transparent" stroke="#e69875" class="zr0-cls-0"></polyline> -<polyline points="220 113.6 197.5 135.5 182.5 135.5" fill="transparent" stroke="#dbbc7f" class="zr0-cls-0"></polyline> -<polyline points="273.2 69.5 190.3 117.4 190.3 117.4" fill="transparent" stroke="#a7c080" class="zr0-cls-0"></polyline> -<polyline points="304.6 61 201.7 99.3 201.7 99.3" fill="transparent" stroke="#83c092" class="zr0-cls-0"></polyline> -<polyline points="310.7 60.4 217.9 81.2 217.9 81.2" fill="transparent" stroke="#7fbbb3" class="zr0-cls-0"></polyline> -<polyline points="315.4 60.1 242.2 63.1 242.2 63.1" fill="transparent" stroke="#d699b6" class="zr0-cls-0"></polyline> -<polyline points="318.6 60 318.4 45 303.4 45" fill="transparent" stroke="#f85552" class="zr0-cls-0"></polyline> -<path d="M320 60A120 120 0 1 1 223.6 251.5L320 180Z" fill="#e67e80" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="0" ecmeta_ssr_type="chart" class="zr0-cls-1"></path> -<path d="M223.6 251.5A120 120 0 0 1 203.6 150.7L320 180Z" fill="#e69875" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="1" ecmeta_ssr_type="chart" class="zr0-cls-2"></path> -<path d="M203.6 150.7A120 120 0 0 1 247.9 84.1L320 180Z" fill="#dbbc7f" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="2" ecmeta_ssr_type="chart" class="zr0-cls-3"></path> -<path d="M247.9 84.1A120 120 0 0 1 301.3 61.5L320 180Z" fill="#a7c080" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="3" ecmeta_ssr_type="chart" class="zr0-cls-4"></path> -<path d="M301.3 61.5A120 120 0 0 1 307.8 60.6L320 180Z" fill="#83c092" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="4" ecmeta_ssr_type="chart" class="zr0-cls-5"></path> -<path d="M307.8 60.6A120 120 0 0 1 313.6 60.2L320 180Z" fill="#7fbbb3" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="5" ecmeta_ssr_type="chart" class="zr0-cls-6"></path> -<path d="M313.6 60.2A120 120 0 0 1 317.2 60L320 180Z" fill="#d699b6" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="6" ecmeta_ssr_type="chart" class="zr0-cls-7"></path> -<path d="M317.2 60A120 120 0 0 1 320 60L320 180Z" fill="#f85552" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="7" ecmeta_ssr_type="chart" class="zr0-cls-8"></path> -<text dominant-baseline="central" text-anchor="start" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(460.5907 240.6868)" fill="var(--fg)">Progressive Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(172.5853 206.2935)" fill="var(--fg)">Alternative</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(177.5244 135.5094)" fill="var(--fg)">Post-Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(185.3496 117.4094)" fill="var(--fg)">Post-Hardcore</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(196.7011 99.3094)" fill="var(--fg)">Post-Metal</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(212.8733 81.2094)" fill="var(--fg)">Rock</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(237.2337 63.1094)" fill="var(--fg)">Shoegaze</text> -<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(298.4082 45.0094)" fill="var(--fg)">Progressive Metal</text> -<style> -.zr0-cls-0:hover { -cursor:pointer; -} -.zr0-cls-1:hover { -cursor:pointer; -fill:rgba(253,138,140,1); -} -.zr0-cls-2:hover { -cursor:pointer; -fill:rgba(253,167,128,1); -} -.zr0-cls-3:hover { -cursor:pointer; -fill:rgba(240,206,139,1); -} -.zr0-cls-4:hover { -cursor:pointer; -fill:rgba(183,211,140,1); -} -.zr0-cls-5:hover { -cursor:pointer; -fill:rgba(144,211,160,1); -} -.zr0-cls-6:hover { -cursor:pointer; -fill:rgba(139,205,196,1); -} -.zr0-cls-7:hover { -cursor:pointer; -fill:rgba(235,168,200,1); -} -.zr0-cls-8:hover { -cursor:pointer; -fill:rgba(255,93,90,1); -} - - - -</style> -</svg> - <figcaption>Here's one I generated just now.</figcaption> - </figure> - </p> - <p> - And that's pretty much all there is to it! The finished thing can be found <a href="/rockstats" target="_blank">here</a>. - </p> - </section> - </article> -</body> - -</html>
\ No newline at end of file diff --git a/blog/terminal_renderer_mkii/index.html b/blog/terminal_renderer_mkii/index.html deleted file mode 100644 index 326c5d4..0000000 --- a/blog/terminal_renderer_mkii/index.html +++ /dev/null @@ -1,189 +0,0 @@ -<!DOCTYPE html> - -<html> - -<head> - <title>Terminal Renderer Mk. II - Rendering to Text with Compute</title> - <meta name="og:title" content="Terminal Renderer Mk. II - Rendering to Text with Compute"/> - <meta name="og:description" content="This week I brought my terminal renderer to the next level by performing text rendering on the GPU."/> - <meta name="og:image" content="https://soaos.dev/blog/terminal_renderer_mkii/cover.png"/> - <link rel="stylesheet" href="/style.css"> - <link rel="stylesheet" href="/blog/blog.css"> -</head> - -<body> - <div class="text-section"> - <a href="..">↰ Back</a> - <a href="/">⌂ Home</a> - </div> - <article> - <section> - <div class="text-section"> - <!-- Header Section --> - <h1>Terminal Renderer Mk. II - Rendering to Text with Compute</h1> - <p>October 2, 2025</p> - <p>This week I brought my terminal renderer to the next level by performing text rendering on the GPU. - </p> - </div> - <figure class="cover-image"> - <img src="cover.png" alt=""> - <figcaption>The Stanford Dragon, outlined and rendered as Braille characters in a terminal emulator. <a href="https://tv.soaos.dev/w/fBnDAUPsTPHaoPeNNxBGch" target="_blank"> -Full video</a> - </figcaption> - </figure> - </section> - <section class="text-section"> - <h2>Context</h2> - <h3>Unicode Braille</h3> - <p> - I first messed around with rendering images to the terminal with Braille characters in like 2022 I - think? I wrote a simple CLI tool - that applied a threshold to an input image and output it as Braille characters in the terminal. <a - href="https://tv.soaos.dev/w/twpHAu4Jv8LJc9YjZbfw5g" target="_blank">Here's a recording I took back - when I did it.</a> - </p> - - <p> - <figure class="fig fig-right"> - <div class="centered"> - <table class="schema-table"> - <tbody> - <tr> - <td>0</td> - <td>3</td> - </tr> - <tr> - <td>1</td> - <td>4</td> - </tr> - <tr> - <td>2</td> - <td>5</td> - </tr> - <tr> - <td>6</td> - <td>7</td> - </tr> - </tbody> - </table> - </div> - <figcaption>The corresponding bit position for each braille dot.</figcaption> - </figure> - This effect is pretty cool, and it was pretty easy to implement as well. The trick lies in how the - <a href="https://en.wikipedia.org/wiki/Braille_Patterns#Block" target="_blank">Unicode Braille block</a> - is laid out. Every 8-dot Braille combination happens to add up to 256 combinations, the perfect amount to - fit in the range between <code>0x2800</code> (⠀) and <code>0x28FF</code> (⣿). In other words, every - character - within the block can be represented by changing the value of a single byte. - </p> - <p> - The lowest 6 bits of the pattern map on to a 6-dot braille pattern. However, due - to historical reasons the 8-dot values were tacked on after the fact, which adds - a slightly annoying mapping to the conversion process. Either way, it's a lot easier - than it could be to just read a pixel value, check its brightness, and then use a - bitwise operation to set/clear a dot. - </p> - <h3>Ordered Dithering</h3> - <p> - Comparing the brightnes of a pixel against a constant threshold is a fine way to - display black and white images, but it's far from ideal and often results in the loss - of a lot of detail from the original image. - </p> - <figure class="fig fig-horizontal"> - <div class="horizontal-container"> - <img src="david.png" alt="" /> - <img src="davidthreshold.png" alt="" /> - <img src="davidbayer.png" alt="" /> - </div> - <figcaption>From left to right: Original image, threshold, and ordered dither. <a - href="https://en.wikipedia.org/wiki/Dither" target="_blank">Wikipedia</a></figcaption> - </figure> - <p>By using <a href="https://en.wikipedia.org/wiki/Ordered_dithering" target="_blank">ordered dithering</a>, - we - can preserve much more of the subtleties of the original image. While not the "truest" version of - dithering possible, - ordered dithering (and <i>Bayer</i> dithering in particular) provides a few advantages that make it very - well suited to realtime computer graphics: - <ul> - <li>Each pixel is dithered independent of any other pixel in the image, making it extremely - parallelizable and good for shaders.</li> - <li>It's visually stable, changes to one part of the image won't disturb other areas.</li> - <li>It's dead simple.</li> - </ul> - Feel free to read up on the specifics of threshold maps and stuff, but for the purposes of this little - explanation it's - enough to know that it's basically just a matrix of 𝓃⨉𝓃 values between 0 and 1, and then to determine - whether a pixel (𝓍,𝓎) - is white or black, you check the brightness against the threshold value at (𝓍%𝓃,𝓎%𝓃) in the map. - </p> - </section> - <section class="text-section"> - <h2>The old way™</h2> - <p> - My first attempt at <i>realtime</i> terminal graphics with ordered dithering - (<a href="https://tv.soaos.dev/w/dzHBnPJXtDBwtSvirgwTvY" target="_blank">I put a video up at the time</a>) - ran entirely on the CPU. I pre-calculated the threshold map at the beginning of execution and ran each - frame - through a sequential function to dither it and convert it to Braille characters. - </p> - <p> - To be honest, I never noticed - any significant performance issues doing this, as you can imagine the image size required to fill a - terminal - screen is signficantly smaller than a normal window. However, I knew I could easily perform the - dithering on the GPU - as a post-processing effect, so I eventually wrote a shader to do that. In combination with another - effect I used to - add outlines to objects, I was able to significantly improve the visual fidelity of the experience. A - good example of - where the renderer was at until like a week ago can be seen in <a - href="https://tv.soaos.dev/w/9Pf2tP3PYY5pJ3Cimhqs9x" target="_blank">this video</a>. - </p> - <p> - Until now I hadn't really considered moving the text conversion to the GPU. I mean, <i>G</i>PU is for - graphics, - right? I just copied the <i>entire framebuffer</i> back onto the CPU after dithering - and used the same sequential conversion algorithm. Then I had an idea that would drastically reduce the - amount - of copying necessary. - </p> - </section> - <section class="text-section"> - <h2>Compute post-processing</h2> - <p> - What if, instead of extracting and copying the framebuffer every single frame, we "rendered" the text on - the GPU - and read <i>that</i> back instead? Assuming each pixel in a texture is 32 bits (RGBA8), and knowing that - each braille - character is a block of 8 pixels, could we not theoretically shave off <i>at least</i> 7/8 of the bytes - copied? - </p> - <p> - As it turns out, it's remarkably easy to do. I'm using the <a href="https://bevy.org" - target="_blank">Bevy engine</a>, - and hooking in a compute node to my existing post-processing render pipeline worked right out of the - box. - I allocated a storage buffer large enough to hold the necessary amount of characters, read it back each - frame, and dumped - the contents into the terminal. - </p> - <p> - I used UTF-32 encoding on the storage buffer because I knew I could easily convert a "wide string" into - UTF-8 before printing it, and - 32 bits provides a consistent space to fill for each workgroup in the shader versus a variable-length - encoding like UTF-8. <a href="https://tv.soaos.dev/w/fBnDAUPsTPHaoPeNNxBGch" target="_blank">Here's a video of the new renderer working</a>. - Although now that I think about it, I could probably switch to using UTF-16 since all the Braille - characters could be represented - in 2 bytes, and that would be half the size of the UTF-32 text, which is half empty bytes anyways. - </p> - <p> - Okay so I went and tried that but remembered that shaders only accept 32-bit primitive types, so it doesn't matter anyways. This little side quest has been a part of my - broader efforts to revive a project I - spent a lot of time on. I'm taking the opportunity to really dig in and rework some of the stuff I'm not - totally happy with. So there might be quite a few of this kind of post in the near future. Stay tuned. - </p> - </section> - </article> -</body> - -</html> diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..8ab9b67 --- /dev/null +++ b/config.toml @@ -0,0 +1,18 @@ +# The URL the site will be built for +base_url = "https://soaos.dev" + +# Whether to automatically compile all Sass files in the sass directory +compile_sass = false + +# Whether to build a search index to be used later on by a JavaScript library +build_search_index = false + +[markdown] +# Whether to do syntax highlighting +# Theme can be customised by setting the `highlight_theme` variable to a theme supported by Zola +highlight_code = true + +external_links_target_blank = true + +[extra] +# Put all your custom variables here diff --git a/content/_index.md b/content/_index.md new file mode 100644 index 0000000..4e36d29 --- /dev/null +++ b/content/_index.md @@ -0,0 +1,39 @@ ++++ +title = "soaos" +template = "index.html" ++++ + +{% window() %} +{% title_bar() %} +🧍 Welcome to {% soaosed() %}soaos{% end %}.dev +{% end %} +{% window_body() %} + +I'm an "artist" and professional software developer. In my free time I mostly work on eccentric software projects which you can read about here. + +{% tree_view(height="16rem") %} + +{% subtree(name="On this site", open=true) %} +{{ treelink(text="📖 Blog", url="/blog") }} +{{ treelink(text="♫ Rockbox Stats", url="/rockstats", wip=true, wip_tooltip="🦌 -this shit is under construction, pal!") }} +{{ treelink(text="🦌 Things I Like", url="/things_i_like", wip=true, wip_tooltip="🦌 -honestly these things are redundant, everything is under construction") }} +{% end %} + +{% subtree(name='<span class="flip" data-title="Soaos"><span class="hidden-selectable">Soaos</span></span> Services') %} +{{ treelink(text="🌱 Source Code", url="https://app.radicle.xyz/nodes/seed.soaos.dev", blank=true) }} +{{ treelink(text="📺 Videos", url="https://tv.soaos.dev/c/soaosdev", blank=true) }} +{{ treelink(text="🗃 Web Archive", url="https://archive.soaos.dev", blank=true) }} +{{ treelink(text="🔍 Search Engine", url="https://search.soaos.dev", blank=true) }} +{{ treelink(text="♊ Gemini Site", url="gemini://soaos.dev", blank=true) }} +{% end %} + +{% subtree(name="Find me on the Web", open=true)%} +{{ treelink(text="📧 E-Mail: soaos@soaos.dev", url="mailto:soaos@soaos.dev", rel="me") }} +{{ treelink(text="🐘 Mastodon: soaos@furry.engineer", url="http://soaos.dog", rel="me", blank=true) }} +{{ treelink(text="🦀 SSB: @Y1EKP4PU77qby4lI+m5MN6+NcYdjTdRQlV6NmluevuY=.ed25519", url="https://ssb.soaos.dev/~core/ssb/#@Y1EKP4PU77qby4lI+m5MN6+NcYdjTdRQlV6NmluevuY=.ed25519", rel="me", blank=true) }} +{{ treelink(text="⬡ ListenBrainz: soaos", url="https://listenbrainz.org/user/soaos/", rel="me", blank=true) }} +{% end %} + +{% end %} +{% end %} +{% end %} diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..44bbfa0 --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,6 @@ ++++ +title = "soaos blog" +template = "blog.html" +page_template = "post.html" +sort_by = "date" ++++
\ No newline at end of file diff --git a/blog/blog.css b/content/blog/blog.css index 372964d..372964d 100644 --- a/blog/blog.css +++ b/content/blog/blog.css diff --git a/content/blog/rockbox_stats/index.md b/content/blog/rockbox_stats/index.md new file mode 100644 index 0000000..626cbe9 --- /dev/null +++ b/content/blog/rockbox_stats/index.md @@ -0,0 +1,547 @@ ++++ +title = "Rockbox Stat Tracking" +date = "2025-09-02" ++++ + +<article> +<section> +<div class="text-section"> +<p>In this post I talk about how I went about setting up a <a href="/rockstats" target="_blank">stat visualization page</a> for my rockbox mp3 player.</p> +</div> +<figure class="cover-image"> +<svg width="100%" height="360" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" viewBox="0 0 640 360"> +<rect width="640" height="360" x="0" y="0" fill="rgb(0,0,0)" fill-opacity="0"></rect> +<polyline points="427.2 233.9 440.6 240.7 455.6 240.7" fill="transparent" stroke="#e67e80" class="zr0-cls-0"></polyline> +<polyline points="202.3 203.4 192.6 206.3 177.6 206.3" fill="transparent" stroke="#e69875" class="zr0-cls-0"></polyline> +<polyline points="220 113.6 197.5 135.5 182.5 135.5" fill="transparent" stroke="#dbbc7f" class="zr0-cls-0"></polyline> +<polyline points="273.2 69.5 190.3 117.4 190.3 117.4" fill="transparent" stroke="#a7c080" class="zr0-cls-0"></polyline> +<polyline points="304.6 61 201.7 99.3 201.7 99.3" fill="transparent" stroke="#83c092" class="zr0-cls-0"></polyline> +<polyline points="310.7 60.4 217.9 81.2 217.9 81.2" fill="transparent" stroke="#7fbbb3" class="zr0-cls-0"></polyline> +<polyline points="315.4 60.1 242.2 63.1 242.2 63.1" fill="transparent" stroke="#d699b6" class="zr0-cls-0"></polyline> +<polyline points="318.6 60 318.4 45 303.4 45" fill="transparent" stroke="#f85552" class="zr0-cls-0"></polyline> +<path d="M320 60A120 120 0 1 1 223.6 251.5L320 180Z" fill="#e67e80" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="0" ecmeta_ssr_type="chart" class="zr0-cls-1"></path> +<path d="M223.6 251.5A120 120 0 0 1 203.6 150.7L320 180Z" fill="#e69875" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="1" ecmeta_ssr_type="chart" class="zr0-cls-2"></path> +<path d="M203.6 150.7A120 120 0 0 1 247.9 84.1L320 180Z" fill="#dbbc7f" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="2" ecmeta_ssr_type="chart" class="zr0-cls-3"></path> +<path d="M247.9 84.1A120 120 0 0 1 301.3 61.5L320 180Z" fill="#a7c080" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="3" ecmeta_ssr_type="chart" class="zr0-cls-4"></path> +<path d="M301.3 61.5A120 120 0 0 1 307.8 60.6L320 180Z" fill="#83c092" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="4" ecmeta_ssr_type="chart" class="zr0-cls-5"></path> +<path d="M307.8 60.6A120 120 0 0 1 313.6 60.2L320 180Z" fill="#7fbbb3" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="5" ecmeta_ssr_type="chart" class="zr0-cls-6"></path> +<path d="M313.6 60.2A120 120 0 0 1 317.2 60L320 180Z" fill="#d699b6" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="6" ecmeta_ssr_type="chart" class="zr0-cls-7"></path> +<path d="M317.2 60A120 120 0 0 1 320 60L320 180Z" fill="#f85552" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="7" ecmeta_ssr_type="chart" class="zr0-cls-8"></path> +<text dominant-baseline="central" text-anchor="start" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(460.5907 240.6868)" fill="var(--fg)">Progressive Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(172.5853 206.2935)" fill="var(--fg)">Alternative</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(177.5244 135.5094)" fill="var(--fg)">Post-Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(185.3496 117.4094)" fill="var(--fg)">Post-Hardcore</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(196.7011 99.3094)" fill="var(--fg)">Post-Metal</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(212.8733 81.2094)" fill="var(--fg)">Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(237.2337 63.1094)" fill="var(--fg)">Shoegaze</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(298.4082 45.0094)" fill="var(--fg)">Progressive Metal</text> +<style> +.zr0-cls-0:hover { +cursor:pointer; +} +.zr0-cls-1:hover { +cursor:pointer; +fill:rgba(253,138,140,1); +} +.zr0-cls-2:hover { +cursor:pointer; +fill:rgba(253,167,128,1); +} +.zr0-cls-3:hover { +cursor:pointer; +fill:rgba(240,206,139,1); +} +.zr0-cls-4:hover { +cursor:pointer; +fill:rgba(183,211,140,1); +} +.zr0-cls-5:hover { +cursor:pointer; +fill:rgba(144,211,160,1); +} +.zr0-cls-6:hover { +cursor:pointer; +fill:rgba(139,205,196,1); +} +.zr0-cls-7:hover { +cursor:pointer; +fill:rgba(235,168,200,1); +} +.zr0-cls-8:hover { +cursor:pointer; +fill:rgba(255,93,90,1); +} +</style> +</svg> +<figcaption>A static site generation experiment</figcaption> +</figure> +</section> +<section class="text-section"> + <h2>Preamble: Digital Sovereignity & Rockbox</h2> + <p> + I've been building up a pretty sizeable collection of digital music + over the last couple of years. I think there's a lot of value in owning + the music I pay for and being able to choose how I listen to it. + Purchasing music also allows me to support artists in a more direct + and substantial way than the fractions of cents for using streaming services, + but that's more of a happy consequence than some moral obligation I feel. + </p> + <p> + Over the years, I've enjoyed listening to my music in a variety of ways. + For years I kept all of my music files on all of my devices and used + various local music clients depending on the platform, most notably mpd + and ncmpcpp on linux. Eventually, as I charged headlong into the glorious + world of self-hosting, I began using a central Jellyfin media server that + I stream music and video from. It's super convenient, and works on all of + my devices (including my TV!). + </p> + <p> + My media server is great, and it's been the primary way I listen to music + for a while now. But it has limitations. For example, I don't expose my media + server to the internet, so I'm unable to stream from it while I'm out and + about. And even if I could, the bandwidth requirements would be pretty high. + I figured I would need a dedicated music player if I wanted to take my music + library on the go, and settled on the HIFI Walker H2 after reading some + online recommendations. The ability to install <a href="https://rockbox.org" target="_blank">Rockbox</a>, an open-source firmware, + was a big factor in my decision. I couldn't tell you how the device works + out of the box, since I flashed the firmware pretty much immediately once I got it, + but I've been super impressed with how the device works while running Rockbox. + </p> + <p> + <figure class="fig fig-right"> + <img src="player.bmp" alt="Screenshot of Rockbox player showing cool theme."> + <figcaption>I'm using a modified version of the <a + href="https://themes.rockbox.org/index.php?themeid=3266&target=aigoerosq" + target="_blank">InfoMatrix-v2</a> theme, which looks great.</figcaption> + </figure> + Rockbox comes with many codecs for common audio formats including FLAC and MP3. The + device boots extremely quickly, and the interface is snappy. Virtually every aspect + of the user experience is tweakable and customizable to a crazy degree. I've even begun + listening to music on my player even at home, since a device specifically for the + purpose provides less distraction while I'm trying to be productive. + </p> + <p> + All this to say I'm pretty much sold on Rockbox. But there's certain things I + still miss from my days of being a user of popular services like Spotify with + fancy APIs and data tracking. Things like Spotify wrapped or third-party apps + for visualizing playback statistics are a fun way to see what my listening history + looks like and could potentially be used to help find more music that I'd enjoy. + This is why when I noticed that Rockbock has a playback logging feature, a little + lightbulb lit up over my head. + </p> +</section> +<section class="text-section"> + <h2>Generating and Parsing Logs</h2> + <p> + <figure class="fig fig-right"> + <img src="log-setting.bmp" alt="Logging"> + <figcaption>The logging feature can be accessed through the settings menu.</figcaption> + </figure> + Rockbox has a feature that logs playback information to a text file. This feature can + be enabled by setting <b>Playback Settings > Logging</b> to "On". With this setting enabled, a + new line gets added to the end of the <b>.rockbox/playback.log</b> file every time you play a track, + containing info about what you played and when. + </p> + <p> + The logging feature is actually already used by the LastFM scrobbler plugin that comes preloaded with + Rockbox, which is probably the simplest way to get insights into your playback. However, + I personally want to avoid using third-party services as much as possible, because it's more fun. + </p> + <p> + If I take a look at a logfile generated after a bit of listening, I'll see that I've wound up with + a series of lines that each look something like this: + <figure class="fig"> + <pre><samp>1758478258:336689:336718:/<microSD0>/Music/This Is The Glasshouse/This Is The Glasshouse - 867/This Is The Glasshouse - 867 - 01 Streetlight By Streetlight.flac</samp></pre> + <figcaption>An example of a log entry for "Streetlight by Streetlight" by This is the Glasshouse. + </figcaption> + </figure> + </p> + <p> + I wasn't really able to find any information online about the format of these logs, but they appear + to be simple enough to figure out. From what I can tell, each event is broken up into 4 pieces: + <ol> + <li><b>Timestamp:</b> The number of milliseconds since the UNIX epoch. + <li><b>Playback Duration:</b> The amount of the song that was played, in milliseconds. + <li><b>Total Track Length:</b> The length of the played track, in milliseconds. + <li><b>File Path:</b> An absolute path to the file containing the track on the filesystem. + </ol> + All of this is enough to know what I was listening to and when. I can use the file path to check for + audio tags which can help glean even more information about my listening habits. + </p> + <p>Now that I have this information and know how to interpret it, I'm ready to start processing it!</p> +</section> +<section class="text-section"> + <h2>Analyzing Playback History</h2> + <p> + In order to get some useful information out of my playback history, I think it's a good idea to start by + building + a database. I created a sqlite database with the following tables: + <div class="sunken-panel" style="width: min-content;"> + <table class="schema-table"> + <thead> + <tr> + <th colspan="3">songs</th> + </tr> + </thead> + <tbody> + <tr> + <td>id</td> + <td>i64</td> + <td>PK</td> + </tr> + <tr> + <td>title</td> + <td>String</td> + <td></td> + </tr> + <tr> + <td>artists</td> + <td>JSON</td> + <td></td> + </tr> + <tr> + <td>album_id</td> + <td>i64?</td> + <td></td> + </tr> + <tr> + <td>genre</td> + <td>String?</td> + <td></td> + </tr> + </tbody> + </table> + </div> + <div class="sunken-panel" style="width: min-content;"> + <table class="schema-table"> + <thead> + <tr> + <th colspan="3">albums</th> + </tr> + </thead> + <tbody> + <tr> + <td>id</td> + <td>i64</td> + <td>PK</td> + </tr> + <tr> + <td>title</td> + <td>String</td> + <td></td> + </tr> + <tr> + <td>artist</td> + <td>String</td> + <td></td> + </tr> + <tr> + <td>cover_art</td> + <td>Blob?</td> + <td></td> + </tr> + </tbody> + </table> + </div> + <div class="sunken-panel" style="width: min-content;"> + <table class="schema-table"> + <thead> + <tr> + <th colspan="3">history</th> + </tr> + </thead> + <tbody> + <tr> + <td>id</td> + <td>i64</td> + <td>PK</td> + </tr> + <tr> + <td>timestamp</td> + <td>Datetime</td> + <td></td> + </tr> + <tr> + <td>duration</td> + <td>i64</td> + <td></td> + </tr> + <tr> + <td>song_id</td> + <td>i64</td> + <td></td> + </tr> + </tbody> + </table> + </div> + <br> + I can add more columns later, but this is a good place to start. + </p> + <p> + Now, as I read through the logfile line-by-line, I can check if each album exists before + inserting it into the database: + <figure class="fig"> + <pre><code>for line in log_file.lines().flatten() { +println!("{line}"); +// Skip comments +if line.starts_with("#") { +continue; +} +let chunks = line.split(":").collect::<Vec<_>>(); + +let timestamp = DateTime::from_timestamp_secs( +i64::from_str_radix(chunks[0], 10).context("Failed to parse timestamp")?, +) +.context("Failed to convert timestamp")?; + +// Load tags from file on device +let file_path = chunks[chunks.len() - 1][1..] +.split_once("/") +.context("Missing file")? +.1; +let tags = Tag::new() +.read_from_path(args.mount_point.join(file_path)) +.context("Failed to read audio tags")?; + +//... +}</code></pre> + <figcaption>Parsing log entry and loading audio metadata.</figcaption> + </figure> + <figure class="fig"> + <pre><code>if let Some(existing_album) = +sqlx::query("SELECT id FROM albums WHERE title=$1 AND artist=$2") +.bind(album_title) +.bind(album_artist) +.fetch_optional(&mut *db) +.await +.context("Failed to execute query to find existing album")? +{ +let album_id: i64 = existing_album.get("id"); +info!("Album already exists, id {album_id}"); +//... +} else { +info!("Inserting new album: {album_title} by {album_artist}"); +//... +let result = sqlx::query( +"INSERT INTO albums (title, artist, cover_art) VALUES ($1, $2, $3);", +) +.bind(album_title) +.bind(album_artist) +.bind(cover) +.execute(&mut *db) +.await +.context("Failed to execute query to insert album into database")?; + +//... +}</code></pre> + <figcaption>Checking for an album with matching artist and title before creating a new row in the + database.</figcaption> + </figure> + I did something similar with the <b>songs</b> and <b>history</b> tables, basically building up a cache + of history information and skipping anything that's already in the database on repeat runs. + </p> + <p> + With this database constructed, it's pretty easy to get a bunch of different information + about my listening. For example (forgive me if my SQL skills are kind of ass lol): + <figure class="fig"> + <pre><code>SELECT +songs.title AS song_title, +songs.artists AS song_artists, +songs.genre AS song_genre, +albums.title AS album_title, +albums.artist AS album_artist, +history.timestamp AS timestamp, +history.duration AS duration +FROM history +CROSS JOIN songs ON songs.id = history.song_id +CROSS JOIN albums ON albums.id = songs.album_id +ORDER BY timestamp DESC;</code></pre> + <figcaption>Querying for a list of each history entry along with track metadata, sorted from most to + least recent.</figcaption> + </figure> + <figure class="fig"> + <pre><code>SELECT +songs.genre, +SUM(history.duration) AS total_duration +FROM history +CROSS JOIN songs ON history.song_id = songs.id +GROUP BY genre +ORDER BY total_duration DESC +LIMIT 10; </code></pre> + <figcaption>Querying for the top 10 most listened genres by playtime.</figcaption> + </figure> + </p> + <p> + It's all well and good to be able to view this information using a database client, + but it would be really cool if I could visualize this data somehow. + </p> +</section> +<section class="text-section"> + <h2>Visualizing this Data Somehow</h2> + <p> + I wanted to make this data available on my website for people to view, and for a bunch of mostly trivial + reasons I won't get into here, I have a couple of requirements for pages on this site: + <ol> + <li>Pages need to be static. + <li>Pages need to be JavaScript-free. + </ol> + This means any chart rendering needs to be done automatically at build time before + deploying. I don't currently use a static site generator for my site (just for fun), + so I'm basically going to need to write one specifically to generate this page. + </p> + <p> + I won't get too deep into the specifics of how I queried the database and generated each visualization + on + the page, but I can explain the visualizations I created using the queries from the previous section. + For the + listening history I wanted to generate a table displaying the information. To accomplish this, I first + used a combination of <a href="https://crates.io/crates/sqlx" target="_blank">sqlx</a>'s ability to convert a row to a struct and <a href="https://crates.io/crates/serde" target="_blank">serde</a> to serialize + the rows as JSON values. + <figure class="fig"> + <pre><code>#[derive(Serialize, Deserialize, FromRow)] +struct HistoryEntry { +song_title: String, +song_artists: Value, +timestamp: DateTime<Utc>, +duration: i64, +album_title: String, +album_artist: Option<String>, +song_genre: Option<String>, +} + +//...later +let history = sqlx::query_as::<_, HistoryEntry>( +/* SELECT... */ +).fetch_all(&mut *db).await; + +//...later still, tera context accepts +let mut context = tera::Context::new(); +context.insert("history", &history); +</code></pre> + <figcaption>Struct definition for a history entry, allowing conversion from a sqlx row and + de/serialization from/to JSON.</figcaption> + </figure> + </p> + <p> + In order to keep the generation as painless as possible, I decided to use the <a href="https://keats.github.io/tera" target="_blank">Tera</a> template + engine, which allows me to define a template HTML file and substitute in values from + a context which I can define before rendering. In the case of the table, I can just generate a <code><tr></code> + matching the data for each item: + <figure class="fig"> + <pre><code>{% macro history_table(history) %} +<h3>Playback History</h3> +<div class="table-container"> +<table> +<thead> + <tr> + <th>Timestamp</th> + <th>Played Duration</th> + <th>Title</th> + <th>Artists</th> + <th>Album</th> + <th>Genre</th> + </tr> +</thead> +<tbody> + {% for item in history %}<tr> + <td>{{ item.timestamp | date(format="%Y-%m-%d %H:%M:%S") }}</td> + <td>{{ item.duration | hms }}</td> + <td>{{ item.song_title }}</td> + <td>{{ item.song_artists }}</td> + <td>{{ item.album_title }}</td> + <td>{{ item.song_genre }}</td> + </tr> + {% endfor %} +</tbody> +</table> +</div> +{% endmacro history_table %}</code></pre> + <figcaption> + A Tera macro for generating a table from a list of playback history items. + I used a macro so I can re-use this later if I want to add time range views. + (last month, year, etc.) + </figcaption> + </figure> + </p> + <p> + I wrote similar macros for each of the visualizations I wanted to create. Most are + easy, but for my top 10 genres I wanted to display a pie chart. I found a pretty decent + data visualization crate called <a href="https://crates.io/crates/charming" target="_blank">charming</a> that's able to render to html, however + the output contains javascript so it's a no-go for me. Luckily, it can also render to + an SVG which I can embed nicely within the page. + <figure class="fig"> +<svg width="100%" height="360" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" baseProfile="full" viewBox="0 0 640 360"> +<rect width="640" height="360" x="0" y="0" fill="rgb(0,0,0)" fill-opacity="0"></rect> +<polyline points="427.2 233.9 440.6 240.7 455.6 240.7" fill="transparent" stroke="#e67e80" class="zr0-cls-0"></polyline> +<polyline points="202.3 203.4 192.6 206.3 177.6 206.3" fill="transparent" stroke="#e69875" class="zr0-cls-0"></polyline> +<polyline points="220 113.6 197.5 135.5 182.5 135.5" fill="transparent" stroke="#dbbc7f" class="zr0-cls-0"></polyline> +<polyline points="273.2 69.5 190.3 117.4 190.3 117.4" fill="transparent" stroke="#a7c080" class="zr0-cls-0"></polyline> +<polyline points="304.6 61 201.7 99.3 201.7 99.3" fill="transparent" stroke="#83c092" class="zr0-cls-0"></polyline> +<polyline points="310.7 60.4 217.9 81.2 217.9 81.2" fill="transparent" stroke="#7fbbb3" class="zr0-cls-0"></polyline> +<polyline points="315.4 60.1 242.2 63.1 242.2 63.1" fill="transparent" stroke="#d699b6" class="zr0-cls-0"></polyline> +<polyline points="318.6 60 318.4 45 303.4 45" fill="transparent" stroke="#f85552" class="zr0-cls-0"></polyline> +<path d="M320 60A120 120 0 1 1 223.6 251.5L320 180Z" fill="#e67e80" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="0" ecmeta_ssr_type="chart" class="zr0-cls-1"></path> +<path d="M223.6 251.5A120 120 0 0 1 203.6 150.7L320 180Z" fill="#e69875" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="1" ecmeta_ssr_type="chart" class="zr0-cls-2"></path> +<path d="M203.6 150.7A120 120 0 0 1 247.9 84.1L320 180Z" fill="#dbbc7f" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="2" ecmeta_ssr_type="chart" class="zr0-cls-3"></path> +<path d="M247.9 84.1A120 120 0 0 1 301.3 61.5L320 180Z" fill="#a7c080" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="3" ecmeta_ssr_type="chart" class="zr0-cls-4"></path> +<path d="M301.3 61.5A120 120 0 0 1 307.8 60.6L320 180Z" fill="#83c092" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="4" ecmeta_ssr_type="chart" class="zr0-cls-5"></path> +<path d="M307.8 60.6A120 120 0 0 1 313.6 60.2L320 180Z" fill="#7fbbb3" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="5" ecmeta_ssr_type="chart" class="zr0-cls-6"></path> +<path d="M313.6 60.2A120 120 0 0 1 317.2 60L320 180Z" fill="#d699b6" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="6" ecmeta_ssr_type="chart" class="zr0-cls-7"></path> +<path d="M317.2 60A120 120 0 0 1 320 60L320 180Z" fill="#f85552" stroke="var(--bg0)" stroke-linejoin="round" ecmeta_series_index="0" ecmeta_data_index="7" ecmeta_ssr_type="chart" class="zr0-cls-8"></path> +<text dominant-baseline="central" text-anchor="start" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(460.5907 240.6868)" fill="var(--fg)">Progressive Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(172.5853 206.2935)" fill="var(--fg)">Alternative</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(177.5244 135.5094)" fill="var(--fg)">Post-Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(185.3496 117.4094)" fill="var(--fg)">Post-Hardcore</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(196.7011 99.3094)" fill="var(--fg)">Post-Metal</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(212.8733 81.2094)" fill="var(--fg)">Rock</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" transform="translate(237.2337 63.1094)" fill="var(--fg)">Shoegaze</text> +<text dominant-baseline="central" text-anchor="end" style="font-size:16px;font-family:unifont;" xml:space="preserve" transform="translate(298.4082 45.0094)" fill="var(--fg)">Progressive Metal</text> +<style> +.zr0-cls-0:hover { +cursor:pointer; +} +.zr0-cls-1:hover { +cursor:pointer; +fill:rgba(253,138,140,1); +} +.zr0-cls-2:hover { +cursor:pointer; +fill:rgba(253,167,128,1); +} +.zr0-cls-3:hover { +cursor:pointer; +fill:rgba(240,206,139,1); +} +.zr0-cls-4:hover { +cursor:pointer; +fill:rgba(183,211,140,1); +} +.zr0-cls-5:hover { +cursor:pointer; +fill:rgba(144,211,160,1); +} +.zr0-cls-6:hover { +cursor:pointer; +fill:rgba(139,205,196,1); +} +.zr0-cls-7:hover { +cursor:pointer; +fill:rgba(235,168,200,1); +} +.zr0-cls-8:hover { +cursor:pointer; +fill:rgba(255,93,90,1); +} + + + +</style> +</svg> + <figcaption>Here's one I generated just now.</figcaption> + </figure> + </p> + <p> + And that's pretty much all there is to it! The finished thing can be found <a href="/rockstats" target="_blank">here</a>. + </p> +</section> +</article>
\ No newline at end of file diff --git a/blog/rockbox_stats/log-setting.bmp b/content/blog/rockbox_stats/log-setting.bmp Binary files differindex 29789fa..29789fa 100644 --- a/blog/rockbox_stats/log-setting.bmp +++ b/content/blog/rockbox_stats/log-setting.bmp diff --git a/blog/rockbox_stats/playback-settings.bmp b/content/blog/rockbox_stats/playback-settings.bmp Binary files differindex cee3cfb..cee3cfb 100644 --- a/blog/rockbox_stats/playback-settings.bmp +++ b/content/blog/rockbox_stats/playback-settings.bmp diff --git a/blog/rockbox_stats/player.bmp b/content/blog/rockbox_stats/player.bmp Binary files differindex 452a057..452a057 100644 --- a/blog/rockbox_stats/player.bmp +++ b/content/blog/rockbox_stats/player.bmp diff --git a/blog/terminal_renderer_mkii/cover.png b/content/blog/terminal_renderer_mkii/cover.png Binary files differindex b3ddfd9..b3ddfd9 100644 --- a/blog/terminal_renderer_mkii/cover.png +++ b/content/blog/terminal_renderer_mkii/cover.png diff --git a/blog/terminal_renderer_mkii/david.png b/content/blog/terminal_renderer_mkii/david.png Binary files differindex 6cfa884..6cfa884 100644 --- a/blog/terminal_renderer_mkii/david.png +++ b/content/blog/terminal_renderer_mkii/david.png diff --git a/blog/terminal_renderer_mkii/davidbayer.png b/content/blog/terminal_renderer_mkii/davidbayer.png Binary files differindex af4bfc4..af4bfc4 100644 --- a/blog/terminal_renderer_mkii/davidbayer.png +++ b/content/blog/terminal_renderer_mkii/davidbayer.png diff --git a/blog/terminal_renderer_mkii/davidthreshold.png b/content/blog/terminal_renderer_mkii/davidthreshold.png Binary files differindex 6c6e014..6c6e014 100644 --- a/blog/terminal_renderer_mkii/davidthreshold.png +++ b/content/blog/terminal_renderer_mkii/davidthreshold.png diff --git a/content/blog/terminal_renderer_mkii/index.md b/content/blog/terminal_renderer_mkii/index.md new file mode 100644 index 0000000..1797a4a --- /dev/null +++ b/content/blog/terminal_renderer_mkii/index.md @@ -0,0 +1,167 @@ ++++ +title = "Terminal Renderer Mk. II - Rendering to Text with Compute" +date = "2025-10-02" ++++ +<section> +<div class="text-section"> +<p>This week I brought my terminal renderer to the next level by performing text rendering on the GPU. +</p> +</div> +<figure class="cover-image"> +<img src="cover.png" alt="" style="width:100%;"> + <figcaption>The Stanford Dragon, outlined and rendered as Braille characters in a terminal emulator. <a href="https://tv.soaos.dev/w/fBnDAUPsTPHaoPeNNxBGch" target="_blank"> +Full video</a> +</figcaption> +</figure> +</section> +<section class="text-section"> +<h2>Context</h2> +<h3>Unicode Braille</h3> +<p> +I first messed around with rendering images to the terminal with Braille characters in like 2022 I +think? I wrote a simple CLI tool +that applied a threshold to an input image and output it as Braille characters in the terminal. <a + href="https://tv.soaos.dev/w/twpHAu4Jv8LJc9YjZbfw5g" target="_blank">Here's a recording I took back + when I did it.</a> +</p> + +<p> +<figure class="fig fig-right"> +<div class="centered"> + <table class="schema-table"> + <tbody> + <tr> + <td>0</td> + <td>3</td> + </tr> + <tr> + <td>1</td> + <td>4</td> + </tr> + <tr> + <td>2</td> + <td>5</td> + </tr> + <tr> + <td>6</td> + <td>7</td> + </tr> + </tbody> + </table> +</div> +<figcaption>The corresponding bit position for each braille dot.</figcaption> +</figure> +This effect is pretty cool, and it was pretty easy to implement as well. The trick lies in how the +<a href="https://en.wikipedia.org/wiki/Braille_Patterns#Block" target="_blank">Unicode Braille block</a> +is laid out. Every 8-dot Braille combination happens to add up to 256 combinations, the perfect amount to +fit in the range between <code>0x2800</code> (⠀) and <code>0x28FF</code> (⣿). In other words, every +character +within the block can be represented by changing the value of a single byte. +</p> +<p> +The lowest 6 bits of the pattern map on to a 6-dot braille pattern. However, due +to historical reasons the 8-dot values were tacked on after the fact, which adds +a slightly annoying mapping to the conversion process. Either way, it's a lot easier +than it could be to just read a pixel value, check its brightness, and then use a +bitwise operation to set/clear a dot. +</p> +<h3>Ordered Dithering</h3> +<p> +Comparing the brightnes of a pixel against a constant threshold is a fine way to +display black and white images, but it's far from ideal and often results in the loss +of a lot of detail from the original image. +</p> +<figure class="fig fig-horizontal"> +<div class="horizontal-container"> + <img src="david.png" alt="" /> + <img src="davidthreshold.png" alt="" /> + <img src="davidbayer.png" alt="" /> +</div> +<figcaption>From left to right: Original image, threshold, and ordered dither. <a + href="https://en.wikipedia.org/wiki/Dither" target="_blank">Wikipedia</a></figcaption> +</figure> +<p>By using <a href="https://en.wikipedia.org/wiki/Ordered_dithering" target="_blank">ordered dithering</a>, +we +can preserve much more of the subtleties of the original image. While not the "truest" version of +dithering possible, +ordered dithering (and <i>Bayer</i> dithering in particular) provides a few advantages that make it very +well suited to realtime computer graphics: +<ul> +<li>Each pixel is dithered independent of any other pixel in the image, making it extremely + parallelizable and good for shaders.</li> +<li>It's visually stable, changes to one part of the image won't disturb other areas.</li> +<li>It's dead simple.</li> +</ul> +Feel free to read up on the specifics of threshold maps and stuff, but for the purposes of this little +explanation it's +enough to know that it's basically just a matrix of 𝓃⨉𝓃 values between 0 and 1, and then to determine +whether a pixel (𝓍,𝓎) +is white or black, you check the brightness against the threshold value at (𝓍%𝓃,𝓎%𝓃) in the map. +</p> +</section> +<section class="text-section"> +<h2>The old way™</h2> +<p> +My first attempt at <i>realtime</i> terminal graphics with ordered dithering +(<a href="https://tv.soaos.dev/w/dzHBnPJXtDBwtSvirgwTvY" target="_blank">I put a video up at the time</a>) +ran entirely on the CPU. I pre-calculated the threshold map at the beginning of execution and ran each +frame +through a sequential function to dither it and convert it to Braille characters. +</p> +<p> +To be honest, I never noticed +any significant performance issues doing this, as you can imagine the image size required to fill a +terminal +screen is signficantly smaller than a normal window. However, I knew I could easily perform the +dithering on the GPU +as a post-processing effect, so I eventually wrote a shader to do that. In combination with another +effect I used to +add outlines to objects, I was able to significantly improve the visual fidelity of the experience. A +good example of +where the renderer was at until like a week ago can be seen in <a + href="https://tv.soaos.dev/w/9Pf2tP3PYY5pJ3Cimhqs9x" target="_blank">this video</a>. +</p> +<p> +Until now I hadn't really considered moving the text conversion to the GPU. I mean, <i>G</i>PU is for +graphics, +right? I just copied the <i>entire framebuffer</i> back onto the CPU after dithering +and used the same sequential conversion algorithm. Then I had an idea that would drastically reduce the +amount +of copying necessary. +</p> +</section> +<section class="text-section"> +<h2>Compute post-processing</h2> +<p> +What if, instead of extracting and copying the framebuffer every single frame, we "rendered" the text on +the GPU +and read <i>that</i> back instead? Assuming each pixel in a texture is 32 bits (RGBA8), and knowing that +each braille +character is a block of 8 pixels, could we not theoretically shave off <i>at least</i> 7/8 of the bytes +copied? +</p> +<p> +As it turns out, it's remarkably easy to do. I'm using the <a href="https://bevy.org" + target="_blank">Bevy engine</a>, +and hooking in a compute node to my existing post-processing render pipeline worked right out of the +box. +I allocated a storage buffer large enough to hold the necessary amount of characters, read it back each +frame, and dumped +the contents into the terminal. +</p> +<p> +I used UTF-32 encoding on the storage buffer because I knew I could easily convert a "wide string" into +UTF-8 before printing it, and +32 bits provides a consistent space to fill for each workgroup in the shader versus a variable-length + encoding like UTF-8. <a href="https://tv.soaos.dev/w/fBnDAUPsTPHaoPeNNxBGch" target="_blank">Here's a video of the new renderer working</a>. +Although now that I think about it, I could probably switch to using UTF-16 since all the Braille +characters could be represented +in 2 bytes, and that would be half the size of the UTF-32 text, which is half empty bytes anyways. +</p> +<p> +Okay so I went and tried that but remembered that shaders only accept 32-bit primitive types, so it doesn't matter anyways. This little side quest has been a part of my +broader efforts to revive a project I +spent a lot of time on. I'm taking the opportunity to really dig in and rework some of the stuff I'm not +totally happy with. So there might be quite a few of this kind of post in the near future. Stay tuned. +</p> +</section> diff --git a/content/heaven/_index.md b/content/heaven/_index.md new file mode 100644 index 0000000..ce3b970 --- /dev/null +++ b/content/heaven/_index.md @@ -0,0 +1,4 @@ ++++ +title = "heaven" +template = "heaven.html" ++++
\ No newline at end of file diff --git a/heaven/angel.gif b/content/heaven/angel.gif Binary files differindex 1c591e8..1c591e8 100644 --- a/heaven/angel.gif +++ b/content/heaven/angel.gif diff --git a/heaven/angel2.gif b/content/heaven/angel2.gif Binary files differindex 54adc92..54adc92 100644 --- a/heaven/angel2.gif +++ b/content/heaven/angel2.gif diff --git a/heaven/angel3.gif b/content/heaven/angel3.gif Binary files differindex e6aedcb..e6aedcb 100644 --- a/heaven/angel3.gif +++ b/content/heaven/angel3.gif diff --git a/heaven/bg.jpg b/content/heaven/bg.jpg Binary files differindex 91a9238..91a9238 100644 --- a/heaven/bg.jpg +++ b/content/heaven/bg.jpg diff --git a/heaven/everytime_we_touch_nightcore.ogg b/content/heaven/everytime_we_touch_nightcore.ogg Binary files differindex 3ac875d..3ac875d 100644 --- a/heaven/everytime_we_touch_nightcore.ogg +++ b/content/heaven/everytime_we_touch_nightcore.ogg diff --git a/heaven/heaven.css b/content/heaven/heaven.css index 5968745..5968745 100644 --- a/heaven/heaven.css +++ b/content/heaven/heaven.css diff --git a/hell/Flying_Skeleton_Hell.gif b/content/hell/Flying_Skeleton_Hell.gif Binary files differindex 7cfd429..7cfd429 100644 --- a/hell/Flying_Skeleton_Hell.gif +++ b/content/hell/Flying_Skeleton_Hell.gif diff --git a/content/hell/_index.md b/content/hell/_index.md new file mode 100644 index 0000000..310e63e --- /dev/null +++ b/content/hell/_index.md @@ -0,0 +1,70 @@ ++++ +title = "soaos" +template = "hell.html" ++++ + +<div> +<details class="evil centered"> +<summary> + <img src="/hell/hot.gif" /> (please enable autoplay to hear music lol) +</summary> +<audio autoplay loop controls> + <source src="/hell/hell.ogg" /> +</audio> +</details> +<h1 class="evil centered">👿 WELLCOME INTO HELL... YOU SUCKER!!!!! 👿</h1> +<div class="center-content"> +<img src="/hell/firebreak.gif" /> +</div> +<div class="center-content"> +<div class="column"> + <section style="max-width: 640px; position: relative; margin: 4em"> + <img src="/hell/bigguy.gif" style="float: right" /> + <p class="evil bold"> + Hey there pal. It's me haha... the "big" "guy"... AKA satan... I + hope you like it here in hell, i worked hard on making it evil :D + </p> + <p class="evil bold"> + idk what you did to wind up here, but now you're stuck here... + forever... with me haha ;) + </p> + <p class="evil bold"> + so... Make yourself comfortable... haha if you can lol >:] + </p> + <p class="evil bold"> + AND DON'T EFFING TRY ESCAPING!!! }:[ alright lucifer out + </p> +<a + id="inverted-cross" + class="huge" + style="position: absolute; bottom: -0.5em; right: -0.5em" + href="/heaven" + >✝</a +> +</section> +<img src="/hell/firebreak.gif" /> +<div style="position: relative"> +<img src="/hell/Flying_Skeleton_Hell.gif" /> +<img + src="/hell/hellisreal.gif" + style="position: absolute; top: 0; left: -200px; rotate: -15deg" +/> +</div> +</div> +</div> +<div> +<img src="/hell/demon.gif" /> +<img src="/hell/demon2.gif" /> +<img src="/hell/demon3.gif" /> +<img src="/hell/demon4.gif" /> +<img src="/hell/demon_face.gif" /> +<img src="/hell/skull.gif" /> +<img src="/hell/evilorb.gif" /> +<img src="/hell/pitchfork.gif" /> +<img src="/hell/smallfire.gif" /> +<img src="/hell/evilmind.gif" /> +</div> +<div class="huge evil relative"> +<s class="struck">F*CK</s>SCREW YOU, SUCKER 💔🥀 +</div> +</div> diff --git a/hell/bg.jpg b/content/hell/bg.jpg Binary files differindex 8d0fad5..8d0fad5 100644 --- a/hell/bg.jpg +++ b/content/hell/bg.jpg diff --git a/hell/bigguy.gif b/content/hell/bigguy.gif Binary files differindex c3314c7..c3314c7 100644 --- a/hell/bigguy.gif +++ b/content/hell/bigguy.gif diff --git a/hell/comunismo.gif b/content/hell/comunismo.gif Binary files differindex 18da593..18da593 100644 --- a/hell/comunismo.gif +++ b/content/hell/comunismo.gif diff --git a/hell/demon.gif b/content/hell/demon.gif Binary files differindex a5868d2..a5868d2 100644 --- a/hell/demon.gif +++ b/content/hell/demon.gif diff --git a/hell/demon2.gif b/content/hell/demon2.gif Binary files differindex ebbd919..ebbd919 100644 --- a/hell/demon2.gif +++ b/content/hell/demon2.gif diff --git a/hell/demon3.gif b/content/hell/demon3.gif Binary files differindex fc73814..fc73814 100644 --- a/hell/demon3.gif +++ b/content/hell/demon3.gif diff --git a/hell/demon4.gif b/content/hell/demon4.gif Binary files differindex debe626..debe626 100644 --- a/hell/demon4.gif +++ b/content/hell/demon4.gif diff --git a/hell/demon_face.gif b/content/hell/demon_face.gif Binary files differindex 5c71e60..5c71e60 100644 --- a/hell/demon_face.gif +++ b/content/hell/demon_face.gif diff --git a/hell/evilmind.gif b/content/hell/evilmind.gif Binary files differindex 1b6cb9b..1b6cb9b 100644 --- a/hell/evilmind.gif +++ b/content/hell/evilmind.gif diff --git a/hell/evilorb.gif b/content/hell/evilorb.gif Binary files differindex 003ef97..003ef97 100644 --- a/hell/evilorb.gif +++ b/content/hell/evilorb.gif diff --git a/hell/firebreak.gif b/content/hell/firebreak.gif Binary files differindex 981f1bf..981f1bf 100644 --- a/hell/firebreak.gif +++ b/content/hell/firebreak.gif diff --git a/hell/gay.gif b/content/hell/gay.gif Binary files differindex 156bbea..156bbea 100644 --- a/hell/gay.gif +++ b/content/hell/gay.gif diff --git a/hell/gay2.gif b/content/hell/gay2.gif Binary files differindex 1a00852..1a00852 100644 --- a/hell/gay2.gif +++ b/content/hell/gay2.gif diff --git a/hell/gaydudes.gif b/content/hell/gaydudes.gif Binary files differindex 65c4ed3..65c4ed3 100644 --- a/hell/gaydudes.gif +++ b/content/hell/gaydudes.gif diff --git a/hell/hell.ogg b/content/hell/hell.ogg Binary files differindex 0abf641..0abf641 100644 --- a/hell/hell.ogg +++ b/content/hell/hell.ogg diff --git a/hell/hellisreal.gif b/content/hell/hellisreal.gif Binary files differindex acbd894..acbd894 100644 --- a/hell/hellisreal.gif +++ b/content/hell/hellisreal.gif diff --git a/hell/hitler.gif b/content/hell/hitler.gif Binary files differindex 4edc5d8..4edc5d8 100644 --- a/hell/hitler.gif +++ b/content/hell/hitler.gif diff --git a/hell/hitler2.gif b/content/hell/hitler2.gif Binary files differindex 7fd7132..7fd7132 100644 --- a/hell/hitler2.gif +++ b/content/hell/hitler2.gif diff --git a/hell/hitler3.gif b/content/hell/hitler3.gif Binary files differindex 8edd36c..8edd36c 100644 --- a/hell/hitler3.gif +++ b/content/hell/hitler3.gif diff --git a/hell/hot.gif b/content/hell/hot.gif Binary files differindex 4c1660f..4c1660f 100644 --- a/hell/hot.gif +++ b/content/hell/hot.gif diff --git a/hell/kissing.jpg b/content/hell/kissing.jpg Binary files differindex b6190c6..b6190c6 100644 --- a/hell/kissing.jpg +++ b/content/hell/kissing.jpg diff --git a/hell/obama.gif b/content/hell/obama.gif Binary files differindex 4f42d27..4f42d27 100644 --- a/hell/obama.gif +++ b/content/hell/obama.gif diff --git a/hell/pitchfork.gif b/content/hell/pitchfork.gif Binary files differindex ab880fa..ab880fa 100644 --- a/hell/pitchfork.gif +++ b/content/hell/pitchfork.gif diff --git a/hell/redfire.gif b/content/hell/redfire.gif Binary files differindex 8f15a7a..8f15a7a 100644 --- a/hell/redfire.gif +++ b/content/hell/redfire.gif diff --git a/hell/skull.gif b/content/hell/skull.gif Binary files differindex 89ed718..89ed718 100644 --- a/hell/skull.gif +++ b/content/hell/skull.gif diff --git a/hell/smallfire.gif b/content/hell/smallfire.gif Binary files differindex 7b29fdf..7b29fdf 100644 --- a/hell/smallfire.gif +++ b/content/hell/smallfire.gif diff --git a/hell/torch.gif b/content/hell/torch.gif Binary files differindex c06066b..c06066b 100644 --- a/hell/torch.gif +++ b/content/hell/torch.gif diff --git a/projects/bevy_plugins/index.html b/content/projects/bevy_plugins/index.html index 5f5c427..5f5c427 100644 --- a/projects/bevy_plugins/index.html +++ b/content/projects/bevy_plugins/index.html diff --git a/projects/games/NIX_AVREA/index.html b/content/projects/games/NIX_AVREA/index.html index 47c914b..47c914b 100644 --- a/projects/games/NIX_AVREA/index.html +++ b/content/projects/games/NIX_AVREA/index.html diff --git a/projects/piss_daemon/index.html b/content/projects/piss_daemon/index.html index 5cb9481..5cb9481 100644 --- a/projects/piss_daemon/index.html +++ b/content/projects/piss_daemon/index.html diff --git a/projects/piss_daemon/statusbar.png b/content/projects/piss_daemon/statusbar.png Binary files differindex b98e021..b98e021 100644 --- a/projects/piss_daemon/statusbar.png +++ b/content/projects/piss_daemon/statusbar.png diff --git a/projects/project.css b/content/projects/project.css index e69de29..e69de29 100644 --- a/projects/project.css +++ b/content/projects/project.css diff --git a/rockstats/index.html b/content/rockstats/index.html index cfd916c..cfd916c 100644 --- a/rockstats/index.html +++ b/content/rockstats/index.html diff --git a/things_i_like/music/867.png b/content/things_i_like/music/867.png Binary files differindex d416100..d416100 100644 --- a/things_i_like/music/867.png +++ b/content/things_i_like/music/867.png diff --git a/things_i_like/music/act_ii.jpg b/content/things_i_like/music/act_ii.jpg Binary files differindex 97b2e82..97b2e82 100644 --- a/things_i_like/music/act_ii.jpg +++ b/content/things_i_like/music/act_ii.jpg diff --git a/things_i_like/music/apollo.jpg b/content/things_i_like/music/apollo.jpg Binary files differindex 9c742ee..9c742ee 100644 --- a/things_i_like/music/apollo.jpg +++ b/content/things_i_like/music/apollo.jpg diff --git a/things_i_like/music/atebts.jpg b/content/things_i_like/music/atebts.jpg Binary files differindex ca68692..ca68692 100644 --- a/things_i_like/music/atebts.jpg +++ b/content/things_i_like/music/atebts.jpg diff --git a/things_i_like/music/departure_songs.jpg b/content/things_i_like/music/departure_songs.jpg Binary files differindex 2699d5d..2699d5d 100644 --- a/things_i_like/music/departure_songs.jpg +++ b/content/things_i_like/music/departure_songs.jpg diff --git a/things_i_like/music/index.html b/content/things_i_like/music/index.html index 46ebc1f..46ebc1f 100644 --- a/things_i_like/music/index.html +++ b/content/things_i_like/music/index.html diff --git a/things_i_like/music/jiminy.jpg b/content/things_i_like/music/jiminy.jpg Binary files differindex a216de4..a216de4 100644 --- a/things_i_like/music/jiminy.jpg +++ b/content/things_i_like/music/jiminy.jpg diff --git a/things_i_like/music/lysf.jpg b/content/things_i_like/music/lysf.jpg Binary files differindex 8605559..8605559 100644 --- a/things_i_like/music/lysf.jpg +++ b/content/things_i_like/music/lysf.jpg diff --git a/things_i_like/music/twin_fantasy.jpg b/content/things_i_like/music/twin_fantasy.jpg Binary files differindex f83e135..f83e135 100644 --- a/things_i_like/music/twin_fantasy.jpg +++ b/content/things_i_like/music/twin_fantasy.jpg diff --git a/things_i_like/music/wetdream.png b/content/things_i_like/music/wetdream.png Binary files differindex 862544c..862544c 100644 --- a/things_i_like/music/wetdream.png +++ b/content/things_i_like/music/wetdream.png diff --git a/hell/hell.css b/hell/hell.css deleted file mode 100644 index 31119ed..0000000 --- a/hell/hell.css +++ /dev/null @@ -1,66 +0,0 @@ -html { - background: url("bg.jpg"); - background-attachment: fixed; - background-size: cover; - image-rendering: pixelated; -} - -@keyframes fire -{ -0% {text-shadow: 0 0 20px #fefcc9, - 10px -10px 30px #feec85, - -20px -20px 40px #ffae34, - 20px -40px 50px #ec760c, - -20px -60px 60px #cd4606, - 0 -80px 70px #973716, - 10px -90px 80px #451b0e;} -100% {text-shadow: 0 0 20px #fefcc9, - 10px -10px 30px #fefcc9, - -20px -20px 40px #feec85, - 22px -42px 60px #ffae34, - -22px -58px 50px #ec760c, - 0 -82px 80px #cd4606, - 10px -90px 80px #973716;} -} - -.hellfire { - color: var(--orange); - animation: fire 1s ease-in-out infinite alternate; -} - -massive-fucking-background-flame { - width: 100%; - height: 100%; - position: fixed; - top: 0; - left: 0; - z-index: -999; - background-image: url("smallfire.gif"); - background-size: auto 128px; - opacity: 0.25; - --pan: 0 -128px; - animation: pan linear 3s infinite -} - -a { - color: black; -} - -#inverted-cross { - display: inline-block; - rotate: 180deg; - transition: rotate 2s, color 0.5s; - --glow-color: red; - animation: glow 4s linear infinite; -} - -#inverted-cross:hover { - rotate: 0deg; - --glow-color: white; - color: var(--yellow); - transition: rotate 2s, color 1s; -} - -.evil { - color: black; -} diff --git a/hell/index.html b/hell/index.html deleted file mode 100644 index c03f0d0..0000000 --- a/hell/index.html +++ /dev/null @@ -1,75 +0,0 @@ -<!DOCTYPE html> - -<html lang="en"> - -<head> - <title>hell</title> - <link rel="icon" href="smallfire.gif"> - <link rel="preload" href="/assets/unifont.woff2" as="font" type="font/woff2"> - <link rel="preload" href="/assets/unifont_upper.woff2" as="font" type="font/woff2"> - <link rel="stylesheet" href="/style.css"> - <link rel="stylesheet" href="hell.css"> - <meta charset="UTF-8"> -</head> - -<body> - <massive-fucking-background-flame></massive-fucking-background-flame> - <div> - <details class="evil centered"> - <summary> <img src="hot.gif" /> - (please enable autoplay to hear music lol)</summary> - <audio autoplay loop controls> - <source src="hell.ogg"> - </audio> - </details> - <h1 class="evil centered">👿 WELLCOME INTO HELL... YOU SUCKER!!!!! 👿</h1> - <div class="center-content"> - <img src="firebreak.gif" /> - </div> - <div class="center-content"> - <div class="column"> - - <section style="max-width: 640px; position: relative; margin: 4em"> - <img src="bigguy.gif" style="float: right;" /> - <p class="evil bold"> - Hey there pal. It's me haha... the "big" "guy"... AKA satan... I hope you like it here in - hell, i worked hard on - making it evil :D - </p> - <p class="evil bold"> - idk what you did to wind up here, but now you're stuck here... forever... with me haha ;) - </p> - <p class="evil bold"> - so... Make yourself comfortable... haha if you can lol >:] - </p> - <p class="evil bold">AND DON'T EFFING TRY ESCAPING!!! }:[ alright lucifer out</p> - - <a id="inverted-cross" class="huge" style="position: absolute; bottom: -0.5em; right: -0.5em;" - href="/heaven">✝</a> - </section> - <img src="firebreak.gif" /> - <div style="position: relative;"> - <img src="Flying_Skeleton_Hell.gif" /> - <img src="hellisreal.gif" style="position: absolute; top: 0; left: -200px; rotate: -15deg;"/> - </div> - </div> - </div> - <div> - <img src="demon.gif" /> - <img src="demon2.gif" /> - <img src="demon3.gif" /> - <img src="demon4.gif" /> - <img src="demon_face.gif" /> - <img src="skull.gif" /> - <img src="evilorb.gif" /> - <img src="pitchfork.gif" /> - <img src="smallfire.gif" /> - <img src="evilmind.gif" /> - </div> - <div class="huge evil section centered"> - <s class="struck">F*CK</s>SCREW YOU, SUCKER 💔🥀 - </div> - </div> -</body> - -</html> diff --git a/index.html b/index.html deleted file mode 100644 index 42a9d2a..0000000 --- a/index.html +++ /dev/null @@ -1,105 +0,0 @@ -<!DOCTYPE html> -<!-- Hey haha, yeah I wasn't kidding, I'm writing this whole site by hand. --> - -<html lang="en"> - -<head> - <title>soaos</title> - <link rel="icon" href="favicon.png" /> - <link rel="preload" href="/assets/unifont.woff2" as="font" type="font/woff2" /> - <link rel="preload" href="/assets/unifont_upper.woff2" as="font" type="font/woff2" /> - <link rel="stylesheet" href="/style.css" /> - <link rel="stylesheet" href="/index.css" /> - <meta charset="UTF-8" /> -</head> - -<body> - <div class="section"> - <div class="center-content"> - <div id="weird-fucking-header-container" title="🦌 -wuh"> - <h1 class="half whatever">𐂂🌲🌲🌲 soaos 🌲🌲🌲𐂂</h1> - <h1 class="flip-x half whatever">𐂂🌲🌲🌲 soaos 🌲🌲🌲𐂂</h1> - </div> - </div> - <p class="centered"> - I'm an "artist" and professional software developer. - In my free time I mostly work on eccentric software projects which you can - read about here. - </p> - </div> - - <div class="section"> - <div class="horizontal-container"> - <div class="centered"> - <h2>Stuff on this Site</h2> - <ul class="no-bullets inline-block"> - <!-- <li> - <a href="/projects">☿ Projects</a><span class="under-construction unselectable" - title="🦌 -this shit is under construction, pal!"> ⚠</span> - </li> --> - <li> - <a href="/blog">📖 Blog</a><span class="under-construction unselectable" - title="🦌 -this shit is under construction!"> ⚠</span> - </li> - <li> - <a href="/things_i_like">🦌 Things I Like</a><span class="under-construction unselectable" - title="🦌 -honestly these things are redundant, everything is under construction"> ⚠</span> - </li> - <li> - <a href="/rockstats">♫ Rockbox Stats</a> - </li> - </ul> - </div> - - <div class="centered"> - <h2>Stuff on this Server</h2> - <ul class="no-bullets inline-block"> - <li><a href="https://app.radicle.xyz/nodes/seed.soaos.dev" target="_blank">🌱 Source Code</a></li> - <li><a href="https://tv.soaos.dev/c/soaosdev" target="_blank">📺 Videos</a></li> - <li><a href="https://archive.soaos.dev" target="_blank">🗃 Web Archive</a></li> - <li><a href="https://search.soaos.dev" target="_blank">🔍 Search Engine</a></li> - <li><a href="gemini://soaos.dev">♊ Gemini Site</a></li> - <!-- <li><a href="http://3uqdpspct5xocufs6d4wbdelxlqp4bmciwcgwmqv6c5f2gdsqvawgaad.onion">🧅 Tor Site</a></li>--> - </ul> - </div> - <div class="centered"> - <h2>Find Me Elsewhere</h2> - <ul class="no-bullets inline-block"> - <li><a href="mailto:soaos@soaos.dev" rel="me">📧 soaos@soaos.dev</a></li> - <li><a href="http://soaos.dog" rel="me" target="_blank">🐘 soaos@furry.engineer</a></li> - <li><a - href="https://ssb.soaos.dev/~core/ssb/#@Y1EKP4PU77qby4lI+m5MN6+NcYdjTdRQlV6NmluevuY=.ed25519" target="_blank">~😎 - soaos</a></li> - </ul> - </div> - </div> - </div> - - <div class="section centered"> - <h2>Webrings</h2> - <a class="evil" href="https://evilr.ing/soaos/previous">⛧</a> - <a class="evil" href="https://evilr.ing">EVILRING</a> - <a class="evil" href="https://evilr.ing/soaos/next">⛤</a> - </div> - - <footer class="centered"> - <h2 class="hidden">Badges</h2> - <ul id="badge-grid" class="no-bullets"> - <li><a href="https://neocities.org/" target="_blank"><img src="/assets/badges/html.gif" - alt="Learn HTML Now!" /></a></li> - <li><img src="/assets/badges/javascript.gif" alt="Javascript-Free Page" /></li> - <li><a href="https://lynx.browser.org/" target="_blank"><img src="/assets/badges/lynx.gif" - alt="Lynx Compatible" /></a></li> - <li><a href="https://www.debian.org/" target="_blank"><img src="/assets/badges/powered-by-debian.gif" - alt="Powered by Debian" /></a></li> - <li><a href="https://neovim.io/" target="_blank"><img src="/assets/badges/neovim.gif" - alt="Made with Neovim" /></a></li> - <li><a href="/hell"><img src="/assets/badges/go2hell.gif" alt="Go 2 Hell Now!" /></a></li> - </ul> - <div> - <div class="inline-block" title="🦌 -<3"><span class="red-on-white">█🍁█</span> 2025</div> - </div> - </footer> -</body> - -</html> diff --git a/static/98.css b/static/98.css new file mode 100644 index 0000000..8012d93 --- /dev/null +++ b/static/98.css @@ -0,0 +1,1040 @@ +/*! 98.css v0.1.21 - https://github.com/jdan/98.css */ +/** + * 98.css + * Copyright (c) 2020 Jordan Scales <thatjdanisso.cool> + * https://github.com/jdan/98.css/blob/main/LICENSE + */ + +@font-face { + font-family: unifont; + src: url("/assets/UnifontExMono.woff2"); +} + +:root { + /* Color */ + --text-color: var(--fg); + --surface: var(--bg2); + --button-highlight: var(--bg4); + --button-face: var(--bg3); + --button-shadow: var(--bg-dim); + --window-frame: #0a0a0a; + --dialog-blue: var(--bg-blue); + --dialog-blue-light: var(--blue); + --dialog-gray: #808080; + --dialog-gray-light: #b5b5b5; + --link-blue: var(--blue); + + /* Spacing */ + --element-spacing: 8px; + --grouped-button-spacing: 4px; + --grouped-element-spacing: 6px; + --radio-width: 12px; + --checkbox-width: 13px; + --radio-label-spacing: 6px; + --range-track-height: 4px; + --range-spacing: 10px; + + /* Some detailed computations for radio buttons and checkboxes */ + --radio-total-width-precalc: var(--radio-width) + var(--radio-label-spacing); + --radio-total-width: calc(var(--radio-total-width-precalc)); + --radio-left: calc(-1 * var(--radio-total-width-precalc)); + --radio-dot-width: 4px; + --radio-dot-top: calc(var(--radio-width) / 2 - var(--radio-dot-width) / 2); + --radio-dot-left: calc( + -1 * (var(--radio-total-width-precalc)) + var(--radio-width) / 2 - + var(--radio-dot-width) / 2 + ); + + --checkbox-total-width-precalc: var(--checkbox-width) + + var(--radio-label-spacing); + --checkbox-total-width: calc(var(--checkbox-total-width-precalc)); + --checkbox-left: calc(-1 * var(--checkbox-total-width-precalc)); + --checkmark-width: 7px; + --checkmark-left: 3px; + + /* Borders */ + --border-width: 1px; + --border-raised-outer: + inset -1px -1px var(--window-frame), inset 1px 1px var(--button-highlight); + --border-raised-inner: + inset -2px -2px var(--button-shadow), inset 2px 2px var(--button-face); + --border-sunken-outer: + inset -1px -1px var(--button-highlight), inset 1px 1px var(--window-frame); + --border-sunken-inner: + inset -2px -2px var(--button-face), inset 2px 2px var(--button-shadow); + --default-button-border-raised-outer: + inset -2px -2px var(--window-frame), inset 1px 1px var(--window-frame); + --default-button-border-raised-inner: + inset 2px 2px var(--button-highlight), inset -3px -3px var(--button-shadow), + inset 3px 3px var(--button-face); + --default-button-border-sunken-outer: + inset 2px 2px var(--window-frame), inset -1px -1px var(--window-frame); + --default-button-border-sunken-inner: + inset -2px -2px var(--button-highlight), inset 3px 3px var(--button-shadow), + inset -3px -3px var(--button-face); + + /* Window borders flip button-face and button-highlight */ + --border-window-outer: + inset -1px -1px var(--window-frame), inset 1px 1px var(--button-face); + --border-window-inner: + inset -2px -2px var(--button-shadow), inset 2px 2px var(--button-highlight); + + /* Field borders (checkbox, input, etc) flip window-frame and button-shadow */ + --border-field: + inset -1px -1px var(--button-highlight), inset 1px 1px var(--button-shadow), + inset -2px -2px var(--button-face), inset 2px 2px var(--window-frame); + --border-status-field: + inset -1px -1px var(--button-face), inset 1px 1px var(--button-shadow); + + /* Tabs */ + --border-tab: + inset -1px 0 var(--window-frame), inset 1px 1px var(--button-face), + inset -2px 0 var(--button-shadow), inset 2px 2px var(--button-highlight); +} + +@font-face { + font-family: "Pixelated MS Sans Serif"; + src: url("fonts/converted/ms_sans_serif.woff") format("woff"); + src: url("fonts/converted/ms_sans_serif.woff2") format("woff2"); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: "Pixelated MS Sans Serif"; + src: url("fonts/converted/ms_sans_serif_bold.woff") format("woff"); + src: url("fonts/converted/ms_sans_serif_bold.woff2") format("woff2"); + font-weight: bold; + font-style: normal; +} + +body { + font-family: unifont; + font-size: 1rem; + color: var(--text-color); +} + +button, +label, +input, +legend, +textarea, +select, +option, +table, +ul.tree-view, +.window, +.title-bar, +li[role="tab"] { + font-family: unifont; + -webkit-font-smoothing: none; + font-size: 1rem; +} + +h1 { + font-size: 2rem; +} + +h2 { + font-size: 1rem; +} + +h3 { + font-size: 1rem; +} + +h4 { + font-size: 1rem; +} + +u { + text-decoration: none; + border-bottom: 0.5px solid #222222; +} + +button, +input[type="submit"], +input[type="reset"] { + box-sizing: border-box; + border: none; + color: transparent; + text-shadow: 0 0 var(--text-color); + background: var(--surface); + box-shadow: var(--border-raised-outer), var(--border-raised-inner); + border-radius: 0; + + min-width: 75px; + min-height: 23px; + padding: 0 12px; +} + +button.default, +input[type="submit"].default, +input[type="reset"].default { + box-shadow: + var(--default-button-border-raised-outer), + var(--default-button-border-raised-inner); +} + +.vertical-bar { + width: 4px; + height: 20px; + background: #c0c0c0; + box-shadow: var(--border-raised-outer), var(--border-raised-inner); +} + +button:not(:disabled):active, +input[type="submit"]:not(:disabled):active, +input[type="reset"]:not(:disabled):active { + box-shadow: var(--border-sunken-outer), var(--border-sunken-inner); + text-shadow: 1px 1px var(--text-color); +} + +button.default:not(:disabled):active, +input[type="submit"].default:not(:disabled):active, +input[type="reset"].default:not(:disabled):active { + box-shadow: + var(--default-button-border-sunken-outer), + var(--default-button-border-sunken-inner); +} + +@media (not(hover)) { + button:not(:disabled):hover, + input[type="submit"]:not(:disabled):hover, + input[type="reset"]:not(:disabled):hover { + box-shadow: var(--border-sunken-outer), var(--border-sunken-inner); + } +} + +button:focus, +input[type="submit"]:focus, +input[type="reset"]:focus { + outline: 1px dotted #000000; + outline-offset: -4px; +} + +button::-moz-focus-inner, +input[type="submit"]::-moz-focus-inner, +input[type="reset"]::-moz-focus-inner { + border: 0; +} + +:disabled, +:disabled + label, +input[readonly], +input[readonly] + label { + color: var(--button-shadow); +} + +button:disabled, +input[type="submit"]:disabled, +input[type="reset"]:disabled, +:disabled + label { + text-shadow: 1px 1px 0 var(--button-highlight); +} + +.window { + box-shadow: var(--border-window-outer), var(--border-window-inner); + background: var(--surface); + padding: 3px; +} + +.title-bar { + background: linear-gradient(90deg, var(--bg0), var(--green)); + padding: 3px 2px 3px 3px; + display: flex; + justify-content: space-between; + align-items: center; +} + +.title-bar.inactive { + background: linear-gradient( + 90deg, + var(--dialog-gray), + var(--dialog-gray-light) + ); +} + +.title-bar-text { + font-weight: bold; + color: var(--fg); + letter-spacing: 0; + margin-right: 24px; +} + +.title-bar-controls { + display: flex; +} + +.title-bar-controls button { + padding: 0; + display: block; + min-width: 16px; + min-height: 14px; +} + +.title-bar-controls button:active { + padding: 0; +} + +.title-bar-controls button:focus { + outline: none; +} + +.title-bar-controls button[aria-label="Minimize"], +.title-bar-controls button[aria-label].minimize { + background-image: url("/assets/icon/minimize.svg"); + background-repeat: no-repeat; + background-position: bottom 3px left 4px; +} + +.title-bar-controls button[aria-label="Maximize"], +.title-bar-controls button[aria-label].maximize { + background-image: url("/assets/icon/maximize.svg"); + background-repeat: no-repeat; + background-position: top 2px left 3px; +} + +.title-bar-controls button[aria-label="Maximize"]:disabled, +.title-bar-controls button[aria-label].maximize:disabled { + background-image: url("/assets/icon/maximize-disabled.svg"); + background-repeat: no-repeat; + background-position: top 2px left 3px; +} + +.title-bar-controls button[aria-label="Restore"], +.title-bar-controls button[aria-label].restore { + background-image: url("/assets/icon/restore.svg"); + background-repeat: no-repeat; + background-position: top 2px left 3px; +} + +.title-bar-controls button[aria-label="Help"], +.title-bar-controls button[aria-label].help { + background-image: url("/assets/icon/help.svg"); + background-repeat: no-repeat; + background-position: top 2px left 5px; +} + +.title-bar-controls button[aria-label="Close"], +.title-bar-controls button[aria-label].close { + margin-left: 2px; + background-image: url("/assets/icon/close.svg"); + background-repeat: no-repeat; + background-position: top 3px left 4px; +} + +.status-bar { + margin: 0px 1px; + display: flex; + gap: 1px; +} + +.status-bar-field { + box-shadow: var(--border-status-field); + flex-grow: 1; + padding: 2px 3px; + margin: 0; +} + +.window-body { + margin: var(--element-spacing); +} + +fieldset { + border-image: url("/assets/icon/groupbox-border.svg") 2; + padding: calc(2 * var(--border-width) + var(--element-spacing)); + padding-block-start: var(--element-spacing); + margin: 0; +} + +legend { + background: var(--surface); +} + +.field-row { + display: flex; + align-items: center; +} + +[class^="field-row"] + [class^="field-row"] { + margin-top: var(--grouped-element-spacing); +} + +.field-row > * + * { + margin-left: var(--grouped-element-spacing); +} + +.field-row-stacked { + display: flex; + flex-direction: column; +} + +.field-row-stacked * + * { + margin-top: var(--grouped-element-spacing); +} + +label { + display: inline-flex; + align-items: center; + user-select: none; +} + +input[type="radio"], +input[type="checkbox"] { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + margin: 0; + background: 0; + position: fixed; + opacity: 0; + border: none; +} + +input[type="radio"] + label, +input[type="checkbox"] + label { + line-height: 13px; +} + +input[type="radio"] + label { + position: relative; + margin-left: var(--radio-total-width); +} + +input[type="radio"] + label::before { + content: ""; + position: absolute; + top: 0; + left: calc(-1 * (var(--radio-total-width-precalc))); + display: inline-block; + width: var(--radio-width); + height: var(--radio-width); + margin-right: var(--radio-label-spacing); + background: url("/assets/icon/radio-border.svg"); +} + +input[type="radio"]:active + label::before { + background: url("/assets/icon/radio-border-disabled.svg"); +} + +input[type="radio"]:checked + label::after { + content: ""; + display: block; + width: var(--radio-dot-width); + height: var(--radio-dot-width); + top: var(--radio-dot-top); + left: var(--radio-dot-left); + position: absolute; + background: url("/assets/icon/radio-dot.svg"); +} + +input[type="radio"]:focus + label, +input[type="checkbox"]:focus + label { + outline: 1px dotted #000000; +} + +input[type="radio"][disabled] + label::before { + background: url("/assets/icon/radio-border-disabled.svg"); +} + +input[type="radio"][disabled]:checked + label::after { + background: url("/assets/icon/radio-dot-disabled.svg"); +} + +input[type="checkbox"] + label { + position: relative; + margin-left: var(--checkbox-total-width); +} + +input[type="checkbox"] + label::before { + content: ""; + position: absolute; + left: calc(-1 * (var(--checkbox-total-width-precalc))); + display: inline-block; + width: var(--checkbox-width); + height: var(--checkbox-width); + background: var(--button-highlight); + box-shadow: var(--border-field); + margin-right: var(--radio-label-spacing); +} + +input[type="checkbox"]:active + label::before { + background: var(--surface); +} + +input[type="checkbox"]:checked + label::after { + content: ""; + display: block; + width: var(--checkmark-width); + height: var(--checkmark-width); + position: absolute; + left: calc( + -1 * (var(--checkbox-total-width-precalc)) + var(--checkmark-left) + ); + background: url("/assets/icon/checkmark.svg"); +} + +input[type="checkbox"][disabled] + label::before { + background: var(--surface); +} + +input[type="checkbox"][disabled]:checked + label::after { + background: url("/assets/icon/checkmark-disabled.svg"); +} + +input[type="text"], +input[type="password"], +input[type="email"], +input[type="url"], +input[type="tel"], +input[type="number"], +input[type="search"], +select, +textarea { + padding: 3px 4px; + border: none; + box-shadow: var(--border-field); + background-color: var(--button-highlight); + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 0; +} + +input[type="text"], +input[type="password"], +input[type="email"], +input[type="url"], +input[type="tel"], +input[type="search"], +select { + height: 21px; +} +input[type="number"] { + /* need this 1 pixel to fit the spinner controls in box */ + height: 22px; +} +/* clears the ‘X’ from Internet Explorer */ +input[type="search"]::-ms-clear { + display: none; + width: 0; + height: 0; +} +input[type="search"]::-ms-reveal { + display: none; + width: 0; + height: 0; +} +/* clears the ‘X’ from Chrome */ +input[type="search"]::-webkit-search-decoration, +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-results-button, +input[type="search"]::-webkit-search-results-decoration { + display: none; +} + +input[type="text"], +input[type="password"], +input[type="email"], +input[type="url"], +input[type="tel"], +input[type="number"], +input[type="search"] { + /* For some reason descenders are getting cut off without this */ + line-height: 2; +} + +input[type="email"]:disabled, +input[type="url"]:disabled, +input[type="tel"]:disabled, +input[type="password"]:disabled, +input[type="text"]:disabled, +input[type="number"]:disabled, +input[type="search"]:disabled, +input[type="email"]:read-only, +input[type="url"]:read-only, +input[type="tel"]:read-only, +input[type="password"]:read-only, +input[type="text"]:read-only, +input[type="number"]:read-only, +input[type="search"]:read-only, +textarea:disabled { + background-color: var(--surface); +} + +select { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + position: relative; + padding-right: 32px; + background-image: url("/assets/icon/button-down.svg"); + background-position: top 2px right 2px; + background-repeat: no-repeat; + border-radius: 0; +} + +select:focus, +input[type="text"]:focus, +input[type="password"]:focus, +input[type="email"]:focus, +input[type="url"]:focus, +input[type="tel"]:focus, +input[type="number"]:focus, +input[type="search"]:focus, +textarea:focus { + outline: none; +} + +input[type="range"] { + -webkit-appearance: none; + width: 100%; + background: transparent; +} + +input[type="range"]:focus { + outline: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + height: 21px; + width: 11px; + background: url("/assets/icon/indicator-horizontal.svg"); + transform: translateY(-8px); + box-shadow: none; + border: none; +} + +input[type="range"].has-box-indicator::-webkit-slider-thumb { + background: url("/assets/icon/indicator-rectangle-horizontal.svg"); + transform: translateY(-10px); +} + +input[type="range"]::-moz-range-thumb { + height: 21px; + width: 11px; + border: 0; + border-radius: 0; + background: url("/assets/icon/indicator-horizontal.svg"); + transform: translateY(2px); +} + +input[type="range"].has-box-indicator::-moz-range-thumb { + background: url("/assets/icon/indicator-rectangle-horizontal.svg"); + transform: translateY(0px); +} + +input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 2px; + box-sizing: border-box; + background: black; + border-right: 1px solid grey; + border-bottom: 1px solid grey; + box-shadow: + 1px 0 0 white, + 1px 1px 0 white, + 0 1px 0 white, + -1px 0 0 darkgrey, + -1px -1px 0 darkgrey, + 0 -1px 0 darkgrey, + -1px 1px 0 white, + 1px -1px darkgrey; +} + +input[type="range"]::-moz-range-track { + width: 100%; + height: 2px; + box-sizing: border-box; + background: black; + border-right: 1px solid grey; + border-bottom: 1px solid grey; + box-shadow: + 1px 0 0 white, + 1px 1px 0 white, + 0 1px 0 white, + -1px 0 0 darkgrey, + -1px -1px 0 darkgrey, + 0 -1px 0 darkgrey, + -1px 1px 0 white, + 1px -1px darkgrey; +} + +.is-vertical { + display: inline-block; + width: 4px; + height: 150px; + transform: translateY(50%); +} + +.is-vertical > input[type="range"] { + width: 150px; + height: 4px; + margin: 0 calc(var(--grouped-element-spacing) + var(--range-spacing)) 0 + var(--range-spacing); + transform-origin: left; + transform: rotate(270deg) translateX(calc(-50% + var(--element-spacing))); +} + +.is-vertical > input[type="range"]::-webkit-slider-runnable-track { + border-left: 1px solid grey; + border-right: 0; + border-bottom: 1px solid grey; + box-shadow: + -1px 0 0 white, + -1px 1px 0 white, + 0 1px 0 white, + 1px 0 0 darkgrey, + 1px -1px 0 darkgrey, + 0 -1px 0 darkgrey, + 1px 1px 0 white, + -1px -1px darkgrey; +} + +.is-vertical > input[type="range"]::-moz-range-track { + border-left: 1px solid grey; + border-right: 0; + border-bottom: 1px solid grey; + box-shadow: + -1px 0 0 white, + -1px 1px 0 white, + 0 1px 0 white, + 1px 0 0 darkgrey, + 1px -1px 0 darkgrey, + 0 -1px 0 darkgrey, + 1px 1px 0 white, + -1px -1px darkgrey; +} + +.is-vertical > input[type="range"]::-webkit-slider-thumb { + transform: translateY(-8px) scaleX(-1); +} + +.is-vertical > input[type="range"].has-box-indicator::-webkit-slider-thumb { + transform: translateY(-10px) scaleX(-1); +} + +.is-vertical > input[type="range"]::-moz-range-thumb { + transform: translateY(2px) scaleX(-1); +} + +.is-vertical > input[type="range"].has-box-indicator::-moz-range-thumb { + transform: translateY(0px) scaleX(-1); +} + +select:focus { + color: var(--button-highlight); + background-color: var(--dialog-blue); +} +select:focus option { + color: #000; + background-color: #fff; +} + +select:active { + background-image: url("/assets/icon/button-down-active.svg"); +} + +a { + color: var(--link-blue); +} + +a:focus { + outline: 1px dotted var(--link-blue); +} + +ul.tree-view { + display: block; + background: var(--button-highlight); + /* box-shadow: var(--border-field); */ + padding: 6px; + margin: 0; +} + +ul.tree-view li { + list-style-type: none; +} + +ul.tree-view a { + text-decoration: none; + color: var(--blue); +} + +ul.tree-view a:focus { + background-color: var(--dialog-blue); + color: var(--button-highlight); +} + +ul.tree-view ul, +ul.tree-view li { + margin-top: 3px; +} + +ul.tree-view ul { + margin-left: 16px; + padding-left: 16px; + /* Goes down too far */ + border-left: 1px dotted #808080; +} + +ul.tree-view ul > li { + position: relative; +} +ul.tree-view ul > li::before { + content: ""; + display: block; + position: absolute; + left: -16px; + top: 6px; + width: 12px; + border-bottom: 1px dotted #808080; +} + +/* Cover the bottom of the left dotted border */ +ul.tree-view ul > li:last-child::after { + content: ""; + display: block; + position: absolute; + left: -20px; + top: 7px; + bottom: 0px; + width: 8px; + background: var(--button-highlight); +} + +ul.tree-view details { + margin-top: 0; +} + +ul.tree-view details[open] summary { + margin-bottom: 0; +} + +ul.tree-view ul details > summary:before { + margin-left: -22px; + position: relative; + z-index: 1; +} + +ul.tree-view details > summary:before { + text-align: center; + display: block; + float: left; + content: "+"; + border: 1px solid var(--bg5); + width: 11px; + height: 11px; + line-height: 10px; + margin-right: 5px; + background-color: var(--bg1); +} + +ul.tree-view details[open] > summary:before { + content: "-"; +} + +ul.tree-view details > summary::marker, +ul.tree-view details > summary::-webkit-details-marker { + content: ""; +} + +pre { + display: block; + background: var(--button-highlight); + box-shadow: var(--border-field); + padding: 12px 8px; + margin: 0; +} + +code, +code * { + font-family: monospace; +} + +summary:focus { + outline: 1px dotted #000000; +} + +::-webkit-scrollbar { + width: 16px; +} +::-webkit-scrollbar:horizontal { + height: 17px; +} + +::-webkit-scrollbar-corner { + background: var(--button-face); +} + +::-webkit-scrollbar-track { + background-image: url("/assets/icon/scrollbar-background.svg"); +} + +::-webkit-scrollbar-thumb { + background-color: var(--button-face); + box-shadow: var(--border-raised-outer), var(--border-raised-inner); +} + +::-webkit-scrollbar-button:horizontal:start:decrement, +::-webkit-scrollbar-button:horizontal:end:increment, +::-webkit-scrollbar-button:vertical:start:decrement, +::-webkit-scrollbar-button:vertical:end:increment { + display: block; +} + +::-webkit-scrollbar-button:vertical:start { + height: 17px; + background-image: url("/assets/icon/button-up.svg"); +} +::-webkit-scrollbar-button:vertical:end { + height: 17px; + background-image: url("/assets/icon/button-down.svg"); +} +::-webkit-scrollbar-button:horizontal:start { + width: 16px; + background-image: url("/assets/icon/button-left.svg"); +} +::-webkit-scrollbar-button:horizontal:end { + width: 16px; + background-image: url("/assets/icon/button-right.svg"); +} + +.window[role="tabpanel"] { + position: relative; + z-index: 2; +} + +menu[role="tablist"] { + position: relative; + margin: 0 0 -2px 0; + text-indent: 0; + list-style-type: none; + display: flex; + padding-left: 3px; +} + +menu[role="tablist"] > li { + border-top-left-radius: 3px; + border-top-right-radius: 3px; + box-shadow: var(--border-tab); + z-index: 1; +} + +menu[role="tablist"] > li[aria-selected="true"] { + padding-bottom: 2px; + margin-top: -2px; + background-color: var(--surface); + position: relative; + z-index: 8; + margin-left: -3px; +} + +menu[role="tablist"] > li > a { + display: block; + color: #222; + margin: 6px; + text-decoration: none; +} +menu[role="tablist"] > li[aria-selected="true"] > a:focus { + outline: none; +} +menu[role="tablist"] > li > a:focus { + outline: 1px dotted #222; +} + +menu[role="tablist"].multirows > li { + flex-grow: 1; + text-align: center; +} +.sunken-panel { + box-sizing: border-box; + border: 2px groove transparent; + border-image: url("/assets/icon/sunken-panel-border.svg") 2; + overflow: auto; + background-color: #fff; +} + +table { + border-collapse: collapse; + position: relative; + text-align: left; + white-space: nowrap; + background-color: var(--bg0); +} + +table > thead > tr > * { + position: sticky; + top: 0; + height: 17px; + box-shadow: var(--border-raised-outer), var(--border-raised-inner); + background: var(--surface); + box-sizing: border-box; + font-weight: normal; + padding: 0 var(--grouped-element-spacing); +} + +table.interactive > tbody > tr { + cursor: pointer; +} + +table > tbody > tr.highlighted { + color: #fff; + background-color: var(--dialog-blue); +} + +table > tbody > tr > * { + padding: 0 var(--grouped-element-spacing); + height: 14px; +} + +.progress-indicator { + height: 32px; + position: relative; + box-shadow: var(--border-sunken-inner); + padding: 4px 4px; + border: none; + box-sizing: border-box; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + border-radius: 0; +} + +.progress-indicator > .progress-indicator-bar { + height: 100%; + display: block; + background-color: var(--dialog-blue); +} + +.progress-indicator.segmented > .progress-indicator-bar { + width: 100%; + background-color: transparent; /* resets the background color which is set to blue in the non-segmented selector */ + background-image: linear-gradient( + 90deg, + var(--dialog-blue) 0 16px, + transparent 0 2px + ); + background-repeat: repeat; + background-size: 18px 100%; +} + +.field-border { + background: var(--button-highlight); + box-shadow: var(--border-field); + padding: 2px; +} + +.field-border-disabled { + background: var(--surface); + box-shadow: var(--border-field); + padding: 2px; +} + +.status-field-border { + background: var(--surface); + box-shadow: var(--border-status-field); + padding: 1px; +} diff --git a/assets/UnifontExMono.woff2 b/static/assets/UnifontExMono.woff2 Binary files differindex e7db71a..e7db71a 100644 --- a/assets/UnifontExMono.woff2 +++ b/static/assets/UnifontExMono.woff2 diff --git a/static/assets/badges/cookies.png b/static/assets/badges/cookies.png Binary files differnew file mode 100644 index 0000000..70994a7 --- /dev/null +++ b/static/assets/badges/cookies.png diff --git a/assets/badges/go2hell.gif b/static/assets/badges/go2hell.gif Binary files differindex 7f1290a..7f1290a 100644 --- a/assets/badges/go2hell.gif +++ b/static/assets/badges/go2hell.gif diff --git a/static/assets/badges/indieweb.png b/static/assets/badges/indieweb.png Binary files differnew file mode 100644 index 0000000..a241947 --- /dev/null +++ b/static/assets/badges/indieweb.png diff --git a/static/assets/badges/javascript.png b/static/assets/badges/javascript.png Binary files differnew file mode 100644 index 0000000..7ad573f --- /dev/null +++ b/static/assets/badges/javascript.png diff --git a/assets/badges/lynx.gif b/static/assets/badges/lynx.gif Binary files differindex 452ac5e..452ac5e 100644 --- a/assets/badges/lynx.gif +++ b/static/assets/badges/lynx.gif diff --git a/assets/badges/midi_files_now.gif b/static/assets/badges/midi_files_now.gif Binary files differindex 18a2422..18a2422 100644 --- a/assets/badges/midi_files_now.gif +++ b/static/assets/badges/midi_files_now.gif diff --git a/assets/badges/powered-by-debian.gif b/static/assets/badges/powered-by-debian.gif Binary files differindex 1f617c8..1f617c8 100644 --- a/assets/badges/powered-by-debian.gif +++ b/static/assets/badges/powered-by-debian.gif diff --git a/static/assets/badges/soaos.png b/static/assets/badges/soaos.png Binary files differnew file mode 100644 index 0000000..486af90 --- /dev/null +++ b/static/assets/badges/soaos.png diff --git a/assets/bg.jpg b/static/assets/bg.jpg Binary files differindex 4e9044c..4e9044c 100644 --- a/assets/bg.jpg +++ b/static/assets/bg.jpg diff --git a/assets/construction.gif b/static/assets/construction.gif Binary files differindex b9c4eeb..b9c4eeb 100644 --- a/assets/construction.gif +++ b/static/assets/construction.gif diff --git a/static/assets/icon/button-down-active.svg b/static/assets/icon/button-down-active.svg new file mode 100644 index 0000000..fa7cac1 --- /dev/null +++ b/static/assets/icon/button-down-active.svg @@ -0,0 +1,5 @@ +<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H15H16V17H15H0V16V1V0ZM1 16H15V1H1V16Z" fill="#232a2e"/> +<rect x="1" y="1" width="14" height="15" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M12 7H5V8H6V9H7V10H8V11H9V10H10V9H11V8H12V7Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/button-down.svg b/static/assets/icon/button-down.svg new file mode 100644 index 0000000..7bbaa80 --- /dev/null +++ b/static/assets/icon/button-down.svg @@ -0,0 +1,8 @@ +<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 0H0V1V16H1V1H15V0Z" fill="#232a2e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M2 1H1V15H2V2H14V1H2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17H15H0V16H15V0H16V17Z" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H14V15H1V16H14H15V1Z" fill="#232a2e"/> +<rect x="2" y="2" width="12" height="13" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11 6H4V7H5V8H6V9H7V10H8V9H9V8H10V7H11V6Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/button-left.svg b/static/assets/icon/button-left.svg new file mode 100644 index 0000000..80421a1 --- /dev/null +++ b/static/assets/icon/button-left.svg @@ -0,0 +1,8 @@ +<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 0H0V1V16H1V1H15V0Z" fill="#DFDFDF"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M2 1H1V15H2V2H14V1H2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17H15H0V16H15V0H16V17Z" fill="#d3c6aa"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H14V15H1V16H14H15V1Z" fill="#232a2e"/> +<rect x="2" y="2" width="12" height="13" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 4H8V5H7V6H6V7H5V8H6V9H7V10H8V11H9V4Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/button-right.svg b/static/assets/icon/button-right.svg new file mode 100644 index 0000000..5573fe0 --- /dev/null +++ b/static/assets/icon/button-right.svg @@ -0,0 +1,8 @@ +<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 0H0V1V16H1V1H15V0Z" fill="#DFDFDF"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M2 1H1V15H2V2H14V1H2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17H15H0V16H15V0H16V17Z" fill="#d3c6aa"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H14V15H1V16H14H15V1Z" fill="#232a2e"/> +<rect x="2" y="2" width="12" height="13" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M7 4H6V11H7V10H8V9H9V8H10V7H9V6H8V5H7V4Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/button-up.svg b/static/assets/icon/button-up.svg new file mode 100644 index 0000000..4b25846 --- /dev/null +++ b/static/assets/icon/button-up.svg @@ -0,0 +1,8 @@ +<svg width="16" height="17" viewBox="0 0 16 17" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 0H0V1V16H1V1H15V0Z" fill="#232a2e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M2 1H1V15H2V2H14V1H2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M16 17H15H0V16H15V0H16V17Z" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M15 1H14V15H1V16H14H15V1Z" fill="#232a2e"/> +<rect x="2" y="2" width="12" height="13" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8 6H7V7H6V8H5V9H4V10H11V9H10V8H9V7H8V6Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/checkmark-disabled.svg b/static/assets/icon/checkmark-disabled.svg new file mode 100644 index 0000000..f9d34b9 --- /dev/null +++ b/static/assets/icon/checkmark-disabled.svg @@ -0,0 +1,3 @@ +<svg width="7" height="7" viewBox="0 0 7 7" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0H6V1H5V2H4V3H3V4H2V3H1V2H0V5H1V6H2V7H3V6H4V5H5V4H6V3H7V0Z" fill="#232a2e"/> +</svg> diff --git a/static/assets/icon/checkmark.svg b/static/assets/icon/checkmark.svg new file mode 100644 index 0000000..297996a --- /dev/null +++ b/static/assets/icon/checkmark.svg @@ -0,0 +1,3 @@ +<svg width="7" height="7" viewBox="0 0 7 7" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M7 0H6V1H5V2H4V3H3V4H2V3H1V2H0V5H1V6H2V7H3V6H4V5H5V4H6V3H7V0Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/close.svg b/static/assets/icon/close.svg new file mode 100644 index 0000000..9b426f5 --- /dev/null +++ b/static/assets/icon/close.svg @@ -0,0 +1,3 @@ +<svg width="8" height="7" viewBox="0 0 8 7" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H1H2V1H3V2H4H5V1H6V0H7H8V1H7V2H6V3H5V4H6V5H7V6H8V7H7H6V6H5V5H4H3V6H2V7H1H0V6H1V5H2V4H3V3H2V2H1V1H0V0Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/groupbox-border.svg b/static/assets/icon/groupbox-border.svg new file mode 100644 index 0000000..a205da9 --- /dev/null +++ b/static/assets/icon/groupbox-border.svg @@ -0,0 +1,4 @@ +<svg width="5" height="5" viewBox="0 0 5 5" fill="grey" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H5V5H0V2H2V3H3V2H0" fill="#4f585e" /> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4V4H0V1H1V3H3V1H0" fill="#232a2e" /> +</svg> diff --git a/static/assets/icon/help.svg b/static/assets/icon/help.svg new file mode 100644 index 0000000..f7a5c07 --- /dev/null +++ b/static/assets/icon/help.svg @@ -0,0 +1,8 @@ +<svg width="6" height="9" viewBox="0 0 6 9" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect y="1" width="2" height="2" fill="#d3c6aa"/> +<rect x="1" width="4" height="1" fill="#d3c6aa"/> +<rect x="4" y="1" width="2" height="2" fill="#d3c6aa"/> +<rect x="3" y="3" width="2" height="1" fill="#d3c6aa"/> +<rect x="2" y="4" width="2" height="2" fill="#d3c6aa"/> +<rect x="2" y="7" width="2" height="2" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/indicator-horizontal.svg b/static/assets/icon/indicator-horizontal.svg new file mode 100644 index 0000000..026baf5 --- /dev/null +++ b/static/assets/icon/indicator-horizontal.svg @@ -0,0 +1,6 @@ +<svg width="11" height="21" viewBox="0 0 11 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0V16H2V18H4V20H5V19H3V17H1V1H10V0Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M1 1V16H2V17H3V18H4V19H6V18H7V17H8V16H9V1Z" fill="#C0C7C8"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1H10V16H8V18H6V20H5V19H7V17H9Z" fill="#87888F"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10 0H11V16H9V18H7V20H5V21H6V19H8V17H10Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/indicator-rectangle-horizontal.svg b/static/assets/icon/indicator-rectangle-horizontal.svg new file mode 100644 index 0000000..9bbfb58 --- /dev/null +++ b/static/assets/icon/indicator-rectangle-horizontal.svg @@ -0,0 +1,6 @@ +<svg width="11" height="21" viewBox="0 0 11 21" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0V20H1V1H10V0Z" fill="#4f585e"/> +<rect x="1" y="1" width="8" height="18" fill="#C0C7C8"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 1H10V20H1V19H9Z" fill="#87888F"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M10 0H11V21H0V20H10Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/maximize-disabled.svg b/static/assets/icon/maximize-disabled.svg new file mode 100644 index 0000000..34c7ff9 --- /dev/null +++ b/static/assets/icon/maximize-disabled.svg @@ -0,0 +1,4 @@ +<svg width="10" height="10" viewBox="0 0 10 10" fill="none" xmlns="http://www.w3.org/2000/svg"> + <path fill-rule="evenodd" clip-rule="evenodd" d="M10 1H1V3V9V10H2H9H10V9V3V1ZM9 3H2V9H9V3Z" fill="black"/> + <path fill-rule="evenodd" clip-rule="evenodd" d="M9 0H0V2V8V9H1H8H9V8V2V0ZM8 2H1V8H8V2Z" fill="#232a2e"/> +</svg>
\ No newline at end of file diff --git a/static/assets/icon/maximize.svg b/static/assets/icon/maximize.svg new file mode 100644 index 0000000..19f8b09 --- /dev/null +++ b/static/assets/icon/maximize.svg @@ -0,0 +1,3 @@ +<svg width="9" height="9" viewBox="0 0 9 9" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 0H0V2V8V9H1H8H9V8V2V0ZM8 2H1V8H8V2Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/minimize.svg b/static/assets/icon/minimize.svg new file mode 100644 index 0000000..6231e4c --- /dev/null +++ b/static/assets/icon/minimize.svg @@ -0,0 +1,3 @@ +<svg width="6" height="2" viewBox="0 0 6 2" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect width="6" height="2" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/radio-border-disabled.svg b/static/assets/icon/radio-border-disabled.svg new file mode 100644 index 0000000..c53fa15 --- /dev/null +++ b/static/assets/icon/radio-border-disabled.svg @@ -0,0 +1,7 @@ +<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0H4V1H2V2H1V4H0V8H1V10H2V8H1V4H2V2H4V1H8V2H10V1H8V0Z" fill="var(--fg)"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1H4V2H2V3V4H1V8H2V9H3V8H2V4H3V3H4V2H8V3H10V2H8V1Z" fill="var(--fg)"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 3H10V4H9V3ZM10 8V4H11V8H10ZM8 10V9H9V8H10V9V10H8ZM4 10V11H8V10H4ZM4 10V9H2V10H4Z" fill="#DFDFDF"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11 2H10V4H11V8H10V10H8V11H4V10H2V11H4V12H8V11H10V10H11V8H12V4H11V2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2H8V3H9V4H10V8H9V9H8V10H4V9H3V8H2V4H3V3H4V2Z" fill="var(--fg)"/> +</svg> diff --git a/static/assets/icon/radio-border.svg b/static/assets/icon/radio-border.svg new file mode 100644 index 0000000..e70978e --- /dev/null +++ b/static/assets/icon/radio-border.svg @@ -0,0 +1,8 @@ +<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8 0H4V1H2V2H1V4H0V8H1V10H2V8H1V4H2V2H4V1H8V2H10V1H8V0Z" fill="#232a2e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M8 1H4V2H2V3V4H1V8H2V9H3V8H2V4H3V3H4V2H8V3H10V2H8V1Z" fill="#d3c6aa"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M9 3H10V4H9V3ZM10 8V4H11V8H10ZM8 10V9H9V8H10V9V10H8ZM4 10V11H8V10H4ZM4 10V9H2V10H4Z" fill="#DFDFDF"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M11 2H10V4H11V8H10V10H8V11H4V10H2V11H4V12H8V11H10V10H11V8H12V4H11V2Z" fill="#4f585e"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M4 2H8V3H9V4H10V8H9V9H8V10H4V9H3V8H2V4H3V3H4V2Z" fill="#4f585e"/> +</svg> + diff --git a/static/assets/icon/radio-dot-disabled.svg b/static/assets/icon/radio-dot-disabled.svg new file mode 100644 index 0000000..e1a76fc --- /dev/null +++ b/static/assets/icon/radio-dot-disabled.svg @@ -0,0 +1,3 @@ +<svg width="4" height="4" viewBox="0 0 4 4" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3 0H1V1H0V2V3H1V4H3V3H4V2V1H3V0Z" fill="#232a2e"/> +</svg> diff --git a/static/assets/icon/radio-dot.svg b/static/assets/icon/radio-dot.svg new file mode 100644 index 0000000..f19e1b8 --- /dev/null +++ b/static/assets/icon/radio-dot.svg @@ -0,0 +1,3 @@ +<svg width="4" height="4" viewBox="0 0 4 4" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M3 0H1V1H0V2V3H1V4H3V3H4V2V1H3V0Z" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/restore.svg b/static/assets/icon/restore.svg new file mode 100644 index 0000000..bd8fe15 --- /dev/null +++ b/static/assets/icon/restore.svg @@ -0,0 +1,10 @@ +<svg width="8" height="9" viewBox="0 0 8 9" fill="none" xmlns="http://www.w3.org/2000/svg"> +<rect x="2" width="6" height="2" fill="#d3c6aa"/> +<rect x="7" y="2" width="1" height="4" fill="#d3c6aa"/> +<rect x="2" y="2" width="1" height="1" fill="#d3c6aa"/> +<rect x="6" y="5" width="1" height="1" fill="#d3c6aa"/> +<rect y="3" width="6" height="2" fill="#d3c6aa"/> +<rect x="5" y="5" width="1" height="4" fill="#d3c6aa"/> +<rect y="5" width="1" height="4" fill="#d3c6aa"/> +<rect x="1" y="8" width="4" height="1" fill="#d3c6aa"/> +</svg> diff --git a/static/assets/icon/scrollbar-background.svg b/static/assets/icon/scrollbar-background.svg new file mode 100644 index 0000000..04b2f59 --- /dev/null +++ b/static/assets/icon/scrollbar-background.svg @@ -0,0 +1,4 @@ +<svg width="2" height="2" viewBox="0 0 2 2" fill="none" xmlns="http://www.w3.org/2000/svg"> +<path fill-rule="evenodd" clip-rule="evenodd" d="M1 0H0V1H1V2H2V1H1V0Z" fill="#3d484d"/> +<path fill-rule="evenodd" clip-rule="evenodd" d="M2 0H1V1H0V2H1V1H2V0Z" fill="#232a2e"/> +</svg> diff --git a/static/assets/icon/sunken-panel-border.svg b/static/assets/icon/sunken-panel-border.svg new file mode 100644 index 0000000..c13e8ce --- /dev/null +++ b/static/assets/icon/sunken-panel-border.svg @@ -0,0 +1,11 @@ +<?xml version="1.0"?> +<svg xmlns="http://www.w3.org/2000/svg" width="5" height="5" viewBox="0 0 5 5"> + <rect width="4" height="1" x="0" y="0" fill="#232a2e"/> + <rect width="1" height="4" x="0" y="0" fill="#232a2e"/> + <rect width="2" height="1" x="1" y="1" fill="#232a2e"/> + <rect width="1" height="2" x="1" y="1" fill="#232a2e"/> + <rect width="5" height="1" x="0" y="4" fill="#4f585e"/> + <rect width="1" height="5" x="4" y="0" fill="#4f585e"/> + <rect width="1" height="3" x="3" y="1" fill="#4f585e"/> + <rect width="3" height="1" x="1" y="3" fill="#4f585e"/> +</svg> diff --git a/favicon.png b/static/favicon.png Binary files differindex fac014b..fac014b 100644 --- a/favicon.png +++ b/static/favicon.png diff --git a/static/hell.css b/static/hell.css new file mode 100644 index 0000000..572f3d1 --- /dev/null +++ b/static/hell.css @@ -0,0 +1,102 @@ +html { + background: url("/hell/bg.jpg"); + background-attachment: fixed; + background-size: cover; + image-rendering: pixelated; +} + +body { + margin: auto 0; + max-width: unset; + margin-right: auto; +} + +@keyframes fire { + 0% { + text-shadow: + 0 0 20px #fefcc9, + 10px -10px 30px #feec85, + -20px -20px 40px #ffae34, + 20px -40px 50px #ec760c, + -20px -60px 60px #cd4606, + 0 -80px 70px #973716, + 10px -90px 80px #451b0e; + } + 100% { + text-shadow: + 0 0 20px #fefcc9, + 10px -10px 30px #fefcc9, + -20px -20px 40px #feec85, + 22px -42px 60px #ffae34, + -22px -58px 50px #ec760c, + 0 -82px 80px #cd4606, + 10px -90px 80px #973716; + } +} + +.hellfire { + color: var(--orange); + animation: fire 1s ease-in-out infinite alternate; +} + +massive-fucking-background-flame { + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + z-index: -999; + background-image: url("/hell/smallfire.gif"); + background-size: auto 128px; + opacity: 0.25; + --pan: 0 -128px; + animation: pan linear 3s infinite; +} + +a { + color: black; +} + +#inverted-cross { + display: inline-block; + rotate: 180deg; + transition: + rotate 2s, + color 0.5s; + --glow-color: red; + animation: glow 4s linear infinite; +} + +#inverted-cross:hover { + rotate: 0deg; + --glow-color: white; + color: var(--yellow); + transition: + rotate 2s, + color 1s; +} + +.evil { + color: black; +} + +.huge { + font-size: 8rem; +} + +.struck { + opacity: 0.5; + position: absolute; + left: 0; + z-index: -999; + user-select: none; +} + +@keyframes pan { + from { + background-position: 0 0; + } + to { + background-position: var(--pan); + } +} diff --git a/index.css b/static/index.css index 9ae6d1e..9ae6d1e 100644 --- a/index.css +++ b/static/index.css diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..b4c788a --- /dev/null +++ b/static/style.css @@ -0,0 +1,184 @@ +:root { + --red: #e67e80; + --orange: #e69875; + --yellow: #dbbc7f; + --green: #a7c080; + --blue: #7fbbb3; + --aqua: #83c092; + --purple: #d699b6; + --fg: #d3c6aa; + --statusline1: #a7c080; + --statusline2: #d3c6aa; + --statusline3: #e67e80; + --gray0: #7a8478; + --gray1: #859289; + --gray2: #9da9a0; + + --bg-dim: #232a2e; + --bg0: #2d353b; + --bg1: #343f44; + --bg2: #3d484d; + --bg3: #475258; + --bg4: #4f585e; + --bg5: #56635f; + --bg-red: #4c3743; + --bg-visual: #493b40; + --bg-yellow: #45443c; + --bg-green: #3c4841; + --bg-blue: #384b55; +} + +html { + background: url("/assets/bg.jpg"); + background-attachment: fixed; + background-size: cover; + image-rendering: pixelated; +} + +a:not(:has(img, div))[target="_blank"]::after { + content: " ⎘"; +} + +a:not(:has(img,div))[href^="https://tv.soaos.dev/w/"]::before +{ + content: "📺 "; +} + +ul { + list-style-type: "• "; +} + +pre { + overflow: auto; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} + +.under-construction { + color: var(--yellow); + animation: blinker 1s linear infinite; +} + +@keyframes glow { + 50% { + text-shadow: + 0 0 0px var(--glow-color), + 0 0 20px hsl(from var(--glow-color) h s calc(l - 10)); + } +} + +@keyframes glow-box { + 50% { + box-shadow: + 0 0 0px var(--glow-color), + 0 0 20px hsl(from var(--glow-color) h s calc(l - 10)); + } +} + +.evil { + color: var(--red); + --glow-color: red; + animation: glow 4s linear infinite; +} + +.evil-box { + color: var(--red); + --glow-color: red; + animation: glow-box 4s linear infinite; +} + +.holy { + color: var(--yellow); + --glow-color: white; + animation: glow 4s linear infinite; +} + +a.evil:visited { + color: var(--red); +} + +a.evil:hover { + color: red; +} + +.hidden { + display: none; +} + +.no-bullets { + list-style-type: none; + padding-left: 0; + :w; +} + +#badge-grid { + display: flex; + gap: 4px; +} + +.unselectable { + user-select: none; +} + +.window { + margin: 1rem; +} + +.red-on-white { + color: red; + background-color: white; +} + +.centered { + display: flex; + justify-content: center; +} + +body { + margin: auto 0; + max-width: 800px; + margin-right: auto; +} + +[data-tooltip]:hover::after { + content: attr(data-tooltip); + border: 1px solid var(--bg-dim); + background: var(--fg); + color: var(--bg-dim); + display: inline-block; + white-space: pre-wrap; + text-align: left; + padding: 0.3em; + position: absolute; + z-index: 98; +} + +.flip { + position: relative; +} + +.hidden-selectable { + opacity: 0; +} + +.flip::after { + display: inline-block; + position: absolute; + content: attr(data-title); + transform: rotateY(180deg); + clip-path: polygon(0 0, 50.1% 0, 50.1% 100%, 0 100%); + visibility: visible; + left: 0; +} + +.flip::before { + display: inline-block; + position: absolute; + content: attr(data-title); + clip-path: polygon(0 0, 50.1% 0, 50.1% 100%, 0 100%); + visibility: visible; +} diff --git a/static/style.css.bak b/static/style.css.bak new file mode 100644 index 0000000..22815a7 --- /dev/null +++ b/static/style.css.bak @@ -0,0 +1,224 @@ +@font-face { + font-family: unifont; + src: url("/assets/UnifontExMono.woff2"); +} + +:root { + --red: #e67e80; + --orange: #e69875; + --yellow: #dbbc7f; + --green: #a7c080; + --blue: #7fbbb3; + --aqua: #83c092; + --purple: #d699b6; + --fg: #d3c6aa; + --statusline1: #a7c080; + --statusline2: #d3c6aa; + --statusline3: #e67e80; + --gray0: #7a8478; + --gray1: #859289; + --gray2: #9da9a0; + + --bg-dim: #232a2e; + --bg0: #2d353b; + --bg1: #343f44; + --bg2: #3d484d; + --bg3: #475258; + --bg4: #4f585e; + --bg5: #56635f; + --bg-red: #4c3743; + --bg-visual: #493b40; + --bg-yellow: #45443c; + --bg-green: #3c4841; + --bg-blue: #384b55; +} + +body { + font-family: unifont; + font-size: 16px; + color: var(--fg); + perspective-origin: top left; +} + +pre, +code, +samp { + font-family: unifont; +} + +samp { + color: var(--gray2); +} + +.section { + margin: 1rem; + padding: 1rem; +} + +.horizontal-container { + display: flex; + justify-content: space-between; +} + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: normal; + text-decoration: underline; +} + +h1 { + font-size: 48px; +} + +h2 { + font-size: 32px; +} + +h3 { + font-size: 16px; +} + +.centered { + text-align: center; +} + +.center-content { + display: flex; + justify-content: center; +} + +.half { + clip-path: polygon(0 0, 50.1% 0, 50.1% 100%, 0 100%); +} + +.flip-x { + transform: scaleX(-1); +} + +.unselectable { + user-select: none; +} + +@keyframes blinker { + 50% { + opacity: 0; + } +} + +.under-construction { + color: var(--yellow); + animation: blinker 1s linear infinite; +} + +@keyframes glow { + 50% { + text-shadow: + 0 0 10px var(--glow-color), + 0 0 20px var(--glow-color), + 0 0 40px var(--glow-color), + 0 0 80px hsl(from var(--glow-color) h s calc(l - 10)), + 0 0 120px hsl(from var(--glow-color) h s calc(l - 10)); + } +} + +.evil { + color: var(--red); + --glow-color: red; + animation: glow 4s linear infinite; +} + +.holy { + color: var(--yellow); + --glow-color: white; + animation: glow 4s linear infinite; +} + +a.evil:visited { + color: var(--red); +} + +a.evil:hover { + color: red; +} + +.inline-block { + display: inline-block; +} + +.no-bullets { + list-style-type: none; + padding-left: 0; +} + +.red-on-white { + color: red; + background-color: white; +} + +.hidden { + display: none; +} + +.huge { + font-size: 128px; +} + +.struck { + opacity: 0.5; + position: absolute; + z-index: -999; + user-select: none; +} + +@keyframes pan { + from { + background-position: 0 0; + } + to { + background-position: var(--pan); + } +} + +.bold { + font-weight: bold; +} + +.column { + display: flex; + flex-direction: column; + align-items: center; +} + +/* Links */ +a { + color: var(--blue); +} + +a:visited { + color: var(--purple); +} + +a:not(:has(img, div))[target="_blank"]::after { + content: " ⎘"; +} + +a:not(:has(img,div))[href^="https://tv.soaos.dev/w/"]::before +{ + content: "📺 "; +} + +ul { + list-style-type: "• "; +} diff --git a/style.css b/style.css deleted file mode 100644 index a3a4364..0000000 --- a/style.css +++ /dev/null @@ -1,215 +0,0 @@ -@font-face { - font-family: unifont; - src: url("/assets/UnifontExMono.woff2"); -} - -:root { - --red: #e67e80; - --orange: #e69875; - --yellow: #dbbc7f; - --green: #a7c080; - --blue: #7fbbb3; - --aqua: #83c092; - --purple: #d699b6; - --fg: #d3c6aa; - --statusline1: #a7c080; - --statusline2: #d3c6aa; - --statusline3: #e67e80; - --gray0: #7a8478; - --gray1: #859289; - --gray2: #9da9a0; - - --bg-dim: #232a2e; - --bg0: #2d353b; - --bg1: #343f44; - --bg2: #3d484d; - --bg3: #475258; - --bg4: #4f585e; - --bg5: #56635f; - --bg-red: #4c3743; - --bg-visual: #493b40; - --bg-yellow: #45443c; - --bg-green: #3c4841; - --bg-blue: #384b55; -} - -body { - font-family: unifont; - font-size: 16px; - color: var(--fg); - perspective-origin: top left -} - -pre, code, samp { - font-family: unifont; -} - -samp { - color: var(--gray2) -} - -.section { - margin: 1rem; - padding: 1rem; -} - -.horizontal-container { - display: flex; - justify-content: space-between; -} - -a { - text-decoration: none; -} - -a:hover { - text-decoration: underline; -} - -h1, h2, h3, h4, h5, h6 { - font-weight: normal; - text-decoration: underline; -} - -h1 { - font-size: 48px; -} - -h2 { - font-size: 32px; -} - -h3 { - font-size: 16px; -} - -.centered { - text-align: center; -} - -.center-content { - display: flex; - justify-content: center; -} - -.half { - clip-path: polygon(0 0, 50.1% 0, 50.1% 100%, 0 100%); -} - -.flip-x { - transform: scaleX(-1); -} - -.unselectable { - user-select: none; -} - -@keyframes blinker { - 50% { - opacity: 0; - } -} - -.under-construction { - color: var(--yellow); - animation: blinker 1s linear infinite; -} - -@keyframes glow { - 50% { - text-shadow: 0 0 10px var(--glow-color), - 0 0 20px var(--glow-color), - 0 0 40px var(--glow-color), - 0 0 80px hsl(from var(--glow-color) h s calc(l - 10)), - 0 0 120px hsl(from var(--glow-color) h s calc(l - 10)); - } -} - -.evil { - color: var(--red); - --glow-color: red; - animation: glow 4s linear infinite; -} - -.holy { - color: var(--yellow); - --glow-color: white; - animation: glow 4s linear infinite; -} - -a.evil:visited { - color: var(--red); -} - -a.evil:hover { - color: red; -} - -.inline-block { - display: inline-block; -} - -.no-bullets { - list-style-type: none; - padding-left: 0; -} - -.red-on-white { - color: red; - background-color: white; -} - -.hidden { - display: none; -} - -.huge { - font-size: 128px; -} - -.struck { - opacity: 0.5; - position: absolute; - z-index: -999; - user-select: none; -} - -@keyframes pan { - from { - background-position: 0 0; - } - to { - background-position: var(--pan); - } -} - -.bold { - font-weight: bold; -} - -.column { - display: flex; - flex-direction: column; - align-items: center; -} - -/* Links */ -a { - color: var(--blue); -} - -a:visited { - color: var(--purple); -} - -a:not(:has(img,div))[target="_blank"]::after { - content: " ⎘"; -} - -a:not(:has(img,div))[href^="https://tv.soaos.dev/w/"]::before { - content: "📺 "; -} - -ul { - list-style-type: "• "; -}
\ No newline at end of file diff --git a/templates/base.html b/templates/base.html new file mode 100644 index 0000000..7af29b7 --- /dev/null +++ b/templates/base.html @@ -0,0 +1,95 @@ +{% import "macros.html" as macros %} +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>soaos</title> + <link rel="icon" href="favicon.png" /> + <link + rel="preload" + href="/assets/UnifontExMono.woff2" + as="font" + type="font/woff2" + /> + <link rel="stylesheet" href="/98.css" /> + <link rel="stylesheet" href="/style.css" /> + <meta charset="UTF-8" /> + </head> + <body> + <header> + <div class="window"> + <div class="title-bar"> + <div class="title-bar-text">𐂂 Badass header</div> + </div> + <div class="window-body centered"> + <h1 class="flip" data-title="𐂂🌲🌲🌲 soaos 🌲🌲🌲𐂂"> + <span class="hidden-selectable">𐂂🌲🌲🌲 soaos 🌲🌲🌲𐂂</span> + </h1> + </div> + </div> + </header> + <main>{% block content %} {% endblock content %}</main> + <footer class="window"> + <div class="title-bar"> + <div class="title-bar-text">🪪 Badges</div> + </div> + <div class="window-body"> + <ul id="badge-grid" class="no-bullets"> + <li> + <img + src="/assets/badges/javascript.png" + alt="Javascript-Free Page" + /> + </li> + <li> + <a href="https://lynx.browser.org/" target="_blank" + ><img src="/assets/badges/lynx.gif" alt="Lynx Compatible" + /></a> + </li> + <li> + <a href="https://www.debian.org/" target="_blank" + ><img + src="/assets/badges/powered-by-debian.gif" + alt="Powered by Debian" + /></a> + </li> + <li> + <a href="/hell" + ><img + class="evil-box" + src="/assets/badges/go2hell.gif" + alt="Go 2 Hell Now!" + /></a> + </li> + <li> + <a + href="/assets/badges/soaos.png" + download + data-tooltip="🦌 - Link to me on your site!" + ><img src="/assets/badges/soaos.png" alt="soaos.dev" + /></a> + </li> + <li> + <img src="/assets/badges/cookies.png" alt="Cookie-Free Page" /> + </li> + </ul> + </div> + <div class="status-bar"> + <p data-tooltip="🦌 -<3" class="status-bar-field"> + <span class="red-on-white">█🍁█</span> + </p> + <p class="status-bar-field"> + <a href="https://evilr.ing/soaos/previous" class="evil">⛧</a> + <a href="https://evilr.ing" class="evil">EVILRING</a> + <a href="https://evilr.ing/soaos/next" class="evil">⛤</a> + </p> + <p class="status-bar-field"> + <span class="hidden">Built at: </span>{{ now() }} + </p> + <p class="status-bar-field"> + <a href="https://soaos.dev" class="flip" data-title="soaos"><span class="hidden-selectable">soaos<span/></a> + </p> + </div> + </footer> + </body> +</html> diff --git a/templates/blog.html b/templates/blog.html new file mode 100644 index 0000000..f659571 --- /dev/null +++ b/templates/blog.html @@ -0,0 +1,38 @@ +{% extends "base.html" %} {% block content %} +<div class="window"> + <div class="title-bar"> + <div class="title-bar-text">📖 Blog</div> + <div class="title-bar-controls"> + <a href="/"><button aria-label="Close"></button></a> + </div> + </div> + <div class="window-body"> + <p> + Welcome to my blog! This is where I'll post longer content about stuff I'm + working on. I'm working out some channels for posting day-to-day short + form shit too so keep an eye out for that. + </p> + <h2>Latest Posts</h2> + <div class="sunken-panel"> + <ul class="tree-view" style="height: 8rem; overflow-y: scroll"> + {% set current_year = now() | date(format="%Y") | int %} {% for i in + range(start=0, end=current_year - 2024) %} + <li> + <details open> + <summary><b>{{current_year - i}}</b></summary> + <ul> + {% for post in section.pages %} {% if post.year == current_year - + i %} + <li> + <a href="{{post.permalink}}">{{post.title}}</a> - {{post.date}} + </li> + {% endif %} {% endfor %} + </ul> + </details> + </li> + {% endfor %} + </ul> + </div> + </div> +</div> +{% endblock content %} diff --git a/heaven/index.html b/templates/heaven.html index e8b40b1..93a86ac 100644 --- a/heaven/index.html +++ b/templates/heaven.html @@ -3,12 +3,9 @@ <html lang="en"> <head> - <title>hell</title> - <link rel="icon" href="smallfire.gif"> - <link rel="preload" href="/assets/unifont.woff2" as="font" type="font/woff2"> - <link rel="preload" href="/assets/unifont_upper.woff2" as="font" type="font/woff2"> + <title>heaven</title> <link rel="stylesheet" href="/style.css"> - <link rel="stylesheet" href="heaven.css"> + <link rel="stylesheet" href="/heaven/heaven.css"> <meta charset="UTF-8"> </head> @@ -19,7 +16,7 @@ <summary>🕊🕊🕊 (please enable autoplay to hear music lol)</summary> <audio autoplay loop controls> - <source src="everytime_we_touch_nightcore.ogg"> + <source src="/heaven/everytime_we_touch_nightcore.ogg"> </audio> </details> <h1 class="holy">😇 GOD BLESS YOU FRIEND... YOU ARE IN HEAVEN! 😇</h1> @@ -29,3 +26,4 @@ </body> </html> + diff --git a/templates/hell.html b/templates/hell.html new file mode 100644 index 0000000..d799db3 --- /dev/null +++ b/templates/hell.html @@ -0,0 +1,22 @@ +<!doctype html> + +<html lang="en"> + <head> + <title>hell</title> + <link rel="icon" href="/hell/smallfire.gif" /> + <link + rel="preload" + href="/assets/UnifontExMono.woff2" + as="font" + type="font/woff2" + /> + <link rel="stylesheet" href="/style.css" /> + <link rel="stylesheet" href="/hell.css" /> + <meta charset="UTF-8" /> + </head> + + <body> + <massive-fucking-background-flame></massive-fucking-background-flame> + {{ section.content | safe }} + </body> +</html> diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..0bf66d0 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} {% block content %} {{ section.content | safe }} {% +endblock content %} diff --git a/templates/macros.html b/templates/macros.html new file mode 100644 index 0000000..eae30fe --- /dev/null +++ b/templates/macros.html @@ -0,0 +1,3 @@ +{% macro post_list() %} {% for post in section.pages %} +<a href="{{ post.permalink }}">{{ post.title }}</a> +{% endfor %} {% endmacro input %}
\ No newline at end of file diff --git a/templates/page.html b/templates/page.html new file mode 100644 index 0000000..4cd271b --- /dev/null +++ b/templates/page.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} {% block content %} {{ page.content | safe }} {% +endblock content %} diff --git a/templates/post.html b/templates/post.html new file mode 100644 index 0000000..794c715 --- /dev/null +++ b/templates/post.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} {% block content %} + +<div class="window"> + <div class="title-bar"> + <b>📰 Post: {{ page.title }}</b> + <div class="title-bar-controls"> + <button + aria-label="Help" + data-tooltip="Posted {{ page.date }}
{{ page.word_count }} words ({{ page.reading_time }} minutes)" + ></button> + <a href="../"><button aria-label="Close"></button></a> + </div> + </div> + <div class="window-body"> + <h1>{{ page.title }}</h1> + <article>{{ page.content | safe}}</article> + </div> +</div> +{% endblock content %} diff --git a/templates/section.html b/templates/section.html new file mode 100644 index 0000000..0bf66d0 --- /dev/null +++ b/templates/section.html @@ -0,0 +1,2 @@ +{% extends "base.html" %} {% block content %} {{ section.content | safe }} {% +endblock content %} diff --git a/templates/shortcodes/soaosed.html b/templates/shortcodes/soaosed.html new file mode 100644 index 0000000..c5ae639 --- /dev/null +++ b/templates/shortcodes/soaosed.html @@ -0,0 +1,3 @@ +<span class="flip" data-title="{{ body }}" + ><span class="hidden-selectable">{{ body }}</span></span +>
\ No newline at end of file diff --git a/templates/shortcodes/subtree.html b/templates/shortcodes/subtree.html new file mode 100644 index 0000000..2246718 --- /dev/null +++ b/templates/shortcodes/subtree.html @@ -0,0 +1,8 @@ +<li> + <details {% if open %}open{% endif %}> + <summary><b>{{ name | safe }}</b></summary> + <ul> + {{ body | markdown | safe }} + </ul> + </details> +</li>
\ No newline at end of file diff --git a/templates/shortcodes/title_bar.html b/templates/shortcodes/title_bar.html new file mode 100644 index 0000000..facc6fb --- /dev/null +++ b/templates/shortcodes/title_bar.html @@ -0,0 +1,8 @@ +<div class="title-bar"> + <div class="title-bar-text">{{ body | safe }}</div> + <div class="title-bar-controls"> + {% if close %} + <a href="{{ close }}"><button aria-label="Close"></button></a> + {% endif %} + </div> +</div> diff --git a/templates/shortcodes/tree_view.html b/templates/shortcodes/tree_view.html new file mode 100644 index 0000000..e8de782 --- /dev/null +++ b/templates/shortcodes/tree_view.html @@ -0,0 +1,5 @@ +<div class="sunken-panel"> + <ul class="tree-view" {% if height %} style="height: {{height}}; overflow-y: scroll" {% endif %} > + {{ body | markdown | safe }} + </ul> +</div> diff --git a/templates/shortcodes/treelink.html b/templates/shortcodes/treelink.html new file mode 100644 index 0000000..ff80fdc --- /dev/null +++ b/templates/shortcodes/treelink.html @@ -0,0 +1,8 @@ +<li> + <a href="{{ url }}" {% if blank %} target="_blank" {% endif %} {% if rel %} rel="{{ rel }}" {% endif %}>{{ text }}</a> + {% if wip %} + <span {% if wip_tooltip %}data-tooltip="{{ wip_tooltip }}" {% endif %}> + <span class="under-construction unselectable">⚠</span> + </span> + {% endif %} +</li>
\ No newline at end of file diff --git a/templates/shortcodes/window.html b/templates/shortcodes/window.html new file mode 100644 index 0000000..8abddbe --- /dev/null +++ b/templates/shortcodes/window.html @@ -0,0 +1,3 @@ +<div class="window"> +{{ body | safe }} +</div> diff --git a/templates/shortcodes/window_body.html b/templates/shortcodes/window_body.html new file mode 100644 index 0000000..f7c84a7 --- /dev/null +++ b/templates/shortcodes/window_body.html @@ -0,0 +1 @@ +<div class="window-body">{{ body | markdown | safe }}</div> |
