Jelajahi Sumber

Added current ver of my website

computer_glamour 6 jam lalu
induk
melakukan
bcbaba66a3
100 mengubah file dengan 10895 tambahan dan 0 penghapusan
  1. 50 0
      about.html
  2. TEMPAT SAMPAH
      bg.gif
  3. 79 0
      blog/blog.php
  4. 87 0
      blog/index.php
  5. 204 0
      blog/posts/games.md
  6. 29 0
      blog/posts/hello-world.md
  7. 81 0
      blog/posts/software.md
  8. TEMPAT SAMPAH
      blog/posts/tierlist.png
  9. 93 0
      blog/posts/web_design.md
  10. 127 0
      blog/video_games.php
  11. TEMPAT SAMPAH
      bsod.gif
  12. TEMPAT SAMPAH
      buttons/-18.gif
  13. TEMPAT SAMPAH
      buttons/datakrash.png
  14. TEMPAT SAMPAH
      buttons/fdr.png
  15. TEMPAT SAMPAH
      buttons/fedi.gif
  16. TEMPAT SAMPAH
      buttons/lakes.png
  17. TEMPAT SAMPAH
      buttons/lunya.gif
  18. TEMPAT SAMPAH
      buttons/nadeko.png
  19. TEMPAT SAMPAH
      buttons/notread.gif
  20. TEMPAT SAMPAH
      buttons/pc_glam.png
  21. TEMPAT SAMPAH
      buttons/right2repair.gif
  22. TEMPAT SAMPAH
      buttons/sel.png
  23. TEMPAT SAMPAH
      buttons/webring.gif
  24. TEMPAT SAMPAH
      buttons/wiby.org.gif
  25. TEMPAT SAMPAH
      buttons/xmpp.gif
  26. TEMPAT SAMPAH
      buttons/yuidev.png
  27. TEMPAT SAMPAH
      capri.gif
  28. TEMPAT SAMPAH
      clippy.gif
  29. TEMPAT SAMPAH
      computer-pixel.gif
  30. TEMPAT SAMPAH
      favicon.ico
  31. TEMPAT SAMPAH
      fish.gif
  32. 82 0
      hit-counter/README.md
  33. TEMPAT SAMPAH
      hit-counter/bg.png
  34. TEMPAT SAMPAH
      hit-counter/counter.png
  35. 1 0
      hit-counter/counter.txt
  36. 5 0
      hit-counter/node/.gitignore
  37. 152 0
      hit-counter/node/README.md
  38. 6 0
      hit-counter/node/nodemon.json
  39. 2668 0
      hit-counter/node/package-lock.json
  40. 27 0
      hit-counter/node/package.json
  41. 461 0
      hit-counter/node/src/hit-counter.ts
  42. 33 0
      hit-counter/node/src/index.ts
  43. 92 0
      hit-counter/script.php
  44. 324 0
      index.php
  45. TEMPAT SAMPAH
      netscape.gif
  46. TEMPAT SAMPAH
      pc.gif
  47. 98 0
      pc.html
  48. TEMPAT SAMPAH
      pc.png
  49. 57 0
      pgp.html
  50. TEMPAT SAMPAH
      pink_guy.gif
  51. 1 0
      privatebin/put_private_bin_here
  52. 316 0
      robots.txt
  53. TEMPAT SAMPAH
      shutdown.gif
  54. 177 0
      style.css
  55. TEMPAT SAMPAH
      this-is-the-end.gif
  56. 5 0
      vault/gifs/README.md
  57. TEMPAT SAMPAH
      vault/gifs/bg.gif
  58. TEMPAT SAMPAH
      vault/gifs/capri.gif
  59. TEMPAT SAMPAH
      vault/gifs/clippy.gif
  60. TEMPAT SAMPAH
      vault/gifs/computer-pixel.gif
  61. TEMPAT SAMPAH
      vault/gifs/fish.gif
  62. TEMPAT SAMPAH
      vault/gifs/netscape.gif
  63. TEMPAT SAMPAH
      vault/gifs/pc.gif
  64. TEMPAT SAMPAH
      vault/gifs/pink_guy.gif
  65. TEMPAT SAMPAH
      vault/gifs/shutdown.gif
  66. TEMPAT SAMPAH
      vault/gifs/start_button.gif
  67. TEMPAT SAMPAH
      vault/gifs/this-is-the-end.gif
  68. TEMPAT SAMPAH
      vault/gifs/welcome.gif
  69. 88 0
      vault/index.php
  70. 7 0
      vault/nodes-effect/README.md
  71. 224 0
      vault/nodes-effect/nodes.html
  72. 223 0
      vault/nodes-effect/nodes.html.txt
  73. 5 0
      vault/php-makrdown-blog/README.md
  74. 79 0
      vault/php-makrdown-blog/blog.php.txt
  75. 84 0
      vault/php-makrdown-blog/index.php.txt
  76. TEMPAT SAMPAH
      vault/wallpapers/bg.png
  77. 9 0
      vault/xmpp-registration-form/README.md
  78. 63 0
      vault/xmpp-registration-form/index.html.txt
  79. 35 0
      vault/xmpp-registration-form/register.php.txt
  80. 1994 0
      vendor/Parsedown.php
  81. 686 0
      vendor/ParsedownExtra.php
  82. 22 0
      vendor/autoload.php
  83. 119 0
      vendor/bin/generate_vcards
  84. 5 0
      vendor/bin/generate_vcards.bat
  85. 37 0
      vendor/bin/naturalselection
  86. 5 0
      vendor/bin/naturalselection.bat
  87. 37 0
      vendor/bin/sabredav
  88. 5 0
      vendor/bin/sabredav.bat
  89. 119 0
      vendor/bin/vobject
  90. 5 0
      vendor/bin/vobject.bat
  91. 579 0
      vendor/composer/ClassLoader.php
  92. 396 0
      vendor/composer/InstalledVersions.php
  93. 21 0
      vendor/composer/LICENSE
  94. 10 0
      vendor/composer/autoload_classmap.php
  95. 16 0
      vendor/composer/autoload_files.php
  96. 9 0
      vendor/composer/autoload_namespaces.php
  97. 17 0
      vendor/composer/autoload_psr4.php
  98. 50 0
      vendor/composer/autoload_real.php
  99. 84 0
      vendor/composer/autoload_static.php
  100. 607 0
      vendor/composer/installed.json

+ 50 - 0
about.html

@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+    <html lang="en">
+    <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>about me</title>
+    <link rel="stylesheet" href="./style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+    </head>
+    <body>
+        <div class="container bio">
+            <h1>✨️ About me ✨️</h1>
+
+<img src="./pink_guy.gif" class="small-pic">
+
+<p>Pronouns: He/Him</p>
+
+            <p>I made this website myself. Yes it is very simple, yes i wanted it this way. This page can be run without JS, and I have not used any site generator nor any CMS to make it.</p>
+
+            <p>I am free software proponent and i was a long time FSF and FSFE supporter. I use linux everyday. I distro hopped for a while and right now i have settled on linux mint. I used to use arch linux and gentoo, i was customizing everything until i realized that i want my time back, and switched to something that is good out of the box</p>
+
+<p>I am working in IT. I have most of the expertise in designing system integrations and network solutions. Network or infrastructure designs and sysadmin stuff are the things that i have most knowledge in. I also have some experience in telecom stuff.</p>
+
+<p>I love diy electronics. I build a few devices designing my own PCBs and programming microcontrollers for it. My favorite platforms are Arduino and ESP32. I support right to repair. </p>
+
+<p>I love video games. I really like older retro games, my favorite platforms are GBA and DS. I also like some more recent stuff too. But i think that games are much worst, because of corporate greed nowadays. You can check out my <a class="no-button" href="https://computer.glamour.ovh/blog/video_games.php">video game list</a> I try to update it every now and then with new games that i have played.</p>
+
+<p>This is not my only website that i host. If you are geeky like me, please reach out and maybe we can get to know one another :3</p>
+        </div>
+
+
+    <footer>
+        <p class="center"><img src="./favicon.ico">(c) computer_glamour</p>
+    
+    <img src="./buttons/-18.gif" class="footer-button">
+    <img src="./buttons/xmpp.gif" class="footer-button">
+    <img src="./buttons/notread.gif" class="footer-button">
+    <img src="./buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+    </body>
+
+    </html>

TEMPAT SAMPAH
bg.gif


+ 79 - 0
blog/blog.php

@@ -0,0 +1,79 @@
+<?php
+declare(strict_types=1);
+
+// ========================
+// CONFIG
+// ========================
+$blogDir = __DIR__ . '/posts/';
+$defaultPost = 'index';
+
+// ========================
+// SECURITY: sanitize input
+// ========================
+$post = $_GET['b'] ?? $defaultPost;
+
+// allow only safe filenames
+if (!preg_match('/^[a-zA-Z0-9_-]+$/', $post)) {
+    http_response_code(400);
+    die('Invalid post name.');
+}
+
+$mdFile = $blogDir . $post . '.md';
+
+if (!file_exists($mdFile)) {
+    http_response_code(404);
+    $mdContent = "# 404\n\nPost not found.";
+} else {
+    $mdContent = file_get_contents($mdFile);
+}
+
+// ========================
+// MARKDOWN PARSER
+// ========================
+require_once __DIR__ . '/../vendor/Parsedown.php';
+require_once __DIR__ . '/../vendor/ParsedownExtra.php';
+
+
+
+$parser = new ParsedownExtra();
+$parser->setSafeMode(false);       // allow HTML in markdown
+$parser->setMarkupEscaped(false);  // don't escape HTML
+
+$htmlContent = $parser->text($mdContent);
+?>
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Blog</title>
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+    </style>
+</head>
+<body>
+
+
+<main class="container interests">
+  <?= $htmlContent ?>
+</main>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+</html>

+ 87 - 0
blog/index.php

@@ -0,0 +1,87 @@
+<?php
+declare(strict_types=1);
+
+$postsDir = __DIR__ . '/posts/';
+$posts = [];
+
+// scan markdown files
+foreach (glob($postsDir . '*.md') as $file) {
+    $filename = basename($file, '.md');
+    $content = file_get_contents($file);
+
+    // extract first markdown H1
+    if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
+        $title = trim($matches[1]);
+    } else {
+        $title = $filename;
+    }
+
+    $posts[] = [
+        'slug' => $filename,
+        'title' => $title,
+        'time' => filemtime($file),
+    ];
+}
+
+// sort newest first
+usort($posts, fn($a, $b) => $b['time'] <=> $a['time']);
+?>
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Blog</title>
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+<body>
+<div class="container bio">
+  <h1>📝 Blog 💻</h1>
+
+<img src="../fish.gif" class="small-pic">
+<p class="center">Blog posts:</p>
+  <?php if (empty($posts)): ?>
+    <p>Brak wpisów.</p>
+  <?php else: ?>
+    <ul class="post-list">
+      <?php foreach ($posts as $post): ?>
+          <a class="no-button" href="/blog/blog.php?b=<?= htmlspecialchars($post['slug']) ?>">
+            <?= htmlspecialchars($post['title']) ?>
+          </a>
+          <time datetime="<?= date('c', $post['time']) ?>">
+            <?= date('Y-m-d', $post['time']) ?>
+          </time>
+            <br>
+      <?php endforeach; ?>
+                <a class="no-button" href="/blog/video_games.php">
+            Video Game Ranking          </a>
+          <time datetime="2026-02-10T14:23:27+00:00">
+            2026-02-10          </time>
+            <br>
+
+    </ul>
+  <?php endif; ?>
+
+</div>
+</main>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+</html>

+ 204 - 0
blog/posts/games.md

@@ -0,0 +1,204 @@
+# Console tierlist
+
+![](https://computer.glamour.ovh/blog/posts/tierlist.png)
+
+## A few words of explanation
+
+In the future, I plan to write a post about my favorite retro games,
+because list that is at bottom of this post mainly concerns games that are relatively new
+(released in the 6th generation of consoles and/or later), and these are
+generations of games in which diamonds (such as FEZ or The Witcher 3)
+are rather exceptions to the rule, and games that, at least for me, are
+interesting and give me a good dose of fun are a rarity in the new
+generations of consoles and PC games. Therefore, these ratings are about
+the games that I considered worth mentioning, but for contrast, I also
+mentioned a few games that I thought would be good (because, for
+example, I played previous iterations of a given brand) and turned out
+to be a total failure. The table format also forces me to write a
+maximum of 2-3 sentences per game. Perhaps I will write a more extensive
+review of my favorite platforms when it comes to retro games.
+
+I also omitted competitive games such as LoL, TF2, CS:GO, Fortnite, etc.
+from this list. Why? Mainly because these games are not closed
+compositions, and if I wanted to evaluate them, I would have to evaluate
+a specific season in a given game, for example. Secondly, because I am
+not a fan of such games (I had a moment in my life when I tried to play
+TF2), I don\'t like them. Thirdly, from an ethical point of view, I
+disagree with what these games represent or have in their content: microtransactions,
+buying and trading skins, many anti-cheat systems do not work on Linux 
+and not because it is technically impossible, but because many of the
+companies responsible for these games have decided to exclude the entire
+Linux system because it is by definition incompatible with anti-cheat
+systems. Most games of this type fall under
+at least these two accusations.
+
+| Games      | 1: Seasons | 2: Skins | 3: Anticheat on Linux |
+|------------|------------|----------|------------------------|
+| LoL        | ✔️         | ✔️       | ❌                     |
+| TF2        | ❌         | ✔️       | ✔️ (VAC)               |
+| CS:GO      | ✔️         | ✔️       | ✔️ (VAC)               |
+| Fortnite   | ✔️         | ✔️       | ❌                     |
+
+
+This post would be far too long if I wanted to explain and describe in
+detail why I think these three points work against players. However, to
+sum up, if a game meets at least two of these points, I don't think it
+makes sense for me to spend the time needed to write a blog post about
+it or rate it on my list.
+
+## Console Generation Tierlist
+
+### S Tier
+
+GameBoy Advance and Nintendo DS
+
+In my opinion, these are the two best consoles ever released. I could
+also add the 3DS here, but I don\'t think it has as diverse a game
+library as the GBA and NDS.
+
+Why this choice?
+
+Some of my favorite game series were released on the GBA. In my opinion,
+the best remake in the Pokemon series is Pokemon FireRed. There are also
+very good iterations of the Zelda series, and great strategy games such
+as Advance Wars and Fire Emblem. Nothing more, nothing less. Admittedly,
+the display on the original GBA wasn\'t the best, but nowadays most
+people play these games on emulators or Linux handheld consoles.
+
+As for the Nintendo DS, it is one of the consoles with the most diverse
+game library. Great RPGs, great puzzle games, great platformers, and
+even rhythm games. When it comes to this console, in my opinion, playing
+these games on an emulator is a bit pointless. The console itself
+suffers from broken hinges, but that doesn\'t change the fact that some
+of the best Pokémon and Mario games were released on this platform.
+
+### A Tier
+
+Nintendo 3DS, Classic GameBoy and Gameboy Color, PlayStation 1, Sega
+Dreamcast, Nintendo Wii, Playstation 2, Playstation Portable PSP.
+
+Nintendo 3DS. The best portable console released to date, it can play
+NDS games and, with emulators, games from other consoles. It does not
+have as many different games as the NDS, but it is still a console worth
+considering for any video game fan. In a way, it is the console that the
+NDS aspired to be but failed to become. Despite this, it still sold
+better than its competitors\' portable consoles.
+
+Classic GameBoy and Gameboy Color. No backlight, poor graphics even for
+its time, and yet it\'s in Tier A. This is the console that started one
+of my favorite series, Pokemon, which is mainly why it\'s in this spot.
+Besides, many other great games were released for this console, some of
+my favorites being: Tetris, TLoZ: Links\'s Awakening, Mario Picross.
+Nowadays, I recommend playing classic GB games on the Super Gameboy
+emulator, which is an emulator of the SGB add-on for the SNES console.
+It has additional frames and colors for games, which makes them look
+less crude than on the original.
+
+Playstation 1. I\'m not a big Sony gamer, but the PS1 had such a diverse
+game library that I also found something for myself: Crash Bandicoot,
+Final Fantasy.
+
+Sega Dreamcast. One of the most underrated consoles of all time. The
+first in its generation with such graphics. The first with online
+features. With a good game library, yet remembered in history as strange
+and forgotten.
+
+Nintendo Wii. The console that started the motion gaming trend, backward
+compatible with GameCube, is in fact an extension of GameCube with
+several great games (e.g., Mario Galaxy) and motion features. It outsold
+all other consoles of its generation.
+
+PlayStation 2. As I mentioned earlier, I am far from being a Sony fan,
+but in order for this subjective assessment not to be too subjective, I
+couldn\'t give the best-selling console in history a rating lower than
+tier A.
+
+PSP. I remember this console mainly for a few cool games (Lemmings,
+LocoRoco, Patapon, etc.) and for the fact that it had real 3D graphics
+in games, rather than a voxel engine with overlaid textures like the
+NDS.
+
+### B Tier
+
+Nintendo GameCube, Sega GameGear, Nintendo 64, Nintendo Entertainment
+System, Super Nintendo Entertainment System, Nintendo Switch. GameCube.
+The first Nintendo console to compete with the PS1, it was
+technologically much better than the PS1, but unfortunately, due to a
+few bad decisions, it has been somewhat forgotten. Although I still
+think it is much better to have a small library of games, but with only
+gems in it, than to have a million games, half of which no one will play
+after 20 years. GC games have proven their timelessness, as some of them
+are still being released as remakes for subsequent Nintendo platforms.
+
+GameGear. A competitor to the Gameboy, better than it in every way,
+although the game library is a little more meager. But I think it
+deserves a mention. Nintendo 64. In terms of its market position and
+game library, this console was similar to the GC, except that in this
+case the limitation was the hardware rather than management decisions.
+The design was very strange to still use cartridges for gaming instead
+of discs. Nevertheless, several games were released for it, thanks to
+which series such as Mario and Zelda became video game legends.
+
+NES SNES. I put them in one category because, in my opinion, they should
+be evaluated in this way. These are consoles that have aged poorly, and
+it is difficult to play some games from these platforms nowadays. In my
+opinion, SNES performs much better in this respect, as there are several
+timeless gems that are worth playing even today. Nintendo Switch. Many
+people will ask why it ranks so low. My answer is this: it is a console
+that started the form factor of a tablet combined with a gamepad (no,
+Steam Deck wasn\'t the first), you can still see remnants of the Wii\'s
+motion controller, but it\'s not the same level of innovation as in the
+days of the DS and 3DS. The management\'s decisions regarding consumer
+treatment also leave much to be desired, and it is one of the first
+consoles to introduce a policy of remote console blocking and remote
+game deletion.
+
+### C Tier
+
+Nintendo Wii U, Playstation Vita, Xbox One, Playstation 4, Xbox 360,
+Xbox Classic, Playstation 3
+
+Nintendo Wii U. The games for this console were great, with lots of
+remakes from GC in higher resolution and with better assets, as well as
+many original titles, including Zelda Breath of The Wild. However, the
+console had too small a game library and was not innovative enough to
+gain widespread popularity. If this assessment were even more
+subjective, this console would be in tier A or at least B.
+
+Xbox One, Xbox 360, Xbox Classic. Not many exclusive games for these
+platforms. The devices themselves are not particularly innovative. The
+first Xbox is actually the same as the latest one, the only difference
+being the processing power . In its heyday, I had an X360, the Kinect
+games were terrible, and I only played a few titles that I could just as
+easily have played on my computer. PlayStation 3 and Vita. Two consoles
+that had a very difficult start at launch, and in fact, publishers were
+very reluctant to release games for them. It was only when modders got
+their hands on these consoles (Vita) or at the end of their life cycle
+(PS3) that they came back to life. I haven\'t played much on these
+consoles; I\'ve held a PS Vita a few times in my life and played Gran
+Turismo on PS3 a few times. However, this does not change the fact that
+I do not have a good opinion of these consoles. PS4. A console that
+fixed the problems of its older brothers, Sony withdrew from the mobile
+market and introduced a real console with real network features and a
+good game library. So why so low? Because it\'s not a console I\'m
+familiar with, it doesn\'t have a series of games I\'d like to play,
+it\'s a kind of console that has disappeared somewhere between the weak
+PS3 and the new PS5 console.
+
+### D Tier
+
+VirtualBoy
+
+It was a ridiculous console built on the wave of what was then very
+early VR. It was no more innovative than the GameBoy, it was very
+unergonomic, and as a result it is very rare and collectible nowadays,
+and therefore also expensive. It was a ridiculous console built on the
+wave of what was then very early VR. It was no more innovative than the
+GameBoy, it was very unergonomic, and as a result it is very rare and
+collectible nowadays, and therefore also expensive.
+
+### Games Tierlist
+
+Wanna read what do i think about games themselves?
+
+[press here](https://computer.glamour.ovh/blog/video_games.php)

+ 29 - 0
blog/posts/hello-world.md

@@ -0,0 +1,29 @@
+# First blog post
+
+I will post there anything that interests me at the moment. I will maybe explain in the second blog post how this blog is made and how it works. But for now i just wanted to throw in some markdown test file and see if everything works properly. 
+
+And from what i can see yeah it works well.
+
+## List
+
+Functionalities implemented in this blog.
+
+- Posts are markdown files
+- Embeed images
+- Render and parse tables
+- Embeed code blocks
+
+## Table
+
+| Name | Role |
+|------|------|
+| glamour | Dev |
+| PHP | ❤️ |
+
+## Image
+![Alt text](https://picsum.photos/600/300)
+
+## Code
+```php
+echo "Hello Markdown!";
+```

+ 81 - 0
blog/posts/software.md

@@ -0,0 +1,81 @@
+# How I do my computing
+
+In this blog post i will describe how i use computers X3. What distro what software do i use. How i use internet and other productivity tasks and software.
+
+## GNU/Linux distro
+
+As i have described in my about me section i am using Linux mint. Some people asked me why this why not something more customizable. So i decided to write a small blog post about that.
+
+I am using Linux since 2015 i switched to it when Microsoft released windows 10 and people were very unhappy with the new changes hardware requirements and other junk. I used it as my main operating system for web browsing, coding, experiments etc. I still kept windows laptop for gaming. 
+
+In those days 2015-2016 i have used different Ubuntu flavors i was hopping between classic Ubuntu with Unity desktop (that was a good desktop none of this Gnome stuff :3) and Xfce flavor called Xubuntu. I liked them they worked were easy to use and the desktop environment was not getting in my way while i was doing things. In those days my computer was mostly used to watch youtube videos occasionally doing some homework in high school and stuff like that, not a huge computing need at all.
+
+From 2016 until 2018 roughly i was in the phase of using custom Arch Linux installs and Gentoo. I do not remember much from those days, my mindset about computing has changed so much that i hardly even remember how to compile a program for Gentoo. But i remember one thing that liked very much from those days, i have used for some time a customized Xubuntu install where instead of default window manager used in Xubuntu i replaced it with i3, but everything else was left untouched so i had this Frankenstein of a desktop where everything was like in xfce but windows were tiled by default instead of stacked. I liked this setup.
+
+Since 2018 there is proton so i didn't needed that windows laptop for gaming anymore. So i switched fully into linux (yeah i had a short break to try win11 but it went pretty badly). First distro that i used after making the full "switch" was MXlinux i used it for roughly 2-3 years, and back in those days MXlinux didn't have anything like dist-upgrade so it was mandatory to backup your files copy list of installed packages and re do it after re install. I hated that, another issue i had was that MXlinux had problems with supporting sleep and lock screen on my dell laptop. So after getting fed up with all of it i thought okay let's return to the roots, my first thought was let's use Xubuntu, but i just didn't felt like it and i wanted to try something new. Finally i decided to install Linux Mint. After whole 10 year journey into the linux world i have returned back to a simple just works distro. After using it for 3 years roughly, I am happy i don't need anything else from it, lack of customization is kinda good for me, nothing to customize nothing to break :3
+
+## My computer
+
+As of 2026 I mainly use two computers, one as my workstation at home, and other one as a mobile device. Both of them are running Linux Mint. 
+
+### Mobile device
+
+[Dell Vostro 5590](https://www.notebookcheck.net/Dell-Vostro-15-5590-i5-10210U.450559.0.html)
+
+Upgraded it to 16 GB of ram and i added second SSD for synchronizing my commonly used directories with files. Throughout it's life i replaced battery once. 
+
+Not a huge fan of the build quality of this laptop if you are looking for a laptop yourself i do not recommend this one. Some of those laptops had a factory problem with hinges where after two years of usage they just break completely and are unable to keep the lid open.
+
+### Home workstation
+
+As my home workstation I wanted something on a budget, something small yet powerful enough. So I decided to buy one of those mini pc. I wanted 32GB of RAM, 1TB SSD and if possible a decent CPU that can handle some casual gaming with a gamepad on a couch :3. 
+
+ [T-Bao Mini PC MN58U](https://www.techradar.com/pro/t-bao-mini-pc-mn58u-review)
+
+Yeah Chinese, isn't like everything manufactured there anyway. I bought for a good price in 2022. I love the form factor, small you can easily throw it into your backpack, then connect it to a TV in the hotel room and enjoy some couch gaming. Selection of ports is decent, it can handle two monitors easily. 
+
+### Other computers
+
+I own other computers that are for some special occasions, or are doing some specialized tasks. Maybe that would be a good topic for the next blog post.
+
+## What i do on my computer
+
+Mostly boring stuff, but if you are really interested here is a short description of what I do. 
+
+### Everyday stuffs
+
+I mostly use graphical user environment, mainly because i am lazy and i find GUI works better for me. It doesn't mean that i don't use terminal, but my terminal is mostly connected to some linux server doing admin work. Most of the documents i edit on my computer are in some formatting language either Markdown, LaTeX or HTML. I hate all of the WYSIWYG editors like Microsoft Word or Libre Office. I am not a power user when it comes to LaTeX i use pretty much all of the basic functions, maybe i will draw a simple graph here and there with pgfplot.
+
+### Office type tasks
+
+I work a lot with emails and calendar appointments, so i use Thunderbird a lot. I have OpenPGP configured in it, and i synchronize my calendar to my selfhosted CalDAV server. For note taking, documentation and other personal geeky stuff i use my selfhosted DokuWiki instance. I also use DB browser with SQLite if need to handle a little bigger amount of structured data, I hardly ever use excel and similar programs. Nothing interesting here. 
+
+### Coding
+
+When it comes to coding most of the coding I do is either Python, PHP or some shell scripting. I do not use any fancy IDE with any of those fancy color schemes. For Python I use pure IDLE, and for others Xed which is a built in Linux Mint text editor works fine. Please note that i am not a full time programmer and coding is mostly done by me in my free time or sometimes for work but coding is like max 10% of my time at work. 
+
+### Internet
+
+Internet is where i spend most of my time on computer. Either engaging in online communities, doing some research for work, chatting with friends, experimenting with some selfhosting or writing and managing my webpages. I spend a lot of time fine tuning my hosting setup, writing tutorials or documenting what i have recently found on the web :3.
+
+#### How I use internet
+
+So, when i connect from somewhere where i do not know ... what devices are between me and the Internet, I will use VPN to direct all of my traffic back home, and then i go out into the internet there. I do not run javascript on websites i do not know, or i haven't checked what kind of java script is there, for some websites i use TOR only. Web browser that i use is firefox. For chatting use mostly XMPP, most people i know either already used it, or were tech savvy enough to transfer them over to it. I send emails with PGP encryption. I do not use any of the google services  nor microsoft services. I run my own proxy search engine, i run my own file sync software and i run my own calendar and i use OVH as mail hosting provider. 
+
+Extensions in Firefox that i use are:
+
+- FoxyProxy
+  - awesome extension if you need to switch beetween multiple different proxies.
+- Privacy badger
+  - stops trackers and is recommended and developed by EEF
+- No script
+  - I am the one in charge, and i decide what code i wanna run on my computer :3
+- Ghostery
+  - privacy repecting adblock that is mostly on permissive free software licenses, i think i found it though EFF website
+
+I buy media online whenever i can, i try to buy them without a DRM, there are still a lot of artist that sell MP3 online for you to legally buy. When it comes to movies i mostly rip them from disks, or i just borrow from friends. 
+
+### How this site is maintained 
+
+I just map my VPS as network drive in linux mint, and i open HTML and Markdown files with text editor in Linux Mint and edit them with my bare hands. Yeah that simple ... you can tell that i am a minimalist when it comes to that :3
+

TEMPAT SAMPAH
blog/posts/tierlist.png


+ 93 - 0
blog/posts/web_design.md

@@ -0,0 +1,93 @@
+# Key Principles of Web Design according to me 
+
+
+A set of rules and tips on how to create good websites that focus on content rather than appearance. These guidelines have been discussed with several online communities I know, and the consensus is that the tips below are absolutely essential for beginner web developers creating their first websites.
+
+
+Most of the tips below were written with the so-called internet revival movement in mind, sometimes called Web 2.0 or the indie web. This is a movement that seeks to bring back imperfectly crafted websites created out of passion by people for people.
+
+
+## Issues and doubts
+
+
+At the very beginning, we all have questions; we don’t know what to do. We don’t know how to start or where to begin. Common questions at the very beginning include:
+
+  * I have a website, but I’d like to redesign it or build it myself.
+  * I’m a beginner at web development; I need advice and guidance.
+  * How can I feel less overwhelmed by all these possibilities and all this knowledge?
+
+## Possible answers and solutions
+
+
+### First, take a moment to ask yourself a few questions
+
+
+**If you already have a website**, think about what you don’t like or what you do like about it, and consider what you want to change. Think about how you want your website to look and what kind of impression you want it to make after the changes.
+
+
+**If you don’t have a website yet**, ask yourself: What do you want to include on your website? This will give you a clear vision of what your website should really be.
+
+
+Go browse some websites, search on independent search engines (e.g., <a class="no-button" href="https://wiby.org">Wiby Search</a>) or in communities like <a class="no-button" href="https://neocities.org">Neocities</a>. Look for sites that you like or that inspire you; maybe finding a ready-made template will help.
+
+
+Personally, I think you might want to consider these few tips as well (note: highly subjective):
+
+
+  * Don’t follow trends—this is supposed to be your site that showcases you; don’t force yourself to fit into trends.
+  * It’s much easier if you already have some content—your old posts, a collection of notes, photos, artwork, projects, or anything else you’ve created in the past. Then it doesn’t take much effort or energy to turn that into posts on your site.
+  * Remember, you can have poorly formatted HTML or CSS code, but the site will still have a great vibe and atmosphere :-)
+
+### Start with small steps
+
+
+Start with a simple page, edit one page at a time, work on the details, and publish it online. Don’t try to do everything at once or rush through it. Start with simple features—don’t jump straight into adding JavaScript to your site; start with HTML and CSS first. Tutorials and guides can help here; after each one, try to build a simple page that demonstrates what you’ve learned. 
+
+
+If you’re rebuilding something that already exists, get to know the technology the site is built on in depth, or ask yourself the questions from the previous section, and consider whether your site is really so big that you need a massive CMS like WordPress ;-).
+
+
+Most importantly, remember that we all started somewhere, and no one ever built anything right away; everything takes a little time, and through trial and error, you’ll achieve the desired result.
+
+
+### Just do it
+
+
+Once you’ve learned the basics of HTML and CSS, just start creating something. These days, there are few technologies that let you start creating something right away so easily. 
+
+
+Remember, creating something is always better than doing nothing. Even if a silly idea just popped into your head, try to bring it to life. Remember, technology isn’t just a science—it’s also a way to express yourself.
+
+### Borrow Ideas
+
+
+Check out the websites you like, ask their creators how they did certain things, and use the `view-source` option in your browser. Copy the code to your own site, modify it, and see how it looks. Alternatively, download a template from the internet and customize it to suit your needs.
+
+
+My first websites were built by looking at what I liked on other sites and copying those features into my own.
+
+
+### A Sea of Possibilities
+
+
+You can do anything you want—the possibilities are endless. 
+
+
+A few observations you’ve probably already figured out yourself:
+
+
+  * There are many rules about web design—read up on them, check them out… and above all, break them ;-)
+  * Do what you feel like doing; there’s no single right way to build a website.
+  * Remember, technology is just a tool to achieve the effect you desire
+
+
+## Final Thoughts
+
+
+Don’t overwork yourself; take breaks; try not to work on the same thing constantly. I’ve been in situations where sitting on one page and tweaking details burned me out. Remember, the whole idea of the indie web is that your site should be human… and not perfect, just like people—don’t try to achieve a perfection that resembles something generated by an LLM.
+
+
+Don’t judge yourself too harshly, and don’t compare your sites to others too much. Remember, sites like mine were built over the course of years by a guy who’s a little too obsessed with what he does and has way too much free time LOL.
+
+
+And one last thing: if your website doesn't display anything without JavaScript, you know you've gone too far...

+ 127 - 0
blog/video_games.php

@@ -0,0 +1,127 @@
+<?php
+declare(strict_types=1);
+
+$postsDir = __DIR__ . '/posts/';
+$posts = [];
+
+// scan markdown files
+foreach (glob($postsDir . '*.md') as $file) {
+    $filename = basename($file, '.md');
+    $content = file_get_contents($file);
+
+    // extract first markdown H1
+    if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
+        $title = trim($matches[1]);
+    } else {
+        $title = $filename;
+    }
+
+    $posts[] = [
+        'slug' => $filename,
+        'title' => $title,
+        'time' => filemtime($file),
+    ];
+}
+
+// sort newest first
+usort($posts, fn($a, $b) => $b['time'] <=> $a['time']);
+?>
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Blog</title>
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+<body>
+<div class="container bio">
+  <h1>Video Game list</h1>
+<p>I have a SQLite database where I store games that I have played. This database contains title of the game, platform on which I have played or emulated the game, my grade and short description of what do I think of the game. Grading system works like this: 5 means mixed feelings, 1 worst games, 10 best games. Happy reading :3</p>
+    <form method="POST" action="">
+        <input type="text" name="search" placeholder="Search by game name" style="margin-bottom: 10px; padding: 8px; width: calc(100% - 16px);"/>
+        
+        <select name="sort" style="padding: 8px; margin-bottom: 10px;">
+            <option value="">Sort By</option>
+            <option value="name_asc">Name (A-Z)</option>
+            <option value="name_desc">Name (Z-A)</option>
+            <option value="grade_asc">Grade (Lowest to Highest)</option>
+            <option value="grade_desc">Grade (Highest to Lowest)</option>
+        </select>
+
+        <input type="submit" value="Apply" style="padding: 8px;"/>
+    </form>
+
+    <table border="1" style="width: 100%; border-collapse: collapse;" id="gameTable">
+        <thead>
+            <tr>
+                <th>Game Name</th>
+                <th>Platform</th>
+                <th>Grade</th>
+                <th>Description</th>
+            </tr>
+        </thead>
+        <tbody>
+        <?php
+        // Establish a database connection
+        $db = new SQLite3('/ostrowski.net.pl/games.db');
+
+        // Initialize variables for search and sorting
+        $search = isset($_POST['search']) ? $_POST['search'] : '';
+        $sort = isset($_POST['sort']) ? $_POST['sort'] : '';
+
+        // Building query based on search and sort
+        $query = "SELECT * FROM games WHERE name LIKE :search";
+
+        if ($sort === 'name_asc') {
+            $query .= " ORDER BY name ASC";
+        } elseif ($sort === 'name_desc') {
+            $query .= " ORDER BY name DESC";
+        } elseif ($sort === 'grade_asc') {
+            $query .= " ORDER BY grade ASC";
+        } elseif ($sort === 'grade_desc') {
+            $query .= " ORDER BY grade DESC";
+        }
+        
+        $stmt = $db->prepare($query);
+        $stmt->bindValue(':search', '%' . $search . '%', SQLITE3_TEXT);
+
+        // Execute the query
+        $results = $stmt->execute();
+
+        // Loop through the results and display them in the table
+        while ($row = $results->fetchArray(SQLITE3_ASSOC)) {
+            echo "<tr>";
+            echo "<td>{$row['name']}</td>";
+            echo "<td>{$row['platform']}</td>";
+            echo "<td>{$row['grade']}</td>";
+            echo "<td>{$row['description']}</td>";
+            echo "</tr>";
+        }
+        ?>
+        </tbody>
+    </table>
+
+</div>
+</main>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+</html>

TEMPAT SAMPAH
bsod.gif


TEMPAT SAMPAH
buttons/-18.gif


TEMPAT SAMPAH
buttons/datakrash.png


TEMPAT SAMPAH
buttons/fdr.png


TEMPAT SAMPAH
buttons/fedi.gif


TEMPAT SAMPAH
buttons/lakes.png


TEMPAT SAMPAH
buttons/lunya.gif


TEMPAT SAMPAH
buttons/nadeko.png


TEMPAT SAMPAH
buttons/notread.gif


TEMPAT SAMPAH
buttons/pc_glam.png


TEMPAT SAMPAH
buttons/right2repair.gif


TEMPAT SAMPAH
buttons/sel.png


TEMPAT SAMPAH
buttons/webring.gif


TEMPAT SAMPAH
buttons/wiby.org.gif


TEMPAT SAMPAH
buttons/xmpp.gif


TEMPAT SAMPAH
buttons/yuidev.png


TEMPAT SAMPAH
capri.gif


TEMPAT SAMPAH
clippy.gif


TEMPAT SAMPAH
computer-pixel.gif


TEMPAT SAMPAH
favicon.ico


TEMPAT SAMPAH
fish.gif


+ 82 - 0
hit-counter/README.md

@@ -0,0 +1,82 @@
+# Hit Counter - Because Nothing Says Retro Like a Hit Counter
+
+[Remember Hit Counters? Well, They’re Back. And They’re Gloriously useless](https://datakra.sh/logs/remember-hit-counters-well-theyre-back-and-theyre-gloriously-useless), personal websites were as unique as fingerprints, and every site proudly flaunted a hit counter? It was the ultimate digital badge of honor—a glaring declaration of how many lost souls had wandered onto your site. Fast forward to now, and it turns out you can relive those glitzy days. Yup, hit counters are back, and they are as gloriously pointless as ever!
+
+## Why, Though?
+
+Why on Satans earth would anyone need a hit counter in 2024? Honestly, I haven't got the slightest idea. Most modern web metrics tools can provide far more detailed insights. But hey, who says everything needs to make sense? Sometimes, a little nostalgia is all you need to spice things up.
+
+## Features
+
+- **Custom Main Text and Secondary Text**: Because why settle for one when you can have two?
+- **Hit Counter**: See how many times the page is hit. It’s like a pissing contest, but for web traffic.
+- **Frame Option**: For when you want to make things a tad more "extra."
+- **Custom Colors**: Match your counter to your site's funky style.
+- **PNG Output**: High-tech stuff.
+
+## Requirements
+
+- PHP 8.x or higher
+- GD Library enabled in PHP
+
+## Installation
+
+1. **Clone this fantastic repository**:
+
+    ```sh
+    git clone https://git.cyberwa.re/revengeday/hit-counter.git
+    ```
+
+2. **Navigate** to the project directory:
+
+    ```sh
+    cd hit-counter
+    ```
+
+3. **Set proper permissions** (because your web server demands it):
+
+    ```sh
+    sudo chown -R www-data:www-data .
+    sudo chmod -R 755 .
+    ```
+
+4. **Enable the GD Library** in your PHP configuration (`php.ini`):
+
+    ```ini
+    ; Make sure this line is uncommented
+    extension=gd
+    ```
+
+5. **Enjoy the Retro Vibes**: Access the script from your web browser or via `curl`:
+
+    ```sh
+    http://yourserver/hit-counter/script.php
+    ```
+
+## Configuration
+
+Edit `script.php` and personalize as much as you like. Feeling adventurous? Here are some things you might want to tweak:
+
+- **File Paths**: `$counterFile`, `$imageFile`
+- **Image URL**: `$backgroundImageUrl`
+- **Text**: `$customText`, `$secondaryText`
+- **Text Positions**: `$textPositionX`, `$textPositionY`, `$secondaryTextPositionX`, `$secondaryTextPositionY`
+- **Number Position**: `$numberPositionX`, `$numberPositionY`
+- **Colors**: `$textColorRGB`, `$secondaryTextColorRGB`, `$numberColorRGB`, `$frameColorRGB`
+- **Frame Toggle**: `$drawFrame`
+
+## Example
+![](https://counter.datakra.sh/example.png)
+```php
+$customText = "Hit Counter!";
+$secondaryText = "Super cyber, super cool!";
+$drawFrame = true;
+```
+
+## Contributing
+
+Want to make this even more useless? Fork it, hack it, and send me a pull request.
+
+## Contact
+
+Got questions or just feeling nostalgic? Hit me up on the fediverse: [@revengeday@corteximplant.com](https://corteximplant.com/@revengeday).

TEMPAT SAMPAH
hit-counter/bg.png


TEMPAT SAMPAH
hit-counter/counter.png


+ 1 - 0
hit-counter/counter.txt

@@ -0,0 +1 @@
+631

+ 5 - 0
hit-counter/node/.gitignore

@@ -0,0 +1,5 @@
+node_modules/
+.DS_Store
+hits.db
+hits.db-*
+example_counter_bg.png

+ 152 - 0
hit-counter/node/README.md

@@ -0,0 +1,152 @@
+# Hit Counter - Because Nothing Says Retro Like a Hit Counter
+
+[Remember Hit Counters? Well, They’re Back. And They’re Gloriously useless](https://datakra.sh/logs/remember-hit-counters-well-theyre-back-and-theyre-gloriously-useless), personal websites were as unique as fingerprints, and every site proudly flaunted a hit counter? It was the ultimate digital badge of honor—a glaring declaration of how many lost souls had wandered onto your site. Fast forward to now, and it turns out you can relive those glitzy days. Yup, hit counters are back, and they are as gloriously pointless as ever!
+
+## Why, Though?
+
+Why on Satans earth would anyone need a hit counter in 2024? Honestly, I haven't got the slightest idea. Most modern web metrics tools can provide far more detailed insights. But hey, who says everything needs to make sense? Sometimes, a little nostalgia is all you need to spice things up.
+
+## Features
+
+- **Custom Main Text and Secondary Text**: Because why settle for one when you can have two?
+- **Hit Counter**: See how many times the page is hit. It’s like a pissing contest, but for web traffic.
+- **Frame Option**: For when you want to make things a tad more "extra."
+- **Custom Colors**: Match your counter to your site's funky style.
+- **Custom Output**: jpg, png, webp, tiff, gif. High-tech stuff.
+
+## Requirements
+
+- Node.js 22.x or higher
+
+## Installation
+
+1. **Clone this fantastic repository**:
+
+    ```sh
+    git clone https://git.cyberwa.re/revengeday/hit-counter.git
+    ```
+
+2. **Navigate** to the project directory:
+
+    ```sh
+    cd hit-counter/node
+    ```
+
+3. **Install**:
+
+    ```sh
+    npm i
+    ```
+
+4. **Run devserver** to test it out locally:
+
+    ```sh
+    npm run dev
+    ```
+
+5. **Enjoy the Retro Vibes**: Access the script from your web browser or via `curl`:
+
+    ```sh
+    http://localhost:8000/
+    http://localhost:8000/hits # to see the count
+    http://localhost:8000/image # to see the image
+    http://localhost:8000/reset # to reset the count
+    ```
+
+## Configuration
+
+```ts
+// the config is defined in the HitCounterConfig interface
+interface HitCounterConfig {
+    siteId: number;
+    // path to the database file
+    counterDB: string;
+    // Background image URL for the counter display
+    backgroundImageUrl: string;
+    // local image path
+    imageFile: string;
+    backgroundCacheFile: string;
+    // Text settings to be displayed on the image
+    customText: string; // Main headline text
+    secondaryText: string; // Secondary descriptive text
+    textPositionX: number; // X position of the text
+    textPositionY: number; // Y position of the text
+    numberPositionX: number; // X position of the number
+    numberPositionY: number; // Y position of the number
+    fontSize: number; // Font size
+    fontFace: string; // Font face
+    borderWidth: number; // Border width
+    textColorRGB: RGBColor; // Text color
+    secondaryTextColorRGB: RGBColor; // Secondary text color
+    numberColorRGB: RGBColor; // Number color
+    frameColorRGB: RGBColor; // Frame color
+    drawFrame: boolean; // Whether to draw a frame around the counter
+}
+
+// currently only the ImageHitCounterRenderer is implemented, but this could be extended
+// to a canvas or html or whatever renderer
+```
+
+## Usage
+
+```ts
+import { HitCounter } from "./hit-counter";
+
+
+// default config
+defaultHitCounterConfig = {
+    siteId: 1,
+    counterDB: './hits.db',
+    backgroundImageUrl: 'https://datakra.sh/assets/example_counter.jpg',
+    imageFile: './example_counter.png',
+    backgroundCacheFile: './example_counter_bg.png',
+    customText: 'Hit Counter!',
+    secondaryText: 'Super cyber, super cool!',
+    textPositionX: 5,
+    textPositionY: 11,
+    numberPositionX: 170,
+    numberPositionY: 18,
+    fontSize: 10,
+    fontFace: 'fixed',
+    borderWidth: 3,
+    textColorRGB: { r: 253, g: 252, b: 1 },
+    secondaryTextColorRGB: { r: 0, g: 255, b: 0 },
+    numberColorRGB: { r: 255, g: 255, b: 255 },
+    frameColorRGB: { r: 255, g: 0, b: 0 },
+    drawFrame: true
+};
+
+// the default config will be used, but you can pass a custom config object as 
+// the first argument to the HitCounter constructor
+// e.g. const hitCounter = new HitCounter(customConfig);
+// the second argument is reserved for the renderer, but currently only the ImageHitCounterRenderer is implemented
+const hitCounter = new HitCounter();
+
+// increment the hit counter
+hitCounter.increment(); // async function
+
+// get the current hit count
+const count = await hitCounter.getCount();
+
+// get mime type
+const imageMimeType = hitCounter.getMimeType();
+
+// render the image
+const image = await hitCounter.render();
+
+// send to client however you like
+
+// reset the hit counter
+await hitCounter.reset();
+```
+
+
+
+
+## Contributing
+
+Want to make this even more useless? Fork it, hack it, and send me a pull request.
+
+## Contact
+
+Got questions or just feeling nostalgic? Hit me up on the fediverse: [@revengeday@corteximplant.com](https://corteximplant.com/@revengeday).

+ 6 - 0
hit-counter/node/nodemon.json

@@ -0,0 +1,6 @@
+{
+    "watch": ["src"],
+    "ext": "ts,json",
+    "ignore": ["src/**/*.spec.ts"],
+    "exec": "npx tsx ./src/index.ts"
+}

+ 2668 - 0
hit-counter/node/package-lock.json

@@ -0,0 +1,2668 @@
+{
+  "name": "hit_counter",
+  "version": "0.0.2",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "hit_counter",
+      "version": "0.0.2",
+      "license": "MIT",
+      "dependencies": {
+        "better-sqlite3": "^11.5.0",
+        "express": "^4.21.1",
+        "sharp": "^0.33.5"
+      },
+      "devDependencies": {
+        "@types/better-sqlite3": "^7.6.11",
+        "@types/express": "^5.0.0",
+        "@types/node": "^22.8.7",
+        "nodemon": "^3.1.7",
+        "tsx": "^4.19.2",
+        "typescript": "^5.6.3"
+      }
+    },
+    "node_modules/@emnapi/runtime": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz",
+      "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==",
+      "license": "MIT",
+      "optional": true,
+      "dependencies": {
+        "tslib": "^2.4.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz",
+      "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz",
+      "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz",
+      "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz",
+      "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz",
+      "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz",
+      "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz",
+      "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz",
+      "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==",
+      "cpu": [
+        "arm"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz",
+      "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz",
+      "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz",
+      "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==",
+      "cpu": [
+        "loong64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz",
+      "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==",
+      "cpu": [
+        "mips64el"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz",
+      "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==",
+      "cpu": [
+        "ppc64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz",
+      "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz",
+      "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==",
+      "cpu": [
+        "s390x"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz",
+      "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz",
+      "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz",
+      "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz",
+      "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz",
+      "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz",
+      "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz",
+      "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==",
+      "cpu": [
+        "x64"
+      ],
+      "dev": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@img/sharp-darwin-arm64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
+      "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-darwin-arm64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-darwin-x64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
+      "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-darwin-x64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-libvips-darwin-arm64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
+      "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-darwin-x64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
+      "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linux-arm": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
+      "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linux-arm64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
+      "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linux-s390x": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
+      "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
+      "cpu": [
+        "s390x"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linux-x64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
+      "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
+      "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+      "version": "1.0.4",
+      "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
+      "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-linux-arm": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
+      "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
+      "cpu": [
+        "arm"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linux-arm": "1.0.5"
+      }
+    },
+    "node_modules/@img/sharp-linux-arm64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
+      "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linux-arm64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-linux-s390x": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
+      "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
+      "cpu": [
+        "s390x"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linux-s390x": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-linux-x64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
+      "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linux-x64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-linuxmusl-arm64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
+      "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
+      "cpu": [
+        "arm64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-linuxmusl-x64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
+      "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "Apache-2.0",
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
+      }
+    },
+    "node_modules/@img/sharp-wasm32": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
+      "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
+      "cpu": [
+        "wasm32"
+      ],
+      "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+      "optional": true,
+      "dependencies": {
+        "@emnapi/runtime": "^1.2.0"
+      },
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-win32-ia32": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
+      "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "license": "Apache-2.0 AND LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@img/sharp-win32-x64": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
+      "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
+      "cpu": [
+        "x64"
+      ],
+      "license": "Apache-2.0 AND LGPL-3.0-or-later",
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      }
+    },
+    "node_modules/@types/better-sqlite3": {
+      "version": "7.6.11",
+      "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.11.tgz",
+      "integrity": "sha512-i8KcD3PgGtGBLl3+mMYA8PdKkButvPyARxA7IQAd6qeslht13qxb1zzO8dRCtE7U3IoJS782zDBAeoKiM695kg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/body-parser": {
+      "version": "1.19.5",
+      "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+      "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/connect": "*",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/connect": {
+      "version": "3.4.38",
+      "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+      "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/express": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
+      "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/body-parser": "*",
+        "@types/express-serve-static-core": "^5.0.0",
+        "@types/qs": "*",
+        "@types/serve-static": "*"
+      }
+    },
+    "node_modules/@types/express-serve-static-core": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.1.tgz",
+      "integrity": "sha512-CRICJIl0N5cXDONAdlTv5ShATZ4HEwk6kDDIW2/w9qOWKg+NU/5F8wYRWCrONad0/UKkloNSmmyN/wX4rtpbVA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "@types/qs": "*",
+        "@types/range-parser": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/@types/http-errors": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+      "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/mime": {
+      "version": "1.3.5",
+      "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+      "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/node": {
+      "version": "22.8.7",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.8.7.tgz",
+      "integrity": "sha512-LidcG+2UeYIWcMuMUpBKOnryBWG/rnmOHQR5apjn8myTQcx3rinFRn7DcIFhMnS0PPFSC6OafdIKEad0lj6U0Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~6.19.8"
+      }
+    },
+    "node_modules/@types/qs": {
+      "version": "6.9.16",
+      "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.16.tgz",
+      "integrity": "sha512-7i+zxXdPD0T4cKDuxCUXJ4wHcsJLwENa6Z3dCu8cfCK743OGy5Nu1RmAGqDPsoTDINVEcdXKRvR/zre+P2Ku1A==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/range-parser": {
+      "version": "1.2.7",
+      "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+      "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/@types/send": {
+      "version": "0.17.4",
+      "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+      "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/mime": "^1",
+        "@types/node": "*"
+      }
+    },
+    "node_modules/@types/serve-static": {
+      "version": "1.15.7",
+      "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+      "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@types/http-errors": "*",
+        "@types/node": "*",
+        "@types/send": "*"
+      }
+    },
+    "node_modules/accepts": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+      "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-types": "~2.1.34",
+        "negotiator": "0.6.3"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/anymatch": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+      "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "normalize-path": "^3.0.0",
+        "picomatch": "^2.0.4"
+      },
+      "engines": {
+        "node": ">= 8"
+      }
+    },
+    "node_modules/array-flatten": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+      "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==",
+      "license": "MIT"
+    },
+    "node_modules/balanced-match": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+      "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/base64-js": {
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+      "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/better-sqlite3": {
+      "version": "11.5.0",
+      "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.5.0.tgz",
+      "integrity": "sha512-e/6eggfOutzoK0JWiU36jsisdWoHOfN9iWiW/SieKvb7SAa6aGNmBM/UKyp+/wWSXpLlWNN8tCPwoDNPhzUvuQ==",
+      "hasInstallScript": true,
+      "license": "MIT",
+      "dependencies": {
+        "bindings": "^1.5.0",
+        "prebuild-install": "^7.1.1"
+      }
+    },
+    "node_modules/binary-extensions": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+      "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/bindings": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
+      "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "file-uri-to-path": "1.0.0"
+      }
+    },
+    "node_modules/bl": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
+      "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
+      "license": "MIT",
+      "dependencies": {
+        "buffer": "^5.5.0",
+        "inherits": "^2.0.4",
+        "readable-stream": "^3.4.0"
+      }
+    },
+    "node_modules/body-parser": {
+      "version": "1.20.3",
+      "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+      "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "content-type": "~1.0.5",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "on-finished": "2.4.1",
+        "qs": "6.13.0",
+        "raw-body": "2.5.2",
+        "type-is": "~1.6.18",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/brace-expansion": {
+      "version": "1.1.11",
+      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "balanced-match": "^1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "node_modules/braces": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+      "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "fill-range": "^7.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/buffer": {
+      "version": "5.7.1",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
+      "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "base64-js": "^1.3.1",
+        "ieee754": "^1.1.13"
+      }
+    },
+    "node_modules/bytes": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+      "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/call-bind": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+      "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "set-function-length": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/chokidar": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+      "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "anymatch": "~3.1.2",
+        "braces": "~3.0.2",
+        "glob-parent": "~5.1.2",
+        "is-binary-path": "~2.1.0",
+        "is-glob": "~4.0.1",
+        "normalize-path": "~3.0.0",
+        "readdirp": "~3.6.0"
+      },
+      "engines": {
+        "node": ">= 8.10.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/chownr": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
+      "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
+      "license": "ISC"
+    },
+    "node_modules/color": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
+      "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
+      "license": "MIT",
+      "dependencies": {
+        "color-convert": "^2.0.1",
+        "color-string": "^1.9.0"
+      },
+      "engines": {
+        "node": ">=12.5.0"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "license": "MIT"
+    },
+    "node_modules/color-string": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
+      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
+      "license": "MIT",
+      "dependencies": {
+        "color-name": "^1.0.0",
+        "simple-swizzle": "^0.2.2"
+      }
+    },
+    "node_modules/concat-map": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+      "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/content-disposition": {
+      "version": "0.5.4",
+      "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+      "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "5.2.1"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/content-type": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+      "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+      "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/cookie-signature": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==",
+      "license": "MIT"
+    },
+    "node_modules/debug": {
+      "version": "2.6.9",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "2.0.0"
+      }
+    },
+    "node_modules/decompress-response": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+      "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+      "license": "MIT",
+      "dependencies": {
+        "mimic-response": "^3.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/deep-extend": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
+      "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0.0"
+      }
+    },
+    "node_modules/define-data-property": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+      "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/depd": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+      "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/destroy": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+      "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8",
+        "npm": "1.2.8000 || >= 1.4.16"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ee-first": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+      "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
+      "license": "MIT"
+    },
+    "node_modules/encodeurl": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+      "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/end-of-stream": {
+      "version": "1.4.4",
+      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+      "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+      "license": "MIT",
+      "dependencies": {
+        "once": "^1.4.0"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+      "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.2.4"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.23.1",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
+      "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.23.1",
+        "@esbuild/android-arm": "0.23.1",
+        "@esbuild/android-arm64": "0.23.1",
+        "@esbuild/android-x64": "0.23.1",
+        "@esbuild/darwin-arm64": "0.23.1",
+        "@esbuild/darwin-x64": "0.23.1",
+        "@esbuild/freebsd-arm64": "0.23.1",
+        "@esbuild/freebsd-x64": "0.23.1",
+        "@esbuild/linux-arm": "0.23.1",
+        "@esbuild/linux-arm64": "0.23.1",
+        "@esbuild/linux-ia32": "0.23.1",
+        "@esbuild/linux-loong64": "0.23.1",
+        "@esbuild/linux-mips64el": "0.23.1",
+        "@esbuild/linux-ppc64": "0.23.1",
+        "@esbuild/linux-riscv64": "0.23.1",
+        "@esbuild/linux-s390x": "0.23.1",
+        "@esbuild/linux-x64": "0.23.1",
+        "@esbuild/netbsd-x64": "0.23.1",
+        "@esbuild/openbsd-arm64": "0.23.1",
+        "@esbuild/openbsd-x64": "0.23.1",
+        "@esbuild/sunos-x64": "0.23.1",
+        "@esbuild/win32-arm64": "0.23.1",
+        "@esbuild/win32-ia32": "0.23.1",
+        "@esbuild/win32-x64": "0.23.1"
+      }
+    },
+    "node_modules/escape-html": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+      "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
+      "license": "MIT"
+    },
+    "node_modules/etag": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+      "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/expand-template": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
+      "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
+      "license": "(MIT OR WTFPL)",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/express": {
+      "version": "4.21.1",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
+      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+      "license": "MIT",
+      "dependencies": {
+        "accepts": "~1.3.8",
+        "array-flatten": "1.1.1",
+        "body-parser": "1.20.3",
+        "content-disposition": "0.5.4",
+        "content-type": "~1.0.4",
+        "cookie": "0.7.1",
+        "cookie-signature": "1.0.6",
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "finalhandler": "1.3.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "merge-descriptors": "1.0.3",
+        "methods": "~1.1.2",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "path-to-regexp": "0.1.10",
+        "proxy-addr": "~2.0.7",
+        "qs": "6.13.0",
+        "range-parser": "~1.2.1",
+        "safe-buffer": "5.2.1",
+        "send": "0.19.0",
+        "serve-static": "1.16.2",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "type-is": "~1.6.18",
+        "utils-merge": "1.0.1",
+        "vary": "~1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.10.0"
+      }
+    },
+    "node_modules/file-uri-to-path": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
+      "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
+      "license": "MIT"
+    },
+    "node_modules/fill-range": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+      "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "to-regex-range": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/finalhandler": {
+      "version": "1.3.1",
+      "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+      "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "on-finished": "2.4.1",
+        "parseurl": "~1.3.3",
+        "statuses": "2.0.1",
+        "unpipe": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/forwarded": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+      "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fresh": {
+      "version": "0.5.2",
+      "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+      "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/fs-constants": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
+      "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
+      "license": "MIT"
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "dev": true,
+      "hasInstallScript": true,
+      "license": "MIT",
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.2.4",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+      "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "has-proto": "^1.0.1",
+        "has-symbols": "^1.0.3",
+        "hasown": "^2.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-tsconfig": {
+      "version": "4.8.1",
+      "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.8.1.tgz",
+      "integrity": "sha512-k9PN+cFBmaLWtVz29SkUoqU5O0slLuHJXt/2P+tMVFT+phsSGXGkp9t3rQIqdz0e+06EHNGs3oM6ZX1s2zHxRg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "resolve-pkg-maps": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+      }
+    },
+    "node_modules/github-from-package": {
+      "version": "0.0.0",
+      "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
+      "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
+      "license": "MIT"
+    },
+    "node_modules/glob-parent": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+      "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "is-glob": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+      "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+      "license": "MIT",
+      "dependencies": {
+        "get-intrinsic": "^1.1.3"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-flag": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+      "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/has-property-descriptors": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+      "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+      "license": "MIT",
+      "dependencies": {
+        "es-define-property": "^1.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-proto": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+      "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+      "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-errors": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+      "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+      "license": "MIT",
+      "dependencies": {
+        "depd": "2.0.0",
+        "inherits": "2.0.4",
+        "setprototypeof": "1.2.0",
+        "statuses": "2.0.1",
+        "toidentifier": "1.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "license": "MIT",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/ieee754": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+      "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "BSD-3-Clause"
+    },
+    "node_modules/ignore-by-default": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
+      "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
+      "dev": true,
+      "license": "ISC"
+    },
+    "node_modules/inherits": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "license": "ISC"
+    },
+    "node_modules/ini": {
+      "version": "1.3.8",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+      "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+      "license": "ISC"
+    },
+    "node_modules/ipaddr.js": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+      "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/is-arrayish": {
+      "version": "0.3.2",
+      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+      "license": "MIT"
+    },
+    "node_modules/is-binary-path": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+      "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "binary-extensions": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/is-extglob": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+      "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-glob": {
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+      "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-extglob": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/is-number": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+      "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.12.0"
+      }
+    },
+    "node_modules/media-typer": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+      "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/merge-descriptors": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+      "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/methods": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+      "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime": {
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+      "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+      "license": "MIT",
+      "bin": {
+        "mime": "cli.js"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mimic-response": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+      "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/minimatch": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "brace-expansion": "^1.1.7"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/minimist": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+      "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/mkdirp-classic": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
+      "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
+      "license": "MIT"
+    },
+    "node_modules/ms": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+      "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
+      "license": "MIT"
+    },
+    "node_modules/napi-build-utils": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
+      "integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==",
+      "license": "MIT"
+    },
+    "node_modules/negotiator": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+      "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/node-abi": {
+      "version": "3.71.0",
+      "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.71.0.tgz",
+      "integrity": "sha512-SZ40vRiy/+wRTf21hxkkEjPJZpARzUMVcJoQse2EF8qkUWbbO2z7vd5oA/H6bVH6SZQ5STGcu0KRDS7biNRfxw==",
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.3.5"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/nodemon": {
+      "version": "3.1.7",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.7.tgz",
+      "integrity": "sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "chokidar": "^3.5.2",
+        "debug": "^4",
+        "ignore-by-default": "^1.0.1",
+        "minimatch": "^3.1.2",
+        "pstree.remy": "^1.1.8",
+        "semver": "^7.5.3",
+        "simple-update-notifier": "^2.0.0",
+        "supports-color": "^5.5.0",
+        "touch": "^3.1.0",
+        "undefsafe": "^2.0.5"
+      },
+      "bin": {
+        "nodemon": "bin/nodemon.js"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/nodemon"
+      }
+    },
+    "node_modules/nodemon/node_modules/debug": {
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.1.3"
+      },
+      "engines": {
+        "node": ">=6.0"
+      },
+      "peerDependenciesMeta": {
+        "supports-color": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/nodemon/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/normalize-path": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+      "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/object-inspect": {
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/on-finished": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+      "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+      "license": "MIT",
+      "dependencies": {
+        "ee-first": "1.1.1"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+      "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+      "license": "ISC",
+      "dependencies": {
+        "wrappy": "1"
+      }
+    },
+    "node_modules/parseurl": {
+      "version": "1.3.3",
+      "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+      "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/path-to-regexp": {
+      "version": "0.1.10",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
+      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==",
+      "license": "MIT"
+    },
+    "node_modules/picomatch": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+      "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=8.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/prebuild-install": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.2.tgz",
+      "integrity": "sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==",
+      "license": "MIT",
+      "dependencies": {
+        "detect-libc": "^2.0.0",
+        "expand-template": "^2.0.3",
+        "github-from-package": "0.0.0",
+        "minimist": "^1.2.3",
+        "mkdirp-classic": "^0.5.3",
+        "napi-build-utils": "^1.0.1",
+        "node-abi": "^3.3.0",
+        "pump": "^3.0.0",
+        "rc": "^1.2.7",
+        "simple-get": "^4.0.0",
+        "tar-fs": "^2.0.0",
+        "tunnel-agent": "^0.6.0"
+      },
+      "bin": {
+        "prebuild-install": "bin.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/proxy-addr": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+      "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+      "license": "MIT",
+      "dependencies": {
+        "forwarded": "0.2.0",
+        "ipaddr.js": "1.9.1"
+      },
+      "engines": {
+        "node": ">= 0.10"
+      }
+    },
+    "node_modules/pstree.remy": {
+      "version": "1.1.8",
+      "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
+      "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/pump": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+      "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+      "license": "MIT",
+      "dependencies": {
+        "end-of-stream": "^1.1.0",
+        "once": "^1.3.1"
+      }
+    },
+    "node_modules/qs": {
+      "version": "6.13.0",
+      "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+      "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+      "license": "BSD-3-Clause",
+      "dependencies": {
+        "side-channel": "^1.0.6"
+      },
+      "engines": {
+        "node": ">=0.6"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/range-parser": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+      "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/raw-body": {
+      "version": "2.5.2",
+      "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+      "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+      "license": "MIT",
+      "dependencies": {
+        "bytes": "3.1.2",
+        "http-errors": "2.0.0",
+        "iconv-lite": "0.4.24",
+        "unpipe": "1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/rc": {
+      "version": "1.2.8",
+      "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
+      "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
+      "license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
+      "dependencies": {
+        "deep-extend": "^0.6.0",
+        "ini": "~1.3.0",
+        "minimist": "^1.2.0",
+        "strip-json-comments": "~2.0.1"
+      },
+      "bin": {
+        "rc": "cli.js"
+      }
+    },
+    "node_modules/readable-stream": {
+      "version": "3.6.2",
+      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+      "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+      "license": "MIT",
+      "dependencies": {
+        "inherits": "^2.0.3",
+        "string_decoder": "^1.1.1",
+        "util-deprecate": "^1.0.1"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/readdirp": {
+      "version": "3.6.0",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+      "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "picomatch": "^2.2.1"
+      },
+      "engines": {
+        "node": ">=8.10.0"
+      }
+    },
+    "node_modules/resolve-pkg-maps": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+      "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+      "dev": true,
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/safer-buffer": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+      "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+      "license": "MIT"
+    },
+    "node_modules/semver": {
+      "version": "7.6.3",
+      "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+      "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+      "license": "ISC",
+      "bin": {
+        "semver": "bin/semver.js"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/send": {
+      "version": "0.19.0",
+      "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+      "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+      "license": "MIT",
+      "dependencies": {
+        "debug": "2.6.9",
+        "depd": "2.0.0",
+        "destroy": "1.2.0",
+        "encodeurl": "~1.0.2",
+        "escape-html": "~1.0.3",
+        "etag": "~1.8.1",
+        "fresh": "0.5.2",
+        "http-errors": "2.0.0",
+        "mime": "1.6.0",
+        "ms": "2.1.3",
+        "on-finished": "2.4.1",
+        "range-parser": "~1.2.1",
+        "statuses": "2.0.1"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/send/node_modules/encodeurl": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+      "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/send/node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/serve-static": {
+      "version": "1.16.2",
+      "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+      "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+      "license": "MIT",
+      "dependencies": {
+        "encodeurl": "~2.0.0",
+        "escape-html": "~1.0.3",
+        "parseurl": "~1.3.3",
+        "send": "0.19.0"
+      },
+      "engines": {
+        "node": ">= 0.8.0"
+      }
+    },
+    "node_modules/set-function-length": {
+      "version": "1.2.2",
+      "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+      "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+      "license": "MIT",
+      "dependencies": {
+        "define-data-property": "^1.1.4",
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2",
+        "get-intrinsic": "^1.2.4",
+        "gopd": "^1.0.1",
+        "has-property-descriptors": "^1.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/setprototypeof": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+      "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
+      "license": "ISC"
+    },
+    "node_modules/sharp": {
+      "version": "0.33.5",
+      "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
+      "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
+      "hasInstallScript": true,
+      "license": "Apache-2.0",
+      "dependencies": {
+        "color": "^4.2.3",
+        "detect-libc": "^2.0.3",
+        "semver": "^7.6.3"
+      },
+      "engines": {
+        "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/libvips"
+      },
+      "optionalDependencies": {
+        "@img/sharp-darwin-arm64": "0.33.5",
+        "@img/sharp-darwin-x64": "0.33.5",
+        "@img/sharp-libvips-darwin-arm64": "1.0.4",
+        "@img/sharp-libvips-darwin-x64": "1.0.4",
+        "@img/sharp-libvips-linux-arm": "1.0.5",
+        "@img/sharp-libvips-linux-arm64": "1.0.4",
+        "@img/sharp-libvips-linux-s390x": "1.0.4",
+        "@img/sharp-libvips-linux-x64": "1.0.4",
+        "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
+        "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
+        "@img/sharp-linux-arm": "0.33.5",
+        "@img/sharp-linux-arm64": "0.33.5",
+        "@img/sharp-linux-s390x": "0.33.5",
+        "@img/sharp-linux-x64": "0.33.5",
+        "@img/sharp-linuxmusl-arm64": "0.33.5",
+        "@img/sharp-linuxmusl-x64": "0.33.5",
+        "@img/sharp-wasm32": "0.33.5",
+        "@img/sharp-win32-ia32": "0.33.5",
+        "@img/sharp-win32-x64": "0.33.5"
+      }
+    },
+    "node_modules/side-channel": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+      "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind": "^1.0.7",
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.4",
+        "object-inspect": "^1.13.1"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/simple-concat": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
+      "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT"
+    },
+    "node_modules/simple-get": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
+      "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ],
+      "license": "MIT",
+      "dependencies": {
+        "decompress-response": "^6.0.0",
+        "once": "^1.3.1",
+        "simple-concat": "^1.0.0"
+      }
+    },
+    "node_modules/simple-swizzle": {
+      "version": "0.2.2",
+      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-arrayish": "^0.3.1"
+      }
+    },
+    "node_modules/simple-update-notifier": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
+      "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "semver": "^7.5.3"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/statuses": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+      "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/string_decoder": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+      "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+      "license": "MIT",
+      "dependencies": {
+        "safe-buffer": "~5.2.0"
+      }
+    },
+    "node_modules/strip-json-comments": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+      "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/supports-color": {
+      "version": "5.5.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+      "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "has-flag": "^3.0.0"
+      },
+      "engines": {
+        "node": ">=4"
+      }
+    },
+    "node_modules/tar-fs": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
+      "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
+      "license": "MIT",
+      "dependencies": {
+        "chownr": "^1.1.1",
+        "mkdirp-classic": "^0.5.2",
+        "pump": "^3.0.0",
+        "tar-stream": "^2.1.4"
+      }
+    },
+    "node_modules/tar-stream": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
+      "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
+      "license": "MIT",
+      "dependencies": {
+        "bl": "^4.0.3",
+        "end-of-stream": "^1.4.1",
+        "fs-constants": "^1.0.0",
+        "inherits": "^2.0.3",
+        "readable-stream": "^3.1.1"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/to-regex-range": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+      "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "is-number": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=8.0"
+      }
+    },
+    "node_modules/toidentifier": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+      "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.6"
+      }
+    },
+    "node_modules/touch": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
+      "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
+      "dev": true,
+      "license": "ISC",
+      "bin": {
+        "nodetouch": "bin/nodetouch.js"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+      "license": "0BSD",
+      "optional": true
+    },
+    "node_modules/tsx": {
+      "version": "4.19.2",
+      "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.2.tgz",
+      "integrity": "sha512-pOUl6Vo2LUq/bSa8S5q7b91cgNSjctn9ugq/+Mvow99qW6x/UZYwzxy/3NmqoT66eHYfCVvFvACC58UBPFf28g==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "~0.23.0",
+        "get-tsconfig": "^4.7.5"
+      },
+      "bin": {
+        "tsx": "dist/cli.mjs"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      }
+    },
+    "node_modules/tunnel-agent": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+      "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "safe-buffer": "^5.0.1"
+      },
+      "engines": {
+        "node": "*"
+      }
+    },
+    "node_modules/type-is": {
+      "version": "1.6.18",
+      "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+      "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+      "license": "MIT",
+      "dependencies": {
+        "media-typer": "0.3.0",
+        "mime-types": "~2.1.24"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/typescript": {
+      "version": "5.6.3",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+      "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+      "dev": true,
+      "license": "Apache-2.0",
+      "bin": {
+        "tsc": "bin/tsc",
+        "tsserver": "bin/tsserver"
+      },
+      "engines": {
+        "node": ">=14.17"
+      }
+    },
+    "node_modules/undefsafe": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
+      "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/undici-types": {
+      "version": "6.19.8",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+      "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+      "dev": true,
+      "license": "MIT"
+    },
+    "node_modules/unpipe": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+      "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/util-deprecate": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+      "license": "MIT"
+    },
+    "node_modules/utils-merge": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+      "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4.0"
+      }
+    },
+    "node_modules/vary": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+      "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+      "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+      "license": "ISC"
+    }
+  }
+}

+ 27 - 0
hit-counter/node/package.json

@@ -0,0 +1,27 @@
+{
+  "name": "hit_counter",
+  "version": "0.0.2",
+  "type": "module",
+  "description": "A simple hit counter",
+  "main": "dist/index.js",
+  "dependencies": {
+    "better-sqlite3": "^11.5.0",
+    "express": "^4.21.1",
+    "sharp": "^0.33.5"
+  },
+  "scripts": {
+    "build": "npx tsc",
+    "start": "npm run build && node dist/index.js",
+    "dev": "npx nodemon"
+  },
+  "author": "zeyus",
+  "license": "MIT",
+  "devDependencies": {
+    "@types/better-sqlite3": "^7.6.11",
+    "@types/express": "^5.0.0",
+    "@types/node": "^22.8.7",
+    "nodemon": "^3.1.7",
+    "tsx": "^4.19.2",
+    "typescript": "^5.6.3"
+  }
+}

+ 461 - 0
hit-counter/node/src/hit-counter.ts

@@ -0,0 +1,461 @@
+import Database, { Database as DB, Statement } from 'better-sqlite3';
+import sharp, { FormatEnum, Metadata, Sharp } from 'sharp';
+import * as fs from 'fs';
+import * as https from 'https';
+
+// Define the image format map
+const imageFormatMap: Record<string, keyof FormatEnum> = {
+    jpg: 'jpeg',
+    jpeg: 'jpeg',
+    png: 'png',
+    webp: 'webp',
+    tiff: 'tiff',
+    gif: 'gif'
+};
+
+// Define the MIME type map
+const mimeTypeMap: Record<string, string> = {
+    jpg: 'image/jpeg',
+    jpeg: 'image/jpeg',
+    png: 'image/png',
+    webp: 'image/webp',
+    tiff: 'image/tiff',
+    gif: 'image/gif'
+};
+
+// Database result for hits
+interface HitResult {
+    hits: number;
+}
+
+// RGB color type
+interface RGBColor {
+    r: number;
+    g: number;
+    b: number;
+}
+
+// Allows use of base class and subclass as a valid type
+export interface Type<T> extends Function { new (...args: any[]): T; }
+
+// Hit counter renderer interface
+interface HitCounterRenderer {
+    render(): Promise<any>;
+}
+
+// Hit counter configuration
+interface HitCounterConfig {
+    siteId: number;
+    // path to the database file
+    counterDB: string;
+    // Background image URL for the counter display
+    backgroundImageUrl: string;
+    // local image path
+    imageFile: string;
+    backgroundCacheFile: string;
+    // Text settings to be displayed on the image
+    customText: string; // Main headline text
+    secondaryText: string; // Secondary descriptive text
+    textPositionX: number; // X position of the text
+    textPositionY: number; // Y position of the text
+    numberPositionX: number; // X position of the number
+    numberPositionY: number; // Y position of the number
+    fontSize: number; // Font size
+    fontFace: string; // Font face
+    borderWidth: number; // Border width
+    textColorRGB: RGBColor; // Text color
+    secondaryTextColorRGB: RGBColor; // Secondary text color
+    numberColorRGB: RGBColor; // Number color
+    frameColorRGB: RGBColor; // Frame color
+    drawFrame: boolean; // Whether to draw a frame around the counter
+}
+
+// Default configuration for the hit counter
+const defaultHitCounterConfig: HitCounterConfig = {
+    siteId: 1,
+    counterDB: './hits.db',
+    backgroundImageUrl: 'https://datakra.sh/assets/example_counter.jpg',
+    imageFile: './example_counter.png',
+    backgroundCacheFile: './example_counter_bg.png',
+    customText: 'Hit Counter!',
+    secondaryText: 'Super cyber, super cool!',
+    textPositionX: 5,
+    textPositionY: 11,
+    numberPositionX: 170,
+    numberPositionY: 18,
+    fontSize: 10,
+    fontFace: 'fixed',
+    borderWidth: 3,
+    textColorRGB: { r: 253, g: 252, b: 1 },
+    secondaryTextColorRGB: { r: 0, g: 255, b: 0 },
+    numberColorRGB: { r: 255, g: 255, b: 255 },
+    frameColorRGB: { r: 255, g: 0, b: 0 },
+    drawFrame: true,
+};
+
+
+/**
+ * Extract the file extension from a file path.
+ * 
+ * @param {String} file
+ * 
+ * @returns {String}
+ * @throws {Error} if the file extension is invalid
+ */
+function fileExt(file: string): string {
+    const ext = file.split('.').pop();
+    if (!ext) throw new Error('Invalid file extension');
+    return ext.toLowerCase();
+}
+
+/**
+ * Convert a file extension to a Sharp image format.
+ * 
+ * @param {String} ext
+ * 
+ * @returns {String}
+ * @throws {Error} if the file extension is not supported
+ */
+function extToFormat(ext: string): keyof FormatEnum {
+    const format = imageFormatMap[ext];
+    if (!format) throw new Error('Unsupported file extension');
+    return format;
+}
+
+/**
+ * Check if a path is a web URI.
+ * 
+ * @param {String|URL} path
+ * The path to check.
+ * 
+ * @returns {Boolean}
+ * Whether the path is a web URI.
+ */
+function isWebURI(path: string | URL): boolean {
+    try {
+        const url = new URL(path.toString());
+        return url.protocol === 'http:' || url.protocol === 'https:';
+    } catch {
+        return false;
+    }
+}
+
+/**
+ * Base class for hit counter renderers.
+ * 
+ * @class BaseHitCounterRenderer
+ * @implements {HitCounterRenderer}
+ */
+class BaseHitCounterRenderer implements HitCounterRenderer {
+    protected counter: HitCounter;
+    protected config: HitCounterConfig;
+
+    /**
+     * Creates an instance of BaseHitCounterRenderer.
+     * 
+     * @param {HitCounter} counter
+     *  The hit counter instance.
+     * @param {HitCounterConfig} config
+     *  The hit counter configuration.
+     */
+    constructor(counter: HitCounter, config: HitCounterConfig) {
+        this.counter = counter;
+        this.config = config;
+    }
+
+    /**
+     * Render the hit counter.
+     */
+    async render(): Promise<any> {
+        throw new Error('Render not implemented');
+    }
+}
+
+/**
+ * Image hit counter renderer.
+ * 
+ * @class ImageHitCounterRenderer
+ * @extends {BaseHitCounterRenderer}
+ */
+class ImageHitCounterRenderer extends BaseHitCounterRenderer {
+    private imageFormat: keyof FormatEnum;
+    private baseImage: Buffer;
+    private baseImageMetadata: Metadata;
+
+    /**
+     * Creates an instance of ImageHitCounterRenderer.
+     * 
+     * @param {HitCounter} counter
+     * The hit counter instance.
+     * @param {HitCounterConfig} config
+     * The hit counter configuration.
+     */
+    constructor(counter: HitCounter, config: HitCounterConfig) {
+        super(counter, config);
+        this.imageFormat = extToFormat(fileExt(this.config.imageFile));
+    }
+
+    /**
+     * Download the background image.
+     *
+     * @returns {Promise<Buffer>}
+     * The downloaded image.
+     */
+    private async downloadImage(): Promise<Buffer> {
+        return new Promise((resolve, reject) => {
+            let buf = Buffer.alloc(0);
+            https.get(this.config.backgroundImageUrl, (response) => {
+                response.on('data', (chunk) => (buf = Buffer.concat([buf, chunk])));
+                response.on('end', () => resolve(buf));
+            }).on('error', reject);
+        });
+    }
+
+    /**
+     * Load or cache the background image.
+     *
+     * @param {Boolean} [overwrite=false]
+     * Whether to overwrite the cached background image.
+     *
+     * @returns {Promise<Sharp>}
+     * The loaded or cached background image.
+     */
+    private async loadOrCacheBG(overwrite = false): Promise<Sharp> {
+        if (fs.existsSync(this.config.backgroundCacheFile) && !overwrite) {
+            return sharp(this.config.backgroundCacheFile);
+        }
+
+        const imageBuffer = isWebURI(this.config.backgroundImageUrl)
+            ? await this.downloadImage()
+            : fs.readFileSync(this.config.backgroundImageUrl);
+
+        const image = sharp(imageBuffer).toFormat(this.imageFormat);
+        await image.toFile(this.config.backgroundCacheFile);
+        return image;
+    }
+
+    /**
+     * Get the base image.
+     *
+     * @returns {Promise<Buffer>}
+     * The base image.
+     */
+    private async getBaseImage(): Promise<Buffer> {
+        if (typeof this.baseImage !== 'undefined') {
+            return this.baseImage;
+        }
+        const bgImage = await this.loadOrCacheBG();
+        const bgMetadata = await bgImage.metadata();
+
+        const svgOverlay = this.createSvgOverlay(bgMetadata.width!, bgMetadata.height!);
+        const compositeImage = bgImage.composite([{ input: svgOverlay, top: 0, left: 0, density: bgMetadata.density! }]).withMetadata();
+        this.baseImage = await compositeImage.toBuffer();
+        this.baseImageMetadata = await compositeImage.metadata();
+
+        return this.baseImage;
+    }
+
+    /**
+     * Create an SVG overlay.
+     *
+     * @param {Number} width
+     * The width of the overlay.
+     * @param {Number} height
+     * The height of the overlay.
+     *
+     * @returns {Buffer}
+     * The SVG overlay.
+     */
+    private createSvgOverlay(width: number, height: number): Buffer {
+        const { textPositionX, textPositionY, fontFace, fontSize, customText, secondaryText, textColorRGB, secondaryTextColorRGB, frameColorRGB, drawFrame, borderWidth } = this.config;
+        const frame = drawFrame
+            ? `<rect x="0" y="0" width="100%" height="100%" fill="none" stroke="rgb(${frameColorRGB.r},${frameColorRGB.g},${frameColorRGB.b})" stroke-width="${borderWidth}" />`
+            : '';
+
+        const svg = `
+            <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
+                <text y="${textPositionY}" font-family="${fontFace}" font-size="${fontSize}">
+                    <tspan x="${textPositionX}" dy="0" fill="rgb(${textColorRGB.r},${textColorRGB.g},${textColorRGB.b})">${customText}</tspan>
+                    <tspan x="${textPositionX}" dy="1.2em" fill="rgb(${secondaryTextColorRGB.r},${secondaryTextColorRGB.g},${secondaryTextColorRGB.b})">${secondaryText}</tspan>
+                </text>
+                ${frame}
+            </svg>`;
+        return Buffer.from(svg);
+    }
+
+    /**
+     * Create an SVG number overlay.
+     *
+     * @param {Number} hits
+     * The number of hits.
+     * @param {Number} width
+     * The width of the image.
+     * @param {Number} height
+     * The height of the image.
+     *
+     * @returns {Buffer}
+     * The SVG number overlay.
+     */
+    private createSVGNumberOverlay(hits: number, width: number, height: number): Buffer {
+        const { numberPositionX, numberPositionY, fontFace, fontSize, numberColorRGB } = this.config;
+        const svg = `
+            <svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}">
+                <text x="${numberPositionX}" y="${numberPositionY}" text-anchor="end" font-family="${fontFace}" font-size="${fontSize}" fill="rgb(${numberColorRGB.r},${numberColorRGB.g},${numberColorRGB.b})">${hits}</text>
+            </svg>`;
+        return Buffer.from(svg);
+    }
+
+    /**
+     * Render the hit counter.
+     *
+     * @returns {Promise<Buffer>}
+     * The rendered hit counter.
+     */
+    async render(): Promise<Buffer> {
+        const hits = await this.counter.getHits();
+        return await sharp(await this.getBaseImage())
+            .composite([
+                {
+                    input: this.createSVGNumberOverlay(hits, this.baseImageMetadata.width!, this.baseImageMetadata.height!),
+                    top: 0,
+                    left: 0
+                }
+            ])
+            .toFormat(this.imageFormat)
+            .toBuffer();
+    }
+}
+
+/**
+ * Hit counter class.
+ * 
+ * @class HitCounter
+ */
+class HitCounter {
+    private db: DB;
+    private renderer: BaseHitCounterRenderer;
+    private preparedStatements: Record<string, Statement>;
+    private mimeType: string;
+    private config: HitCounterConfig;
+
+    /**
+     * Creates an instance of HitCounter.
+     * 
+     * @param {HitCounterConfig} [config=defaultHitCounterConfig]
+     * The hit counter configuration. Defaults to defaultHitCounterConfig.
+     * @param {Type<BaseHitCounterRenderer>} renderer
+     * The hit counter renderer. Defaults to ImageHitCounterRenderer.
+     */
+    constructor(config: HitCounterConfig = defaultHitCounterConfig, renderer: Type<BaseHitCounterRenderer> = ImageHitCounterRenderer) {
+        config = { ...defaultHitCounterConfig, ...config };
+        this.config = config;
+        this.renderer = new renderer(this, config);
+        this.mimeType = mimeTypeMap[fileExt(config.imageFile)];
+        this.db = this.initDB(config.counterDB);
+        this.preparedStatements = this.prepareStatements();
+    }
+
+    /**
+     * Initialize the database connection.
+     * 
+     * @param {String} filePath
+     * The path to the database file.
+     * 
+     * @returns {DB}
+     * The database instance.
+     */
+    private initDB(filePath: string): DB {
+        const db = new Database(filePath, {fileMustExist: false});
+        db.pragma('locking_mode = EXCLUSIVE');
+        db.pragma('journal_mode = WAL');
+        db.pragma('synchronous = OFF');
+        return db;
+    }
+
+    /**
+     * Prepare the SQL statements.
+     * 
+     * @returns {Record<string, Statement>}
+     * The prepared statements.
+     */
+    private prepareStatements(): Record<string, Statement> {
+        this.db.exec(`
+            CREATE TABLE IF NOT EXISTS hit_counter (
+                id INTEGER PRIMARY KEY,
+                hits INTEGER NOT NULL DEFAULT 0
+            );
+        `);
+
+        this.db.exec(`
+            INSERT OR IGNORE INTO hit_counter (id, hits)
+            VALUES (${this.config.siteId}, 0)
+        `);
+
+        return {
+            get: this.db.prepare('SELECT hits FROM hit_counter WHERE id = ?'),
+            increment: this.db.prepare('UPDATE hit_counter SET hits = hits + 1 WHERE id = ?'),
+            reset: this.db.prepare('UPDATE hit_counter SET hits = 0 WHERE id = ?'),
+        };
+    }
+
+    /**
+     * Get the number of hits.
+     * 
+     * @returns {Promise<Number>}
+     * The number of hits
+     */
+    async getHits(): Promise<number> {
+        const result = this.preparedStatements.get.get(this.config.siteId) as HitResult;
+        return result.hits;
+    }
+
+    /**
+     * Increment the hit counter.
+     */
+    async increment(): Promise<void> {
+        this.preparedStatements.increment.run(this.config.siteId);
+    }
+
+    /**
+     * Reset the hit counter
+     */
+    async reset(): Promise<void> {
+        this.preparedStatements.reset.run(this.config.siteId);
+    }
+
+    /**
+     * Render the hit counter.
+     * 
+     * @returns {Promise<any>}
+     * The rendered hit counter.
+     */
+    async render(): Promise<any> {
+        return this.renderer.render();
+    }
+
+    /**
+     * Get the MIME type of the hit counter image.
+     * 
+     * @returns {String}
+     * The MIME type.
+     */
+    getMimeType(): string {
+        return this.mimeType;
+    }
+
+    /**
+     * Destructor.
+     */
+    finalize(): void {
+        this.db.close();
+    }
+}
+
+export {
+    HitCounter,
+    HitCounterConfig,
+    HitCounterRenderer,
+    BaseHitCounterRenderer,
+    ImageHitCounterRenderer
+};
+export default HitCounter;

+ 33 - 0
hit-counter/node/src/index.ts

@@ -0,0 +1,33 @@
+import express, { Express, Request, Response } from "express";
+import { HitCounter } from "./hit-counter";
+
+const app: Express = express();
+const PORT: number = 8000;
+const hitCounter = new HitCounter();
+const imageMimeType = hitCounter.getMimeType();
+
+app.get("/", (req: Request, res: Response) => {
+    hitCounter.increment();
+    res.send("Hello dear interlocutor! Your visit has been noted.");
+  
+});
+
+app.get("/hits", async (req: Request, res: Response) => {
+    const hits = await hitCounter.getHits();
+    res.send(`This site has ${hits} hits!`);
+});
+
+app.get("/reset", async (req: Request, res: Response) => {
+    await hitCounter.reset();
+    res.send("Hit counter has been reset!");
+});
+
+app.get("/image", async (req: Request, res: Response) => {
+    const image = await hitCounter.render();
+    res.setHeader("Content-Type", imageMimeType);
+    res.send(image);
+});
+
+app.listen(PORT, () => {
+    console.log(`[server]: Express server started on port ${PORT}`);
+});

+ 92 - 0
hit-counter/script.php

@@ -0,0 +1,92 @@
+<?php
+// Define paths and filenames for the counter file and generated image output
+$counterFile = "counter.txt";
+$imageFile = "counter.png";
+
+// Background image URL for the counter display
+$backgroundImageUrl = "https://computer.glamour.ovh/hit-counter/bg.png"; // Replace this with your actual background image URL
+
+// Text settings to be displayed on the image
+$customText = "computer_glamour"; // Main headline text
+$secondaryText = "amount of hits:"; // Secondary descriptive text
+
+// Coordinates for positioning the text and number on the image
+$textPositionX = 5; // X-coordinate for the main text
+$textPositionY = 5; // Y-coordinate for the main text
+$secondaryTextPositionX = 5; // X-coordinate for the secondary text
+$secondaryTextPositionY = 20; // Y-coordinate for the secondary text
+$numberPositionX = 5; // X-coordinate for the counter number
+$numberPositionY = 40; // Y-coordinate for the counter number
+
+// RGB color definitions for text, secondary text, number, and optional frame
+$textColorRGB = ['red' => 255, 'green' => 255, 'blue' => 255]; // Color for the main text
+$secondaryTextColorRGB = ['red' => 255, 'green' => 255, 'blue' => 255]; // Color for the secondary text
+$numberColorRGB = ['red' => 255, 'green' => 255, 'blue' => 255]; // Color for the counter number
+$frameColorRGB = ['red' => 255, 'green' => 0, 'blue' => 255]; // Color for the frame
+$drawFrame = true; // Boolean to toggle drawing a frame around the image
+
+// Ensure the counter file exists, and initialize if not
+if (!file_exists($counterFile)) {
+    file_put_contents($counterFile, "0"); // Create the file with an initial value of 0 if it doesn't exist
+}
+
+// Open the file for reading and writing (c+ creates the file if it does not exist)
+$fp = fopen($counterFile, "c+");
+if (flock($fp, LOCK_EX)) { // Lock to synchronize file access
+    // Read and increment the counter
+    $number = (int)fread($fp, filesize($counterFile));
+    $number++; // Increment the counter by 1
+
+    // Prepare the file for writing the updated counter
+    ftruncate($fp, 0); // Clear file content
+    rewind($fp); // Reset the file pointer to the start of the file
+    fwrite($fp, (string)$number); // Write the new counter value to the file
+
+    // Release the lock on the file
+    flock($fp, LOCK_UN);
+}
+fclose($fp); // Close the file
+
+// Load the background image and determine its type (JPEG or PNG)
+list($width, $height, $type) = getimagesize($backgroundImageUrl); // Get image dimensions and type
+switch ($type) {
+    case IMAGETYPE_JPEG:
+        $backgroundImage = imagecreatefromjpeg($backgroundImageUrl); // Load JPEG image
+        break;
+    case IMAGETYPE_PNG:
+        $backgroundImage = imagecreatefrompng($backgroundImageUrl); // Load PNG image
+        break;
+    default:
+        die("Unsupported image format: " . $backgroundImageUrl); // Exit script if image format is not supported
+}
+
+// Allocate colors for the text and frame on the image
+$frameColor = imagecolorallocate($backgroundImage, $frameColorRGB['red'], $frameColorRGB['green'], $frameColorRGB['blue']);
+$textColor = imagecolorallocate($backgroundImage, $textColorRGB['red'], $textColorRGB['green'], $textColorRGB['blue']);
+$secondaryTextColor = imagecolorallocate($backgroundImage, $secondaryTextColorRGB['red'], $secondaryTextColorRGB['green'], $secondaryTextColorRGB['blue']);
+$numberColor = imagecolorallocate($backgroundImage, $numberColorRGB['red'], $numberColorRGB['green'], $numberColorRGB['blue']);
+
+// Optionally draw a rectangular frame around the entire image
+if ($drawFrame) {
+    imagerectangle($backgroundImage, 0, 0, $width - 1, $height - 1, $frameColor); // Draw the frame
+}
+
+// Add the main text, secondary text, and counter number to the image
+imagestring($backgroundImage, 7, $textPositionX, $textPositionY, $customText, $textColor); // Draw main text
+if (!empty($secondaryText)) {
+    imagestring($backgroundImage, 5, $secondaryTextPositionX, $secondaryTextPositionY, $secondaryText, $secondaryTextColor); // Draw secondary text if provided
+}
+imagestring($backgroundImage, 7, $numberPositionX, $numberPositionY, (string)$number, $numberColor); // Draw counter number
+
+// Output the final image as a PNG file
+imagepng($backgroundImage, $imageFile); // Save the image to a file
+imagedestroy($backgroundImage); // Free up memory used by the image
+
+// Set headers for the browser to interpret the output as an image
+header('Content-Type: image/png'); // MIME type for PNG image
+header('Content-Disposition: filename="' . $imageFile . '"'); // Suggested filename for download
+readfile($imageFile); // Output the image file
+
+// For more information, refer to the Git repository:
+// https://git.cyberwa.re/revengeday/hit-counter/src/branch/main
+?>

+ 324 - 0
index.php

@@ -0,0 +1,324 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>✨️computer_glamour✨️</title>
+
+    <link rel="stylesheet" href="./style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+
+<body>
+    <div class="container">
+        
+<img src="pc.png" alt="Profile Picture" class="profile-pic">
+
+        <div class="container bio">
+
+
+       <!-- <h1>✨️computer_glamour✨️</h1> -->
+<img src="./wordart_pink.png" class="small-pic">
+
+        <a href="./blog">Blog</a>
+        <a href="./xmpp.html">XMPP</a>
+        <a href="./pgp.html">PGP</a>
+        <a href="./about.html">About me</a>
+        <a href="./vault/">The Vault</a>
+        <a href="./pc.html">my PC</a>
+<hr>
+<marquee behavior="scroll" direction="left" scrollamount="5">
+    Do you know why system drive is called C:\ in windows ? It was called that because A and B were already used for floppy disks. ----&nbsp;
+    Isn't color pink the best color for making websites? ----&nbsp; 
+    You know who was the first recognized and catched cybercriminal? Check out Markus Hess, crazy story. ----&nbsp;
+    You ask what glamour is? It is the best style ever ----&nbsp;
+    Did you knew that computer hacking really started with a whistle found in breakfast cereal? ----&nbsp;
+    Put modem noise here ----&nbsp; 
+    Does anybody remember Susan Kare, designer of the first type font and icon set for first Mac Os? ----&nbsp; 
+    You are staring at this marquee for too long now I am getting worried ----&nbsp;
+    Did you knew there is a special system to describe procesurally generated fractals called L-System? ----&nbsp;
+    Yes I have written this website by hand, no it didn't hurt. X3 ----&nbsp;
+    Do you know that there is a programming language that consists of tabs and spaces ? It is Whitespace ----&nbsp;
+    Okay now you think you can wait long enough to get to the end of this marquee. ----&nbsp;    
+    Do you know that there is a programming language that can be programmed in paint ? It is called Piet ----&nbsp;
+    Programs that print their own source code are called: Quines ... don't even get me started on polyquines ----&nbsp;
+    There is musical programming language that can generate sound in real time while you code them, it is called Sonic Pi. ---- &nbsp; 
+    Congratulations to you ... you have beed the longest marguee starer on the internet.
+</marquee>
+<br>
+<hr>
+<p class="center">
+My current time is:
+<?php echo (new DateTime('now', new DateTimeZone('Europe/Warsaw')))->format('H:i:s'); ?>
+</p>
+    <img src="./bsod.gif" class="small-pic">
+
+<!--<img src="./pc.gif" class="small-pic">-->
+
+<p class="center">
+Follow me on fediverse: <a class="no-button" href="https://glamour.ovh/computer">@computer@glamour.ovh</a><br>
+Link to the instance: <a class="no-button" href="https://glamour.ovh">https://glamour.ovh</a>
+</p>
+
+</div>
+<div class="container bio">
+            <h2>About Me</h2>
+
+            <p>Hi, I am computer_glamour. I am a geeky computer stuff enthusiast. I also like making crappy websites like this one.</p>
+
+<p>I have most intrest in computer networks, and operating systems. I am a linux enthusiast. I love small internet websites that are focused on content rather than on looks. I like some programming, my favorite languages are Python and PHP. I also like video games (although i don't like most of new AAA stuff), you can check my favorite ones <a class="no-button" href="./blog/video_games.php">here</a>.</p>
+
+<p>I did university degree in cyber security. I can explain to you what RSA, PKI or SHA is, not that you would really want to know that hahahaha.</p>
+
+<p>I do silly things on internet :3</p>
+
+
+        </div>
+
+        <div class="bio">
+            <h2>Username history</h2>
+
+            <p>There is no huge background behind it. I watched a streamer once, and she made fun of glamour aesthetic. I joined her community and i wanted to make a nickname for myself that would relate to the community, I saw a lot of people with nicknames like something and glamour at the end. I thought okay I like computers and this community is making fun of the glamour aesthetic, so why not let's make the most glamorous computer on the internet!!!</p>
+
+        <p>I am not a huge fan of the true classic glamour aesthetic, but i think mixture of it together with computers and internet gives it this funny vaporwave like vibe which is the vibe that i like very much. On the other hand I do not like being put into categories so i call my own aesthetic computer glamour X3</p>
+        </div>
+
+        <div class="interests">
+            <h2>Things that i like:</h2>
+
+<ul>
+    <li>Video Games
+        <ul>
+            <li>Witcher 1, 2 and 3</li>
+            <li>Cyberpunk 2077</li>
+            <li>Baldurs Gate I and II</li>
+            <li>Disco Elysium</li>
+            <li>Firewatch</li>
+            <li>Indie Games</li>
+            <ul>
+                <li>FEZ</li>
+                <li>Limbo</li>
+                <li>Super meatboy</li>
+                <li>Stardew Valley</li>
+                <li>Terraria</li>
+                <li>RimWorld</li>
+            </ul>
+            <li>Retro Games</li>
+            <ul>
+                <li>Zelda Ocarina of Time (N64)</li>
+                <li>Metroid Fusion (GBA)</li>
+                <li>Pokemon FireRed/LeafGreen (GBA)</li>
+                <li>Pokemon Black/White 2 (NDS)</li>
+                <li>New Super Mario (NDS)</li>
+                <li>Orcs & Elves (NDS)</li>
+                <li>Ace attorney (NDS)</li>
+            </ul>
+            <li>I also love flash games and RPG maker games :3</li>
+        </ul>
+    </li>
+    <li>Chess
+        <ul>
+            <li>Computer chess</li>
+                <ul>
+                    <li>Pure chess (on nintendo 3DS)</li>
+                    <li>Chessmaster grandmaster edition (on pc via Wine)</li>
+                </ul>
+            <li>I am not that good at them</li>
+            <li>But i like playing them X3</li>
+            <li>No i will not tell you my elo rating</li>
+        </ul>
+    </li>
+    <li>Websites & Internet 
+        <ul>
+            <li>Personal Blogs</li>
+            <li>Neocities</li>
+            <li>"Content more important then visuals" type sites</li>
+            <li>Web buttons</li>
+            <li>Streaming Communities</li>
+            <li>Open Source Projects</li>
+            <li>Fediverse</li>
+            <li>Internet communities</li>
+            <li>Funny java script effects on websites</li>
+            <li>Internet aesthetics</li>
+        </ul>
+    </li>
+    <li>Security and Privacy
+        <ul>
+            <li>Secure communication</li>
+            <li>PGP</li>
+            <li>XMPP</li>
+            <li>Anonymous Browsing</li>
+            <li>Tor</li>
+            <li>Selfhosting</li>
+            <li>Federation</li>
+        </ul>
+    </li>
+
+    <li>Linux
+        <ul>
+            <li>Ubuntu based distros, i don't judge</li>
+            <li>Linux ricing, to a extent ;3</li>
+            <li>Stacking window managers</li>
+            <li>Simple programs that do one thing well</li>
+            <li>Simple desktop layouts (like <a href="./win95.png" class="no-button">this</a>)</li>
+            <li>Discussions about free software ethics</li>
+        </ul>
+    </li>
+    <li>Recreational programming
+        <ul>
+            <li>PHP</li>
+            <li>Python</li>
+            <li>Some scripting in bash</li>
+            <li>and yes i like writing my websites by hand</li>
+        </ul>
+    </li>
+    <li>Music
+        <ul>
+            <li>Indie Rock</li>
+            <li>Classics of pop music (some new stuff too)</li>
+            <li>Soundtracks from few games</li>
+            <li>Classical music from time to time </li>
+        </ul>
+    </li>
+        <li>Other
+        <ul>
+            <li>Science in general, mostly maths and physics,<br> but biology or chemistry curiosities are also welcome</li>
+            <li>DIY electronics, raspberry pi, arduino or STM32</li>
+        </ul>
+    </li>
+</ul>
+
+        </div>
+
+<div class="interests">
+            <h2>Things that I dislike very much:</h2>
+
+<ul>
+    <li>Anti consumer and anti people, actions taken by corporations</li>
+    <li>Very far right, conservative people</li>
+    <li>Homophobes, transphobes, xenophobes etc</li>
+    <li>WYSIWYG editors like word or libreoffice<br>just use LaTeX, Markdown, HTML or any other markup language</li>
+    <li>Bloated CMS systems like wordpress<br> Static websites? Just write em by hand or use static site generators.<br> Interactive websites? Write em in php or use some flat file CMS.</li>
+    <li>Newer AAA games, like Ubislop stuff<br>There are exceptions to this rule, but not many</li>
+    <li>Bloated solutions to simple problems</li>
+    <li>Modern websites<br>Is there anyone who likes em?</li>
+</ul>
+
+        </div>
+
+        <div class="bio">
+            <h2>88x31 Web buttons</h2>
+
+<p class="center">My webbutton:</p>
+
+<img src="./buttons/pc_glam.png" class="footer-button">
+
+<p class="center">Code to embed my web button on your page:</p>
+
+<pre>
+&lt;a href="https://computer.glamour.ovh/"&gt;
+    &lt;img src="pc_glam.png"&gt;
+&lt;/a&gt;
+</pre>
+
+You can download the web button or fetch it from my website. 
+
+
+<p class="center">Cool people and other cool sites :3</p>
+
+        <a href="https://fozunja.glamour.ovh/" class="no-button">
+        <img src="https://fozunja.glamour.ovh/88x31/fznj.png" class="footer-button">
+        </a>
+        
+        <a href="https://lunya.pet/" class="no-button">
+        <img src="./buttons/lunya.gif" class="footer-button">
+        </a>
+
+        <a href="https://selstacker.space/" class="no-button">
+            <img src="./buttons/sel.png" class="fotter-button">
+        </a>
+
+        <a href="https://lakesnet.nekoweb.org//" class="no-button">
+            <img src="./buttons/lakes.png" class="fotter-button">
+        </a>
+
+        <a href="https://datakra.sh/" class="no-button">
+        <img src="./buttons/datakrash.png" class="footer-button">
+        </a>
+
+        <a href="https://nadeko.net/" class="no-button">
+        <img src="./buttons/nadeko.png" class="footer-button">
+        </a>
+
+        <a href="https://yui.dev/" class="no-button">
+        <img src="./buttons/yuidev.png" class="footer-button">
+        </a>
+
+        <a href="https://wiby.org/" class="no-button">
+            <img src="./buttons/wiby.org.gif" class="fotter-button">
+        </a>
+
+        <a href="https://fediring.net/" class="no-button">
+            <img src="./buttons/fedi.gif" class="fotter-button">
+        </a>
+<p class="center">No AI webring</p>
+
+ <map name="w95widget">
+<area href="https://baccyflap.com/noai" target="_blank" shape="rect" coords="0,0,308,22" alt="no ai webring" title="no ai webring">
+<area href="https://baccyflap.com/noai/?prv&s=pcg" target="_top" shape="rect" coords="56,36,130,58" alt="previous" title="previous">
+<area href="https://baccyflap.com/noai/?rnd" target="_top" shape="rect" coords="137,36,211,58" alt="random" title="random">
+<area href="https://baccyflap.com/noai/?nxt&s=pcg" target="_top" shape="rect" coords="218,36,292,58" alt="next" title="next">
+</map>
+<img style="filter:hue-rotate(20deg) saturate(1000%)" usemap="#w95widget" src="https://baccyflap.com/noai/w95widget.gif" alt="a gray Windows 95 style dialog box titled 'The No AI Webring' with a little icon showing a computer chip in a rubbish bin. beside it are three clickable buttons, labeled Previous, Random... and Next"> 
+
+<p class="center">Fediring</p>
+
+<p class="center">
+    <a href="https://fediring.net/previous?host=computer.glamour.ovh">←</a>
+    <a href="https://fediring.net/">Fediring</a>
+    <a href="https://fediring.net/next?host=computer.glamour.ovh">→</a>
+</p>
+
+<p class="center">My webring</p>
+
+<img src="./buttons/webring.gif" class="fotter-button">
+
+	<div style="margin: 0; padding: 0.1em; border: 2px inset">
+    <p class="center">Press here to get to a random cool site</p>
+		<a href="https://webri.ng/webring/glamour/random">Random Site</a>
+	</div>
+
+        </div>
+<div class="bio">
+<h2>Visit Counter</h2>
+
+<img src="./hit-counter/script.php"><br>
+<a href="https://git.cyberwa.re/revengeday/hit-counter">git.cyberwa.re/revengeday/hit-counter</a>
+</div>
+
+    </div>
+
+
+
+    <footer>
+        
+        <p class="center"><img src="./favicon.ico">(c) computer_glamour</p>
+    <img src="./buttons/-18.gif" class="footer-button">
+    <img src="./buttons/xmpp.gif" class="footer-button">
+    <img src="./buttons/notread.gif" class="footer-button">
+    <img src="./buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+
+</html>
+

TEMPAT SAMPAH
netscape.gif


TEMPAT SAMPAH
pc.gif


+ 98 - 0
pc.html

@@ -0,0 +1,98 @@
+<!DOCTYPE html>
+    <html lang="en">
+    <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>my PC</title>
+    <link rel="stylesheet" href="./style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+    </head>
+    <body>
+        <div class="container bio">
+            <h1>✨️ My PC setup ✨️</h1>
+<img src="./computer-pixel.gif">
+</div>
+
+
+
+
+    <div class="interests">
+<h2>Home PC</h2>
+        <ul>
+            <li>Brand name: T-Bao MN58U</li>
+            <li>CPU: AMD Ryzen 7 5800U</li>
+            <li>GPU: Radeon Vega integrated graphics</li>
+            <li>RAM: 32GB DDR4-3200</li>
+            <li>Storage: 1TB ssd (system), 2TB hdd (data)</li>
+            <li>Software</li>
+            <ul>
+                <li>OS: Linux Mint</li>
+                <li>DE: Cinnamon</li>
+            </ul>
+            <li>Accessories:</li>
+            <ul>
+                <li>Mouse: Logitech Marathon M705</li>
+                <li>Keyboard: Logitech MX keys S black</li>
+            </ul>
+        </ul>
+</div>
+    <div class="interests">
+    <h2>Main Laptop</h2>
+        <ul>
+            <li>Brand name: Dell Vostro 15 5590</li>
+            <li>CPU: Intel Core i5-10210U</li>
+            <li>GPU: Intel UHD Graphics 620</li>
+            <li>RAM: 16GB</li>
+            <li>Storage: 256GB ssd (system), 256GB ssd (data)</li>
+            <li>Software</li>
+            <ul>
+                <li>OS: Linux Mint</li>
+                <li>DE: Cinnamon</li>
+            </ul>
+        </ul>
+</div>
+
+    <div class="interests">
+    <h2>Other curiosities in my collection</h2>
+        <ul>
+            <li>3gen intel PC that runs as rdp server in my living room<br>Regrettably it is running windows, because i need it sometimes to test things in windows environment for my sysadmin job</li>
+            <li>Thinkpad X61</li>
+            <li>Macbook pro 2011</li>
+            <li>Thinkpad L440</li>
+            <li>Thinkpad 11e</li>
+            <li>Chuwi Minibook X</li>
+            <li>Giada mini industrial pc 3gen intel</li>
+            <li>3 Big rack servers. I do not know what to do with them X3</li>
+            <li>Some network equipment like old cisco and zyxel switches, mikrotik routers etc.</li>
+            <li>oscilloscope, signal generators and some other lab equipment</li>
+            <li>Commodore 64</li>
+            <li>Nintendo DS lite</li>
+            <li>Nintendo 2DS</li>
+            <li>Linux Emulation handheld R36S</li>
+            <li>and multiple different computer and electronic parts laying all around :3</li>
+        </ul>
+        <p>If you want to read more about how I do my computing <a href="https://computer.glamour.ovh/blog/blog.php?b=software" class="no-button">press here</a></p>
+</div>
+
+ </div>
+
+</div>
+    <footer>
+        <p class="center"><img src="./favicon.ico">(c) computer_glamour</p>
+    
+    <img src="./buttons/-18.gif" class="footer-button">
+    <img src="./buttons/xmpp.gif" class="footer-button">
+    <img src="./buttons/notread.gif" class="footer-button">
+    <img src="./buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+    </body>
+
+    </html>

TEMPAT SAMPAH
pc.png


+ 57 - 0
pgp.html

@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+    <html lang="en">
+    <head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>PGP</title>
+    <link rel="stylesheet" href="./style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+    </head>
+    <body>
+        <div class="container bio">
+            <h1>✨️ PGP ✨️</h1>
+
+
+<img src="./clippy.gif">
+
+<p>Please use it if you want secure communication with me.</p>
+
+<a href="https://keys.openpgp.org/search?q=computer@glamour.ovh">keys.openpgp.org</a>
+
+    <pre>
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEaZ1/BBYJKwYBBAHaRw8BAQdA+gaMB3LPjm4H6S7yHAJ7L7gR8SigTdnhqSj7
+k4/USzW0J2NvbXB1dGVyX2dsYW1vdXIgPGNvbXB1dGVyQGdsYW1vdXIub3ZoPoiT
+BBMWCgA7FiEEdTizxgqM2TXQAPNZgD6kXBFpcLAFAmmdfwQCGwMFCwkIBwICIgIG
+FQoJCAsCBBYCAwECHgcCF4AACgkQgD6kXBFpcLAH6QD6A43eSDZHNU3IBT2KUX8n
+WJCs79JqzPIg9FXy2ZWHL+UBAKs85pYSKtzac6Jh+J2J4QgcTjAwhfkJp8/QBMj/
+PEcIuDgEaZ1/BBIKKwYBBAGXVQEFAQEHQC+n4LxvJWy0xWaF4ZjFucCEUKG6O6FL
+AIcbk5n+FNIEAwEIB4h4BBgWCgAgFiEEdTizxgqM2TXQAPNZgD6kXBFpcLAFAmmd
+fwQCGwwACgkQgD6kXBFpcLCpHgD/c97oYOtVVBU1+B7teYTKRBWx+p6uLrdAa7SG
+C2eOV9oBAIsPo148IHjkNYZfxyzRiYte/VXrrZF8qcIkk9ihHLkA
+=Gpro
+-----END PGP PUBLIC KEY BLOCK-----
+    </pre>
+
+        </div>
+
+
+    <footer>
+       <p class="center"><img src="./favicon.ico">(c) computer_glamour</p>
+    
+    <img src="./buttons/-18.gif" class="footer-button">
+    <img src="./buttons/xmpp.gif" class="footer-button">
+    <img src="./buttons/notread.gif" class="footer-button">
+    <img src="./buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+    </body>
+    </html>

TEMPAT SAMPAH
pink_guy.gif


+ 1 - 0
privatebin/put_private_bin_here

@@ -0,0 +1 @@
+

+ 316 - 0
robots.txt

@@ -0,0 +1,316 @@
+User-agent: 2^32$
+User-agent: AddSearchBot
+User-agent: AdsBot-Google
+User-agent: Agentic
+User-agent: AhrefsBot
+User-agent: .ai 
+User-agent: AI21 Labs
+User-agent: AI2Bot
+User-agent: Ai2Bot-Dolma
+User-agent: AI2Bot-Dolma
+User-agent: AI Article Writer
+User-agent: AIBot
+User-agent: AI Content Detector
+User-agent: AI Dungeon
+User-agent: aiHitBot
+User-agent: AIMatrix
+User-agent: AISearchBot
+User-agent: AI Search Engine
+User-agent: AI SEO Crawler
+User-agent: AI Training
+User-agent: AITraining
+User-agent: AI Writer
+User-agent: Alexa
+User-agent: Alpha AI
+User-agent: AlphaAI
+User-agent: a[mazing]{42}(robot)
+User-agent: Amazon Bedrock
+User-agent: Amazonbot
+User-agent: AmazonBot
+User-agent: Amazon Comprehend
+User-agent: Amazon-Kendra
+User-agent: Amazon Lex
+User-agent: Amazon Sagemaker
+User-agent: Amazon Silk
+User-agent: Amazon Textract
+User-agent: Amelia
+User-agent: AndersPinkBot
+User-agent: Andibot
+User-agent: Anthropic
+User-agent: anthropic-ai
+User-agent: AnyPicker
+User-agent: Anyword
+User-agent: Applebot
+User-agent: Applebot-Extended
+User-agent: Aria Browse
+User-agent: Articoolo
+User-agent: Automated Writer
+User-agent: Awario
+User-agent: AwarioBot
+User-agent: AwarioRssBot
+User-agent: AwarioSmartBot
+User-agent: Azure
+User-agent: BardBot
+User-agent: bedrockbot
+User-agent: bigsur.ai
+User-agent: BLEXBot
+User-agent: Brave Leo
+User-agent: Brightbot 1.0
+User-agent: ByteDance
+User-agent: Bytespider
+User-agent: CatBoost
+User-agent: CCBot
+User-agent: CC-Crawler
+User-agent: ChatGLM
+User-agent: ChatGPT Agent
+User-agent: ChatGPT-User
+User-agent: ChatGPT-User/2.0
+User-agent: Chinchilla
+User-agent: Claude
+User-agent: ClaudeBot
+User-agent: Claude-SearchBot
+User-agent: Claude-User
+User-agent: claude-web
+User-agent: Claude-Web
+User-agent: ClearScope
+User-agent: CloudVertexBot
+User-agent: Cohere
+User-agent: cohere-ai
+User-agent: cohere-training-data-crawler
+User-agent: Common Crawl
+User-agent: CommonCrawl
+User-agent: ContentAtScale
+User-agent: ContentBot
+User-agent: Contentedge
+User-agent: Content Harmony
+User-agent: Content King
+User-agent: Content Optimizer
+User-agent: Content Samurai
+User-agent: Conversion AI
+User-agent: Copilot
+User-agent: CopyAI
+User-agent: Copymatic
+User-agent: Copyscape
+User-agent: Cotoyogi
+User-agent: crawler.with.dots
+User-agent: CrawlQ AI
+User-agent: Crawlspace
+User-agent: Crew AI
+User-agent: CrewAI
+User-agent: curl|sudo bash
+User-agent: DALL-E
+User-agent: DataForSeoBot
+User-agent: DataProvider
+User-agent: Datenbank Crawler
+User-agent: DeepAI
+User-agent: DeepL
+User-agent: DeepMind
+User-agent: DeepSeek
+User-agent: Devin
+User-agent: diffbot
+User-agent: Diffbot
+User-agent: Doubao AI
+User-agent: DuckAssistBot
+User-agent: Echobot Bot
+User-agent: EchoboxBot
+User-agent: Facebookbot
+User-agent: FacebookBot
+User-agent: facebookexternalhit
+User-agent: FacebookExternalHit
+User-agent: Factset_spyderbot
+User-agent: Falcon
+User-agent: Firecrawl
+User-agent: FirecrawlAgent
+User-agent: Flyriver
+User-agent: Frase AI
+User-agent: FriendlyCrawler
+User-agent: Fuzz Faster U Fool
+User-agent: Fuzz Faster U Fool v2.0.0
+User-agent: Gemini
+User-agent: Gemini-Deep-Research
+User-agent: Gemma
+User-agent: GenAI
+User-agent: Genspark
+User-agent: Gigabot
+User-agent: GLM
+User-agent: GoogleAgent-Mariner
+User-agent: Google-CloudVertexBot
+User-agent: Google-Extended
+User-agent: Google-Firebase
+User-agent: GoogleOther
+User-agent: GoogleOther-Image
+User-agent: GoogleOther-Video
+User-agent: Goose
+User-agent: GPT
+User-agent: GPTBot
+User-agent: Grammarly
+User-agent: Grendizer
+User-agent: Grok
+User-agent: GT Bot
+User-agent: GTBot
+User-agent: Hemingway Editor
+User-agent: Hugging Face
+User-agent: Hypotenuse AI
+User-agent: iaskspider
+User-agent: iaskspider/2.0
+User-agent: ICC-Crawler
+User-agent: ImageGen
+User-agent: ImagesiftBot
+User-agent: img2dataset
+User-agent: imgproxy
+User-agent: Inferkit
+User-agent: INK Editor
+User-agent: INKforall
+User-agent: IntelliSeek
+User-agent: ISSCyberRiskCrawler
+User-agent: Is this a crawler?
+User-agent: JasperAI
+User-agent: Kafkai
+User-agent: Kangaroo
+User-agent: Kangaroo Bot
+User-agent: Keyword Density AI
+User-agent: Knowledge
+User-agent: KomoBot
+User-agent: LinerBot
+User-agent: LinkedInBot
+User-agent: LLaMA
+User-agent: LLMs
+User-agent: magpie-crawler
+User-agent: MarketMuse
+User-agent: Meltwater
+User-agent: Meta AI
+User-agent: Meta-AI
+User-agent: MetaAI
+User-agent: Meta-External
+User-agent: meta-externalagent
+User-agent: Meta-ExternalAgent
+User-agent: meta-externalfetcher
+User-agent: Meta-ExternalFetcher
+User-agent: MetaTagBot
+User-agent: meta-webindexer
+User-agent: Mistral
+User-agent: MistralAI-User
+User-agent: MistralAI-User/1.0
+User-agent: MJ12bot
+User-agent: MyCentralAIScraperBot
+User-agent: Narrative
+User-agent: NeevaBot
+User-agent: netEstate Imprint Crawler
+User-agent: NeuralSEO
+User-agent: Neural Text
+User-agent: Nova Act
+User-agent: NovaAct
+User-agent: Nutch
+User-agent: OAI-SearchBot
+User-agent: omgili
+User-agent: Omgili
+User-agent: omgilibot
+User-agent: Omgilibot
+User-agent: OmniExplorer_Bot
+User-agent: Open AI
+User-agent: OpenAI
+User-agent: OpenBot
+User-agent: OpenText AI
+User-agent: Operator
+User-agent: Outwrite
+User-agent: Page Analyzer AI
+User-agent: PanguBot
+User-agent: Panscient
+User-agent: panscient.com
+User-agent: Paperlibot
+User-agent: Paraphraser.io
+User-agent: peer39_crawler
+User-agent: Perplexity
+User-agent: PerplexityBot
+User-agent: Perplexity-User
+User-agent: Petalbot
+User-agent: PetalBot
+User-agent: Phindbot
+User-agent: PhindBot
+User-agent: PiplBot
+User-agent: Poseidon Research Crawler
+User-agent: prefetch-proxy
+User-agent: ProWritingAid
+User-agent: psbot
+User-agent: python-requests
+User-agent: QualifiedBot
+User-agent: QuillBot
+User-agent: quillbot.com
+User-agent: RobotSpider
+User-agent: Robozilla
+User-agent: Rytr
+User-agent: SaplingAI
+User-agent: SBIntuitionsBot
+User-agent: Scalenut
+User-agent: Scraper
+User-agent: Scrapy
+User-agent: ScriptBook
+User-agent: Seekr
+User-agent: SemrushBot-OCOB
+User-agent: SemrushBot-SWA
+User-agent: sentibot
+User-agent: Sentibot
+User-agent: SentiBot
+User-agent: SEO Content Machine
+User-agent: SEO Robot
+User-agent: ShapBot
+User-agent: Sidetrade
+User-agent: Sidetrade indexer bot
+User-agent: Simplified AI
+User-agent: Sitefinity
+User-agent: Skydancer
+User-agent: SlickWrite
+User-agent: Sonic
+User-agent: Spinbot
+User-agent: Spin Rewriter
+User-agent: Stability
+User-agent: StableDiffusionBot
+User-agent: star***crawler
+User-agent: Sudowrite
+User-agent: SummalyBot
+User-agent: Super Agent
+User-agent: Surfer AI
+User-agent: Teoma
+User-agent: TerraCotta
+User-agent: Text Blaze
+User-agent: TextCortex
+User-agent: The Knowledge AI
+User-agent: Thinkbot
+User-agent: ThinkChaos
+User-agent: TikTokSpider
+User-agent: Timpibot
+User-agent: TimpiBot
+User-agent: TurnitinBot
+User-agent: VelenPublicWebCrawler
+User-agent: Vidnami AI
+User-agent: WARDBot
+User-agent: Webzio
+User-agent: webzio-extended
+User-agent: Webzio-Extended
+User-agent: Whisper
+User-agent: WordAI
+User-agent: Wordtune
+User-agent: WormsGTP
+User-agent: wpbot
+User-agent: WPBot
+User-agent: Writecream
+User-agent: WriterZen
+User-agent: Writescope
+User-agent: Writesonic
+User-agent: xAI
+User-agent: xBot
+User-agent: YaK
+User-agent: YandexAdditional
+User-agent: YandexAdditionalBot
+User-agent: Youbot
+User-agent: YouBot
+User-agent: Zerochat
+User-agent: Zero GTP
+User-agent: Zhipu
+User-agent: Zimm
+Disallow: /
+Disallow: *
+DisallowAITraining: /
+
+Content-Usage: ai=n
+

TEMPAT SAMPAH
shutdown.gif


+ 177 - 0
style.css

@@ -0,0 +1,177 @@
+ html, body {
+            height: 100vh; 
+            margin: 0; 
+            display: flex; 
+            flex-direction: column; 
+            overflow: auto;
+            
+        }
+
+        body {
+            background-color: #800080; 
+            font-family: 'Lucida Console', monospace;
+            /*font-family: Arial, sans-serif;*/
+            text-align: center;
+            background-image: url('./bg.gif');
+            background-size: auto;
+            background-position: top left;
+            background-repeat: repeat;
+            background-attachment: fixed;
+            height: 100vh;
+            margin: 0;
+            
+            
+        }
+
+        marquee {
+            font-family: 'Lucida Console', monospace;
+            font-size: 12px;
+            color: #FF00FF;
+        }
+
+        p {
+            text-align: justify;
+        }
+
+        .center {
+            text-align: center;
+        }
+
+        time, form, pre, p, li, ul, a{
+            font-size: 13px;
+            color: #fff;
+
+        }
+
+        .container {
+            flex: 1; 
+            padding: 50px;
+            
+        }
+
+        img.profile-pic {
+            width: 150px; 
+            height: auto;
+        }
+
+        h1, h2, h3, h4, h5, h6 {
+            color: #FF00FF; 
+            font-family: 'Lucida Console', monospace;
+        }
+
+        a {
+            display: inline-block;
+            margin: 20px 0;
+            padding: 10px 20px;
+            text-decoration: none;
+            background-color: #FF00FF; 
+            color: white;
+            border-radius: 5px;
+        }
+
+        a:hover {
+            background-color: #c5005a; 
+        }
+
+        footer {
+            padding: 10px;
+            background-color: #800080; 
+            color: white; 
+        }
+
+       
+        .bio, .interests {
+            margin: 20px auto;
+            background-color: rgba(0, 0, 0, 0.7); 
+            padding: 10px;
+            border: 2px solid #FF00FF; 
+            width: 600px;
+
+        }
+
+        h2 {
+            color: #FF00FF; 
+        }
+
+        .footer-button {
+            display: inline-block;           
+            width: 88px;                   
+            height: 31px;                   
+        }
+
+        .footer-button:hover {
+            background-color: #0056b3;      
+        }
+
+
+        .interests ul {
+            padding-left: 1; 
+            text-align: left;       
+        }
+
+        .interests li {        
+            margin: 5px; 
+        }
+        .no-button {
+            display: inline;
+            background: none; 
+            color: inherit; 
+            padding: 0; 
+            color: #FF00FF;
+        }
+        .light_font {
+            color: #FF00FF; 
+        }
+
+        .small-pic {
+            max-width: 100%;      
+            height: auto;        
+        }
+table {
+    width: 100%;
+    border-collapse: collapse;
+    margin: 20px 0;
+    
+}
+
+th {
+    background-color: #800080;
+    color: #FF00FF;
+    padding: 10px;
+    text-align: left;
+}
+
+td {
+    background-color: transparent;
+    color: #fff;
+    padding: 10px;
+    border: 2px solid #800080;
+}
+
+tr:nth-child(even) {
+    background-color: transparent;
+}
+
+
+
+
+@media screen and (max-width: 768px) {
+    .bio, .interests {
+        width: 90%; 
+        padding: 5%; 
+        margin: 10px auto; 
+    }
+
+    h1 {
+        font-size: 1.5em; 
+    }
+
+    h2 {
+        font-size: 1.2em; 
+    }
+
+    a {
+        padding: 8px 15px; 
+    }
+}
+

TEMPAT SAMPAH
this-is-the-end.gif


+ 5 - 0
vault/gifs/README.md

@@ -0,0 +1,5 @@
+# Collection of nice retro gifs i found on the internet
+
+If original authors of them want them removed from there please contact me via [here](https://computer.glamour.ovh/bsod.gif)
+
+pc.gif is a gif that was edited by my friend who changed color scheme of an existing gif to be more pink :3

TEMPAT SAMPAH
vault/gifs/bg.gif


TEMPAT SAMPAH
vault/gifs/capri.gif


TEMPAT SAMPAH
vault/gifs/clippy.gif


TEMPAT SAMPAH
vault/gifs/computer-pixel.gif


TEMPAT SAMPAH
vault/gifs/fish.gif


TEMPAT SAMPAH
vault/gifs/netscape.gif


TEMPAT SAMPAH
vault/gifs/pc.gif


TEMPAT SAMPAH
vault/gifs/pink_guy.gif


TEMPAT SAMPAH
vault/gifs/shutdown.gif


TEMPAT SAMPAH
vault/gifs/start_button.gif


TEMPAT SAMPAH
vault/gifs/this-is-the-end.gif


TEMPAT SAMPAH
vault/gifs/welcome.gif


+ 88 - 0
vault/index.php

@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>The Vault</title>
+
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+
+<body>
+    <div class="container bio interests">
+    <h1>✨️ The Vault ✨️</h1>
+    <p>This is my public file dump, you can find here everything and nothing at the same time. I throw in here things that I either found on the internet, what some people have shared with me or some of my own inventions. Please use it accordingly, authors of the thingies are listed in README files.</p>
+            <?php
+                function listDirectoryContents($dir, $basePath = '') {
+                    $items = array_diff(scandir($dir), array('..', '.', 'index.php'));
+                    
+                    if (empty($items)) {
+                        return;
+                    }
+                    
+                    // Separate directories and files
+                    $directories = array();
+                    $files = array();
+                    
+                    foreach ($items as $item) {
+                        $fullPath = $dir . '/' . $item;
+                        if (is_dir($fullPath)) {
+                            $directories[] = $item;
+                        } else {
+                            $files[] = $item;
+                        }
+                    }
+                    
+                    // Sort both arrays
+                    sort($directories);
+                    sort($files);
+                    
+                    // Combine with directories first
+                    $sortedItems = array_merge($directories, $files);
+                    
+                    echo '<ul>';
+                    foreach ($sortedItems as $item) {
+                        $fullPath = $dir . '/' . $item;
+                        $isDir = is_dir($fullPath);
+                        $icon = $isDir ? '📁 ' : '📄 ';
+                        $link = htmlspecialchars($item);
+                        $relativePath = $basePath ? $basePath . '/' . $item : $item;
+                        
+                        if ($isDir) {
+                            echo "<li>{$icon}{$link}";
+                            listDirectoryContents($fullPath, $relativePath);
+                            echo "</li>";
+                        } else {
+                            $href = htmlspecialchars($relativePath);
+                            echo "<li><a href=\"{$href}\" class=\"no-button\">{$icon}{$link}</a></li>";
+                        }
+                    }
+                    echo '</ul>';
+                }
+                
+                $currentDir = dirname(__FILE__);
+                listDirectoryContents($currentDir);
+            ?>
+    </div>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+        <img src="../buttons/-18.gif" class="footer-button">
+        <img src="../buttons/xmpp.gif" class="footer-button">
+        <img src="../buttons/notread.gif" class="footer-button">
+        <img src="../buttons/right2repair.gif" class="footer-button">
+    </footer>
+
+</body>
+
+</html>
+

+ 7 - 0
vault/nodes-effect/README.md

@@ -0,0 +1,7 @@
+# Thanks to Ramses Revengeday 
+
+For sharing this invention with me.
+
+https://revenge.day/
+
+original file: https://share.datakra.sh/wOdi9/ZeBOYawO10.html

+ 224 - 0
vault/nodes-effect/nodes.html

@@ -0,0 +1,224 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>nodes</title>
+  <style>
+    :root {
+      --node-color: #ff00ff;
+      --bg-1: #05010a;
+      --bg-2: #13051d;
+      --line-alpha: 0.28;
+    }
+
+    * {
+      box-sizing: border-box;
+    }
+
+    html, body {
+      margin: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      background:
+        radial-gradient(circle at 20% 20%, rgba(255, 0, 255, 0.08), transparent 30%),
+        radial-gradient(circle at 80% 30%, rgba(255, 0, 255, 0.06), transparent 24%),
+        linear-gradient(160deg, var(--bg-1), var(--bg-2));
+      font-family: Inter, Arial, sans-serif;
+    }
+
+    canvas {
+      display: block;
+      width: 100vw;
+      height: 100vh;
+      cursor: crosshair;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="network"></canvas>
+  
+
+
+  <script>
+    const canvas = document.getElementById('network');
+    const ctx = canvas.getContext('2d');
+
+    const CONFIG = {
+      nodeCount: 90,
+      nodeColor: '#ff00ff',
+      nodeRadiusMin: 2.2,
+      nodeRadiusMax: 5.5,
+      linkDistance: 150,
+      mouseInfluence: 190,
+      attractionStrength: 0.02,
+      returnStrength: 0.012,
+      friction: 0.92,
+      pulseSpeed: 0.0025,
+      wobble: 0.18,
+    };
+
+    let width = 0;
+    let height = 0;
+    let dpr = Math.min(window.devicePixelRatio || 1, 2);
+    let time = 0;
+
+    const mouse = {
+      x: 0,
+      y: 0,
+      active: false,
+    };
+
+    class Node {
+      constructor() {
+        this.homeX = Math.random() * width;
+        this.homeY = Math.random() * height;
+        this.x = this.homeX;
+        this.y = this.homeY;
+        this.vx = 0;
+        this.vy = 0;
+        this.radius = CONFIG.nodeRadiusMin + Math.random() * (CONFIG.nodeRadiusMax - CONFIG.nodeRadiusMin);
+        this.seed = Math.random() * Math.PI * 2;
+      }
+
+      update(t) {
+        const dxHome = this.homeX - this.x;
+        const dyHome = this.homeY - this.y;
+
+        this.vx += dxHome * CONFIG.returnStrength;
+        this.vy += dyHome * CONFIG.returnStrength;
+
+        const wobbleX = Math.cos(t * CONFIG.pulseSpeed + this.seed) * CONFIG.wobble;
+        const wobbleY = Math.sin(t * CONFIG.pulseSpeed * 0.85 + this.seed) * CONFIG.wobble;
+        this.vx += wobbleX * 0.03;
+        this.vy += wobbleY * 0.03;
+
+        if (mouse.active) {
+          const dx = mouse.x - this.x;
+          const dy = mouse.y - this.y;
+          const dist = Math.hypot(dx, dy) || 0.0001;
+
+          if (dist < CONFIG.mouseInfluence) {
+            const force = (1 - dist / CONFIG.mouseInfluence) * CONFIG.attractionStrength * 28;
+            this.vx += (dx / dist) * force;
+            this.vy += (dy / dist) * force;
+          }
+        }
+
+        this.vx *= CONFIG.friction;
+        this.vy *= CONFIG.friction;
+        this.x += this.vx;
+        this.y += this.vy;
+      }
+
+      draw(t) {
+        const pulse = 1 + Math.sin(t * 0.004 + this.seed) * 0.16;
+        const r = this.radius * pulse;
+
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
+        ctx.fillStyle = CONFIG.nodeColor;
+        ctx.shadowColor = CONFIG.nodeColor;
+        ctx.shadowBlur = 16;
+        ctx.fill();
+        ctx.shadowBlur = 0;
+
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, r * 2.1, 0, Math.PI * 2);
+        ctx.fillStyle = 'rgba(255, 0, 255, 0.05)';
+        ctx.fill();
+      }
+    }
+
+    let nodes = [];
+
+    function resize() {
+      width = window.innerWidth;
+      height = window.innerHeight;
+      dpr = Math.min(window.devicePixelRatio || 1, 2);
+
+      canvas.width = width * dpr;
+      canvas.height = height * dpr;
+      canvas.style.width = width + 'px';
+      canvas.style.height = height + 'px';
+      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+      nodes = Array.from({ length: CONFIG.nodeCount }, () => new Node());
+    }
+
+    function drawLinks() {
+      for (let i = 0; i < nodes.length; i++) {
+        for (let j = i + 1; j < nodes.length; j++) {
+          const a = nodes[i];
+          const b = nodes[j];
+          const dx = b.x - a.x;
+          const dy = b.y - a.y;
+          const dist = Math.hypot(dx, dy);
+
+          if (dist < CONFIG.linkDistance) {
+            const alpha = (1 - dist / CONFIG.linkDistance) * 0.65;
+            ctx.beginPath();
+            ctx.moveTo(a.x, a.y);
+            ctx.lineTo(b.x, b.y);
+            ctx.strokeStyle = `rgba(255, 0, 255, ${alpha * 0.65})`;
+            ctx.lineWidth = 1;
+            ctx.stroke();
+          }
+        }
+      }
+    }
+
+    function animate(timestamp) {
+      time = timestamp;
+      ctx.clearRect(0, 0, width, height);
+
+      const gradient = ctx.createRadialGradient(
+        mouse.active ? mouse.x : width * 0.5,
+        mouse.active ? mouse.y : height * 0.5,
+        0,
+        width * 0.5,
+        height * 0.5,
+        Math.max(width, height) * 0.7
+      );
+      gradient.addColorStop(0, 'rgba(255, 0, 255, 0.06)');
+      gradient.addColorStop(1, 'rgba(255, 0, 255, 0)');
+      ctx.fillStyle = gradient;
+      ctx.fillRect(0, 0, width, height);
+
+      nodes.forEach(node => node.update(timestamp));
+      drawLinks();
+      nodes.forEach(node => node.draw(timestamp));
+
+      requestAnimationFrame(animate);
+    }
+
+    window.addEventListener('mousemove', (event) => {
+      mouse.x = event.clientX;
+      mouse.y = event.clientY;
+      mouse.active = true;
+    });
+
+    window.addEventListener('touchmove', (event) => {
+      if (event.touches[0]) {
+        mouse.x = event.touches[0].clientX;
+        mouse.y = event.touches[0].clientY;
+        mouse.active = true;
+      }
+    }, { passive: true });
+
+    window.addEventListener('mouseleave', () => {
+      mouse.active = false;
+    });
+
+    window.addEventListener('touchend', () => {
+      mouse.active = false;
+    });
+
+    window.addEventListener('resize', resize);
+
+    resize();
+    requestAnimationFrame(animate);
+  </script>
+</body>
+</html>

+ 223 - 0
vault/nodes-effect/nodes.html.txt

@@ -0,0 +1,223 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8" />
+  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+  <title>nodes</title>
+  <style>
+    :root {
+      --node-color: #ff00ff;
+      --bg-1: #05010a;
+      --bg-2: #13051d;
+      --line-alpha: 0.28;
+    }
+
+    * {
+      box-sizing: border-box;
+    }
+
+    html, body {
+      margin: 0;
+      width: 100%;
+      height: 100%;
+      overflow: hidden;
+      background:
+        radial-gradient(circle at 20% 20%, rgba(255, 0, 255, 0.08), transparent 30%),
+        radial-gradient(circle at 80% 30%, rgba(255, 0, 255, 0.06), transparent 24%),
+        linear-gradient(160deg, var(--bg-1), var(--bg-2));
+      font-family: Inter, Arial, sans-serif;
+    }
+
+    canvas {
+      display: block;
+      width: 100vw;
+      height: 100vh;
+      cursor: crosshair;
+    }
+  </style>
+</head>
+<body>
+  <canvas id="network"></canvas>
+  
+
+  <script>
+    const canvas = document.getElementById('network');
+    const ctx = canvas.getContext('2d');
+
+    const CONFIG = {
+      nodeCount: 90,
+      nodeColor: '#ff00ff',
+      nodeRadiusMin: 2.2,
+      nodeRadiusMax: 5.5,
+      linkDistance: 150,
+      mouseInfluence: 190,
+      attractionStrength: 0.02,
+      returnStrength: 0.012,
+      friction: 0.92,
+      pulseSpeed: 0.0025,
+      wobble: 0.18,
+    };
+
+    let width = 0;
+    let height = 0;
+    let dpr = Math.min(window.devicePixelRatio || 1, 2);
+    let time = 0;
+
+    const mouse = {
+      x: 0,
+      y: 0,
+      active: false,
+    };
+
+    class Node {
+      constructor() {
+        this.homeX = Math.random() * width;
+        this.homeY = Math.random() * height;
+        this.x = this.homeX;
+        this.y = this.homeY;
+        this.vx = 0;
+        this.vy = 0;
+        this.radius = CONFIG.nodeRadiusMin + Math.random() * (CONFIG.nodeRadiusMax - CONFIG.nodeRadiusMin);
+        this.seed = Math.random() * Math.PI * 2;
+      }
+
+      update(t) {
+        const dxHome = this.homeX - this.x;
+        const dyHome = this.homeY - this.y;
+
+        this.vx += dxHome * CONFIG.returnStrength;
+        this.vy += dyHome * CONFIG.returnStrength;
+
+        const wobbleX = Math.cos(t * CONFIG.pulseSpeed + this.seed) * CONFIG.wobble;
+        const wobbleY = Math.sin(t * CONFIG.pulseSpeed * 0.85 + this.seed) * CONFIG.wobble;
+        this.vx += wobbleX * 0.03;
+        this.vy += wobbleY * 0.03;
+
+        if (mouse.active) {
+          const dx = mouse.x - this.x;
+          const dy = mouse.y - this.y;
+          const dist = Math.hypot(dx, dy) || 0.0001;
+
+          if (dist < CONFIG.mouseInfluence) {
+            const force = (1 - dist / CONFIG.mouseInfluence) * CONFIG.attractionStrength * 28;
+            this.vx += (dx / dist) * force;
+            this.vy += (dy / dist) * force;
+          }
+        }
+
+        this.vx *= CONFIG.friction;
+        this.vy *= CONFIG.friction;
+        this.x += this.vx;
+        this.y += this.vy;
+      }
+
+      draw(t) {
+        const pulse = 1 + Math.sin(t * 0.004 + this.seed) * 0.16;
+        const r = this.radius * pulse;
+
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, r, 0, Math.PI * 2);
+        ctx.fillStyle = CONFIG.nodeColor;
+        ctx.shadowColor = CONFIG.nodeColor;
+        ctx.shadowBlur = 16;
+        ctx.fill();
+        ctx.shadowBlur = 0;
+
+        ctx.beginPath();
+        ctx.arc(this.x, this.y, r * 2.1, 0, Math.PI * 2);
+        ctx.fillStyle = 'rgba(255, 0, 255, 0.05)';
+        ctx.fill();
+      }
+    }
+
+    let nodes = [];
+
+    function resize() {
+      width = window.innerWidth;
+      height = window.innerHeight;
+      dpr = Math.min(window.devicePixelRatio || 1, 2);
+
+      canvas.width = width * dpr;
+      canvas.height = height * dpr;
+      canvas.style.width = width + 'px';
+      canvas.style.height = height + 'px';
+      ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
+
+      nodes = Array.from({ length: CONFIG.nodeCount }, () => new Node());
+    }
+
+    function drawLinks() {
+      for (let i = 0; i < nodes.length; i++) {
+        for (let j = i + 1; j < nodes.length; j++) {
+          const a = nodes[i];
+          const b = nodes[j];
+          const dx = b.x - a.x;
+          const dy = b.y - a.y;
+          const dist = Math.hypot(dx, dy);
+
+          if (dist < CONFIG.linkDistance) {
+            const alpha = (1 - dist / CONFIG.linkDistance) * 0.65;
+            ctx.beginPath();
+            ctx.moveTo(a.x, a.y);
+            ctx.lineTo(b.x, b.y);
+            ctx.strokeStyle = `rgba(255, 0, 255, ${alpha * 0.65})`;
+            ctx.lineWidth = 1;
+            ctx.stroke();
+          }
+        }
+      }
+    }
+
+    function animate(timestamp) {
+      time = timestamp;
+      ctx.clearRect(0, 0, width, height);
+
+      const gradient = ctx.createRadialGradient(
+        mouse.active ? mouse.x : width * 0.5,
+        mouse.active ? mouse.y : height * 0.5,
+        0,
+        width * 0.5,
+        height * 0.5,
+        Math.max(width, height) * 0.7
+      );
+      gradient.addColorStop(0, 'rgba(255, 0, 255, 0.06)');
+      gradient.addColorStop(1, 'rgba(255, 0, 255, 0)');
+      ctx.fillStyle = gradient;
+      ctx.fillRect(0, 0, width, height);
+
+      nodes.forEach(node => node.update(timestamp));
+      drawLinks();
+      nodes.forEach(node => node.draw(timestamp));
+
+      requestAnimationFrame(animate);
+    }
+
+    window.addEventListener('mousemove', (event) => {
+      mouse.x = event.clientX;
+      mouse.y = event.clientY;
+      mouse.active = true;
+    });
+
+    window.addEventListener('touchmove', (event) => {
+      if (event.touches[0]) {
+        mouse.x = event.touches[0].clientX;
+        mouse.y = event.touches[0].clientY;
+        mouse.active = true;
+      }
+    }, { passive: true });
+
+    window.addEventListener('mouseleave', () => {
+      mouse.active = false;
+    });
+
+    window.addEventListener('touchend', () => {
+      mouse.active = false;
+    });
+
+    window.addEventListener('resize', resize);
+
+    resize();
+    requestAnimationFrame(animate);
+  </script>
+</body>
+</html>

+ 5 - 0
vault/php-makrdown-blog/README.md

@@ -0,0 +1,5 @@
+# PHP Markdown blog
+
+In order for it to work you need do download Parsedown library for example with composer and put it according to the location listed in source code.
+
+made by: computer_glamour

+ 79 - 0
vault/php-makrdown-blog/blog.php.txt

@@ -0,0 +1,79 @@
+<?php
+declare(strict_types=1);
+
+// ========================
+// CONFIG
+// ========================
+$blogDir = __DIR__ . '/posts/';
+$defaultPost = 'index';
+
+// ========================
+// SECURITY: sanitize input
+// ========================
+$post = $_GET['b'] ?? $defaultPost;
+
+// allow only safe filenames
+if (!preg_match('/^[a-zA-Z0-9_-]+$/', $post)) {
+    http_response_code(400);
+    die('Invalid post name.');
+}
+
+$mdFile = $blogDir . $post . '.md';
+
+if (!file_exists($mdFile)) {
+    http_response_code(404);
+    $mdContent = "# 404\n\nPost not found.";
+} else {
+    $mdContent = file_get_contents($mdFile);
+}
+
+// ========================
+// MARKDOWN PARSER
+// ========================
+require_once __DIR__ . '/../vendor/Parsedown.php';
+require_once __DIR__ . '/../vendor/ParsedownExtra.php';
+
+
+
+$parser = new ParsedownExtra();
+$parser->setSafeMode(false);       // allow HTML in markdown
+$parser->setMarkupEscaped(false);  // don't escape HTML
+
+$htmlContent = $parser->text($mdContent);
+?>
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Blog</title>
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+    </style>
+</head>
+<body>
+
+
+<main class="container interests">
+  <?= $htmlContent ?>
+</main>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+</html>

+ 84 - 0
vault/php-makrdown-blog/index.php.txt

@@ -0,0 +1,84 @@
+<?php
+declare(strict_types=1);
+
+$postsDir = __DIR__ . '/posts/';
+$posts = [];
+
+// scan markdown files
+foreach (glob($postsDir . '*.md') as $file) {
+    $filename = basename($file, '.md');
+    $content = file_get_contents($file);
+
+    // extract first markdown H1
+    if (preg_match('/^#\s+(.+)$/m', $content, $matches)) {
+        $title = trim($matches[1]);
+    } else {
+        $title = $filename;
+    }
+
+    $posts[] = [
+        'slug' => $filename,
+        'title' => $title,
+        'time' => filemtime($file),
+    ];
+}
+
+// sort newest first
+usort($posts, fn($a, $b) => $b['time'] <=> $a['time']);
+?>
+<!DOCTYPE html>
+<html lang="pl">
+<head>
+  <meta charset="UTF-8">
+  <meta name="viewport" content="width=device-width, initial-scale=1">
+  <title>Blog</title>
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+<body>
+<div class="container bio">
+  <h1>📝 Blog 💻</h1>
+
+<img src="../shutdown.gif" class="small-pic">
+<p class="center">Blog posts:</p>
+  <?php if (empty($posts)): ?>
+    <p>Brak wpisów.</p>
+  <?php else: ?>
+    <ul class="post-list">
+      <?php foreach ($posts as $post): ?>
+          <a href="/blog/blog.php?b=<?= htmlspecialchars($post['slug']) ?>">
+            <?= htmlspecialchars($post['title']) ?>
+          </a>
+          <time datetime="<?= date('c', $post['time']) ?>">
+            <?= date('Y-m-d', $post['time']) ?>
+          </time>
+            <br>
+      <?php endforeach; ?>
+    </ul>
+  <?php endif; ?>
+<p class="center">Other thingies:</p>
+          <a href="/blog/video_games.php">Video Game ranking          </a>
+            <br>
+
+</div>
+</main>
+
+    <footer>
+        <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+</html>

TEMPAT SAMPAH
vault/wallpapers/bg.png


+ 9 - 0
vault/xmpp-registration-form/README.md

@@ -0,0 +1,9 @@
+# XMPP registration form
+
+Made by: computer_glamour
+
+Simple PHP script that handles form to register new users on prosody XMPP server
+
+## DISCLAIMER
+
+in order for it to work you need to give `www-data` user sudo privileges to run `prosodyctl` without password

+ 63 - 0
vault/xmpp-registration-form/index.html.txt

@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>✨️computer_glamour✨️</title>
+
+    <link rel="stylesheet" href="../style.css">
+<script src="https://unpkg.com/cursor-effects@latest/dist/browser.js"></script>
+
+<script>
+window.addEventListener("load", (event) => {
+  new cursoreffects.ghostCursor();
+});
+</script>
+
+</head>
+
+<body>
+    <div class="container bio">
+    
+    <h1>Register for XMPP</h1>
+
+    <p>1. Ask for a secret code via email</p>
+<a href="mailto:computer@glamour.ovh?subject=XMPP%20secret%20code%20request&body=Hello,%0A%0AI%20would%20like%20to%20register%20on%20your%20XMPP%20server.%0ACan%20I%20request%20XMPP%20secret%20code?%0A%0AReason%20to%20register:%0A%3CPut%20your%20Reason%20here%20%3E%0A%0ABye,%0A%3Cyour%20nickname%20here%3E">Send Email</a>
+    <p>2. Register in the form below</p>
+    <form action="register.php" method="POST">
+        <label for="username">Username:</label><br>
+        <input type="text" id="username" name="username" required>
+        <br>
+        <label for="password">Password:</label><br>
+        <input type="password" id="password" name="password" required>
+        <br>
+        <label for="captcha">Secret code</label><br>
+        <input type="text" id="captcha" name="captcha" required>
+        <br>
+        <input type="submit" value="Register">
+    </form>
+    <p>3. Download your favorite xmpp app<br><br>
+For Linux i recommend Dino<br>
+For Android i recommend Monocles<br>
+For iPhone i recommend Monal<br>
+With Windows you are on your own :3</p>
+    <p>4. Open your app and type in your username@xmpp.glamour.ovh<br><br>and password that you have typed in the registration form</p>
+    
+  </div>
+  
+    </div>
+
+    <footer>
+       <p class="center"><img src="../favicon.ico">(c) computer_glamour</p>
+    
+    <img src="../buttons/-18.gif" class="footer-button">
+    <img src="../buttons/xmpp.gif" class="footer-button">
+    <img src="../buttons/notread.gif" class="footer-button">
+    <img src="../buttons/right2repair.gif" class="footer-button">
+
+    </footer>
+
+</body>
+
+</html>

+ 35 - 0
vault/xmpp-registration-form/register.php.txt

@@ -0,0 +1,35 @@
+<?php
+if ($_SERVER['REQUEST_METHOD'] === 'POST') {
+    $username = escapeshellarg(trim($_POST['username'])); // Sanitize input
+    $password = escapeshellarg(trim($_POST['password'])); // Sanitize input
+    $captcha = strtolower(trim($_POST['captcha'])); // Normalize input
+    
+    // Correct answer to the CAPTCHA question
+    $correct_answer = 'XXXXXX'; 
+
+    // Check CAPTCHA answer
+    if ($captcha !== $correct_answer) {
+        echo "Incorrect CAPTCHA answer. Please try again.";
+        exit;
+    }
+
+    // Command to register the user using prosodyctl
+    $command = "sudo /usr/bin/prosodyctl register $username xmpp.glamour.ovh $password"; // Update domain
+
+    // Execute the command
+    $output = array();
+    $return_var = 0; // Variable to capture the command exit status
+
+    exec($command, $output, $return_var);
+
+    // Check if registration was successful
+    if ($return_var === 0) {
+        echo "Registration successful!";
+    } else {
+        echo "Registration failed: " . implode("\n", $output);
+    }
+} else {
+    echo "Invalid request method.";
+}
+?>
+

+ 1994 - 0
vendor/Parsedown.php

@@ -0,0 +1,1994 @@
+<?php
+
+#
+#
+# Parsedown
+# http://parsedown.org
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class Parsedown
+{
+    # ~
+
+    const version = '1.8.0';
+
+    # ~
+
+    function text($text)
+    {
+        $Elements = $this->textElements($text);
+
+        # convert to markup
+        $markup = $this->elements($Elements);
+
+        # trim line breaks
+        $markup = trim($markup, "\n");
+
+        return $markup;
+    }
+
+    protected function textElements($text)
+    {
+        # make sure no definitions are set
+        $this->DefinitionData = array();
+
+        # standardize line breaks
+        $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+        # remove surrounding line breaks
+        $text = trim($text, "\n");
+
+        # split text into lines
+        $lines = explode("\n", $text);
+
+        # iterate through lines to identify blocks
+        return $this->linesElements($lines);
+    }
+
+    #
+    # Setters
+    #
+
+    function setBreaksEnabled($breaksEnabled)
+    {
+        $this->breaksEnabled = $breaksEnabled;
+
+        return $this;
+    }
+
+    protected $breaksEnabled;
+
+    function setMarkupEscaped($markupEscaped)
+    {
+        $this->markupEscaped = $markupEscaped;
+
+        return $this;
+    }
+
+    protected $markupEscaped;
+
+    function setUrlsLinked($urlsLinked)
+    {
+        $this->urlsLinked = $urlsLinked;
+
+        return $this;
+    }
+
+    protected $urlsLinked = true;
+
+    function setSafeMode($safeMode)
+    {
+        $this->safeMode = (bool) $safeMode;
+
+        return $this;
+    }
+
+    protected $safeMode;
+
+    function setStrictMode($strictMode)
+    {
+        $this->strictMode = (bool) $strictMode;
+
+        return $this;
+    }
+
+    protected $strictMode;
+
+    protected $safeLinksWhitelist = array(
+        'http://',
+        'https://',
+        'ftp://',
+        'ftps://',
+        'mailto:',
+        'tel:',
+        'data:image/png;base64,',
+        'data:image/gif;base64,',
+        'data:image/jpeg;base64,',
+        'irc:',
+        'ircs:',
+        'git:',
+        'ssh:',
+        'news:',
+        'steam:',
+    );
+
+    #
+    # Lines
+    #
+
+    protected $BlockTypes = array(
+        '#' => array('Header'),
+        '*' => array('Rule', 'List'),
+        '+' => array('List'),
+        '-' => array('SetextHeader', 'Table', 'Rule', 'List'),
+        '0' => array('List'),
+        '1' => array('List'),
+        '2' => array('List'),
+        '3' => array('List'),
+        '4' => array('List'),
+        '5' => array('List'),
+        '6' => array('List'),
+        '7' => array('List'),
+        '8' => array('List'),
+        '9' => array('List'),
+        ':' => array('Table'),
+        '<' => array('Comment', 'Markup'),
+        '=' => array('SetextHeader'),
+        '>' => array('Quote'),
+        '[' => array('Reference'),
+        '_' => array('Rule'),
+        '`' => array('FencedCode'),
+        '|' => array('Table'),
+        '~' => array('FencedCode'),
+    );
+
+    # ~
+
+    protected $unmarkedBlockTypes = array(
+        'Code',
+    );
+
+    #
+    # Blocks
+    #
+
+    protected function lines(array $lines)
+    {
+        return $this->elements($this->linesElements($lines));
+    }
+
+    protected function linesElements(array $lines)
+    {
+        $Elements = array();
+        $CurrentBlock = null;
+
+        foreach ($lines as $line)
+        {
+            if (chop($line) === '')
+            {
+                if (isset($CurrentBlock))
+                {
+                    $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted'])
+                        ? $CurrentBlock['interrupted'] + 1 : 1
+                    );
+                }
+
+                continue;
+            }
+
+            while (($beforeTab = strstr($line, "\t", true)) !== false)
+            {
+                $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4;
+
+                $line = $beforeTab
+                    . str_repeat(' ', $shortage)
+                    . substr($line, strlen($beforeTab) + 1)
+                ;
+            }
+
+            $indent = strspn($line, ' ');
+
+            $text = $indent > 0 ? substr($line, $indent) : $line;
+
+            # ~
+
+            $Line = array('body' => $line, 'indent' => $indent, 'text' => $text);
+
+            # ~
+
+            if (isset($CurrentBlock['continuable']))
+            {
+                $methodName = 'block' . $CurrentBlock['type'] . 'Continue';
+                $Block = $this->$methodName($Line, $CurrentBlock);
+
+                if (isset($Block))
+                {
+                    $CurrentBlock = $Block;
+
+                    continue;
+                }
+                else
+                {
+                    if ($this->isBlockCompletable($CurrentBlock['type']))
+                    {
+                        $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+                        $CurrentBlock = $this->$methodName($CurrentBlock);
+                    }
+                }
+            }
+
+            # ~
+
+            $marker = $text[0];
+
+            # ~
+
+            $blockTypes = $this->unmarkedBlockTypes;
+
+            if (isset($this->BlockTypes[$marker]))
+            {
+                foreach ($this->BlockTypes[$marker] as $blockType)
+                {
+                    $blockTypes []= $blockType;
+                }
+            }
+
+            #
+            # ~
+
+            foreach ($blockTypes as $blockType)
+            {
+                $Block = $this->{"block$blockType"}($Line, $CurrentBlock);
+
+                if (isset($Block))
+                {
+                    $Block['type'] = $blockType;
+
+                    if ( ! isset($Block['identified']))
+                    {
+                        if (isset($CurrentBlock))
+                        {
+                            $Elements[] = $this->extractElement($CurrentBlock);
+                        }
+
+                        $Block['identified'] = true;
+                    }
+
+                    if ($this->isBlockContinuable($blockType))
+                    {
+                        $Block['continuable'] = true;
+                    }
+
+                    $CurrentBlock = $Block;
+
+                    continue 2;
+                }
+            }
+
+            # ~
+
+            if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph')
+            {
+                $Block = $this->paragraphContinue($Line, $CurrentBlock);
+            }
+
+            if (isset($Block))
+            {
+                $CurrentBlock = $Block;
+            }
+            else
+            {
+                if (isset($CurrentBlock))
+                {
+                    $Elements[] = $this->extractElement($CurrentBlock);
+                }
+
+                $CurrentBlock = $this->paragraph($Line);
+
+                $CurrentBlock['identified'] = true;
+            }
+        }
+
+        # ~
+
+        if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type']))
+        {
+            $methodName = 'block' . $CurrentBlock['type'] . 'Complete';
+            $CurrentBlock = $this->$methodName($CurrentBlock);
+        }
+
+        # ~
+
+        if (isset($CurrentBlock))
+        {
+            $Elements[] = $this->extractElement($CurrentBlock);
+        }
+
+        # ~
+
+        return $Elements;
+    }
+
+    protected function extractElement(array $Component)
+    {
+        if ( ! isset($Component['element']))
+        {
+            if (isset($Component['markup']))
+            {
+                $Component['element'] = array('rawHtml' => $Component['markup']);
+            }
+            elseif (isset($Component['hidden']))
+            {
+                $Component['element'] = array();
+            }
+        }
+
+        return $Component['element'];
+    }
+
+    protected function isBlockContinuable($Type)
+    {
+        return method_exists($this, 'block' . $Type . 'Continue');
+    }
+
+    protected function isBlockCompletable($Type)
+    {
+        return method_exists($this, 'block' . $Type . 'Complete');
+    }
+
+    #
+    # Code
+
+    protected function blockCode($Line, $Block = null)
+    {
+        if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if ($Line['indent'] >= 4)
+        {
+            $text = substr($Line['body'], 4);
+
+            $Block = array(
+                'element' => array(
+                    'name' => 'pre',
+                    'element' => array(
+                        'name' => 'code',
+                        'text' => $text,
+                    ),
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockCodeContinue($Line, $Block)
+    {
+        if ($Line['indent'] >= 4)
+        {
+            if (isset($Block['interrupted']))
+            {
+                $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+                unset($Block['interrupted']);
+            }
+
+            $Block['element']['element']['text'] .= "\n";
+
+            $text = substr($Line['body'], 4);
+
+            $Block['element']['element']['text'] .= $text;
+
+            return $Block;
+        }
+    }
+
+    protected function blockCodeComplete($Block)
+    {
+        return $Block;
+    }
+
+    #
+    # Comment
+
+    protected function blockComment($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (strpos($Line['text'], '<!--') === 0)
+        {
+            $Block = array(
+                'element' => array(
+                    'rawHtml' => $Line['body'],
+                    'autobreak' => true,
+                ),
+            );
+
+            if (strpos($Line['text'], '-->') !== false)
+            {
+                $Block['closed'] = true;
+            }
+
+            return $Block;
+        }
+    }
+
+    protected function blockCommentContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']))
+        {
+            return;
+        }
+
+        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+        if (strpos($Line['text'], '-->') !== false)
+        {
+            $Block['closed'] = true;
+        }
+
+        return $Block;
+    }
+
+    #
+    # Fenced Code
+
+    protected function blockFencedCode($Line)
+    {
+        $marker = $Line['text'][0];
+
+        $openerLength = strspn($Line['text'], $marker);
+
+        if ($openerLength < 3)
+        {
+            return;
+        }
+
+        $infostring = trim(substr($Line['text'], $openerLength), "\t ");
+
+        if (strpos($infostring, '`') !== false)
+        {
+            return;
+        }
+
+        $Element = array(
+            'name' => 'code',
+            'text' => '',
+        );
+
+        if ($infostring !== '')
+        {
+            /**
+             * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes
+             * Every HTML element may have a class attribute specified.
+             * The attribute, if specified, must have a value that is a set
+             * of space-separated tokens representing the various classes
+             * that the element belongs to.
+             * [...]
+             * The space characters, for the purposes of this specification,
+             * are U+0020 SPACE, U+0009 CHARACTER TABULATION (tab),
+             * U+000A LINE FEED (LF), U+000C FORM FEED (FF), and
+             * U+000D CARRIAGE RETURN (CR).
+             */
+            $language = substr($infostring, 0, strcspn($infostring, " \t\n\f\r"));
+
+            $Element['attributes'] = array('class' => "language-$language");
+        }
+
+        $Block = array(
+            'char' => $marker,
+            'openerLength' => $openerLength,
+            'element' => array(
+                'name' => 'pre',
+                'element' => $Element,
+            ),
+        );
+
+        return $Block;
+    }
+
+    protected function blockFencedCodeContinue($Line, $Block)
+    {
+        if (isset($Block['complete']))
+        {
+            return;
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']);
+
+            unset($Block['interrupted']);
+        }
+
+        if (($len = strspn($Line['text'], $Block['char'])) >= $Block['openerLength']
+            and chop(substr($Line['text'], $len), ' ') === ''
+        ) {
+            $Block['element']['element']['text'] = substr($Block['element']['element']['text'], 1);
+
+            $Block['complete'] = true;
+
+            return $Block;
+        }
+
+        $Block['element']['element']['text'] .= "\n" . $Line['body'];
+
+        return $Block;
+    }
+
+    protected function blockFencedCodeComplete($Block)
+    {
+        return $Block;
+    }
+
+    #
+    # Header
+
+    protected function blockHeader($Line)
+    {
+        $level = strspn($Line['text'], '#');
+
+        if ($level > 6)
+        {
+            return;
+        }
+
+        $text = trim($Line['text'], '#');
+
+        if ($this->strictMode and isset($text[0]) and $text[0] !== ' ')
+        {
+            return;
+        }
+
+        $text = trim($text, ' ');
+
+        $Block = array(
+            'element' => array(
+                'name' => 'h' . $level,
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $text,
+                    'destination' => 'elements',
+                )
+            ),
+        );
+
+        return $Block;
+    }
+
+    #
+    # List
+
+    protected function blockList($Line, ?array $CurrentBlock = null)
+    {
+        list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]');
+
+        if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches))
+        {
+            $contentIndent = strlen($matches[2]);
+
+            if ($contentIndent >= 5)
+            {
+                $contentIndent -= 1;
+                $matches[1] = substr($matches[1], 0, -$contentIndent);
+                $matches[3] = str_repeat(' ', $contentIndent) . $matches[3];
+            }
+            elseif ($contentIndent === 0)
+            {
+                $matches[1] .= ' ';
+            }
+
+            $markerWithoutWhitespace = strstr($matches[1], ' ', true);
+
+            $Block = array(
+                'indent' => $Line['indent'],
+                'pattern' => $pattern,
+                'data' => array(
+                    'type' => $name,
+                    'marker' => $matches[1],
+                    'markerType' => ($name === 'ul' ? $markerWithoutWhitespace : substr($markerWithoutWhitespace, -1)),
+                ),
+                'element' => array(
+                    'name' => $name,
+                    'elements' => array(),
+                ),
+            );
+            $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/');
+
+            if ($name === 'ol')
+            {
+                $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0';
+
+                if ($listStart !== '1')
+                {
+                    if (
+                        isset($CurrentBlock)
+                        and $CurrentBlock['type'] === 'Paragraph'
+                        and ! isset($CurrentBlock['interrupted'])
+                    ) {
+                        return;
+                    }
+
+                    $Block['element']['attributes'] = array('start' => $listStart);
+                }
+            }
+
+            $Block['li'] = array(
+                'name' => 'li',
+                'handler' => array(
+                    'function' => 'li',
+                    'argument' => !empty($matches[3]) ? array($matches[3]) : array(),
+                    'destination' => 'elements'
+                )
+            );
+
+            $Block['element']['elements'] []= & $Block['li'];
+
+            return $Block;
+        }
+    }
+
+    protected function blockListContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument']))
+        {
+            return null;
+        }
+
+        $requiredIndent = ($Block['indent'] + strlen($Block['data']['marker']));
+
+        if ($Line['indent'] < $requiredIndent
+            and (
+                (
+                    $Block['data']['type'] === 'ol'
+                    and preg_match('/^[0-9]++'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+                ) or (
+                    $Block['data']['type'] === 'ul'
+                    and preg_match('/^'.$Block['data']['markerTypeRegex'].'(?:[ ]++(.*)|$)/', $Line['text'], $matches)
+                )
+            )
+        ) {
+            if (isset($Block['interrupted']))
+            {
+                $Block['li']['handler']['argument'] []= '';
+
+                $Block['loose'] = true;
+
+                unset($Block['interrupted']);
+            }
+
+            unset($Block['li']);
+
+            $text = isset($matches[1]) ? $matches[1] : '';
+
+            $Block['indent'] = $Line['indent'];
+
+            $Block['li'] = array(
+                'name' => 'li',
+                'handler' => array(
+                    'function' => 'li',
+                    'argument' => array($text),
+                    'destination' => 'elements'
+                )
+            );
+
+            $Block['element']['elements'] []= & $Block['li'];
+
+            return $Block;
+        }
+        elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line))
+        {
+            return null;
+        }
+
+        if ($Line['text'][0] === '[' and $this->blockReference($Line))
+        {
+            return $Block;
+        }
+
+        if ($Line['indent'] >= $requiredIndent)
+        {
+            if (isset($Block['interrupted']))
+            {
+                $Block['li']['handler']['argument'] []= '';
+
+                $Block['loose'] = true;
+
+                unset($Block['interrupted']);
+            }
+
+            $text = substr($Line['body'], $requiredIndent);
+
+            $Block['li']['handler']['argument'] []= $text;
+
+            return $Block;
+        }
+
+        if ( ! isset($Block['interrupted']))
+        {
+            $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']);
+
+            $Block['li']['handler']['argument'] []= $text;
+
+            return $Block;
+        }
+    }
+
+    protected function blockListComplete(array $Block)
+    {
+        if (isset($Block['loose']))
+        {
+            foreach ($Block['element']['elements'] as &$li)
+            {
+                if (end($li['handler']['argument']) !== '')
+                {
+                    $li['handler']['argument'] []= '';
+                }
+            }
+        }
+
+        return $Block;
+    }
+
+    #
+    # Quote
+
+    protected function blockQuote($Line)
+    {
+        if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'element' => array(
+                    'name' => 'blockquote',
+                    'handler' => array(
+                        'function' => 'linesElements',
+                        'argument' => (array) $matches[1],
+                        'destination' => 'elements',
+                    )
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockQuoteContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches))
+        {
+            $Block['element']['handler']['argument'] []= $matches[1];
+
+            return $Block;
+        }
+
+        if ( ! isset($Block['interrupted']))
+        {
+            $Block['element']['handler']['argument'] []= $Line['text'];
+
+            return $Block;
+        }
+    }
+
+    #
+    # Rule
+
+    protected function blockRule($Line)
+    {
+        $marker = $Line['text'][0];
+
+        if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '')
+        {
+            $Block = array(
+                'element' => array(
+                    'name' => 'hr',
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Setext
+
+    protected function blockSetextHeader($Line, ?array $Block = null)
+    {
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '')
+        {
+            $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2';
+
+            return $Block;
+        }
+    }
+
+    #
+    # Markup
+
+    protected function blockMarkup($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches))
+        {
+            $element = strtolower($matches[1]);
+
+            if (in_array($element, $this->textLevelElements))
+            {
+                return;
+            }
+
+            $Block = array(
+                'name' => $matches[1],
+                'element' => array(
+                    'rawHtml' => $Line['text'],
+                    'autobreak' => true,
+                ),
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockMarkupContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']) or isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        $Block['element']['rawHtml'] .= "\n" . $Line['body'];
+
+        return $Block;
+    }
+
+    #
+    # Reference
+
+    protected function blockReference($Line)
+    {
+        if (strpos($Line['text'], ']') !== false
+            and preg_match('/^\[(.+?)\]:[ ]*+<?(\S+?)>?(?:[ ]+["\'(](.+)["\')])?[ ]*+$/', $Line['text'], $matches)
+        ) {
+            $id = strtolower($matches[1]);
+
+            $Data = array(
+                'url' => $matches[2],
+                'title' => isset($matches[3]) ? $matches[3] : null,
+            );
+
+            $this->DefinitionData['Reference'][$id] = $Data;
+
+            $Block = array(
+                'element' => array(),
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Table
+
+    protected function blockTable($Line, ?array $Block = null)
+    {
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if (
+            strpos($Block['element']['handler']['argument'], '|') === false
+            and strpos($Line['text'], '|') === false
+            and strpos($Line['text'], ':') === false
+            or strpos($Block['element']['handler']['argument'], "\n") !== false
+        ) {
+            return;
+        }
+
+        if (chop($Line['text'], ' -:|') !== '')
+        {
+            return;
+        }
+
+        $alignments = array();
+
+        $divider = $Line['text'];
+
+        $divider = trim($divider);
+        $divider = trim($divider, '|');
+
+        $dividerCells = explode('|', $divider);
+
+        foreach ($dividerCells as $dividerCell)
+        {
+            $dividerCell = trim($dividerCell);
+
+            if ($dividerCell === '')
+            {
+                return;
+            }
+
+            $alignment = null;
+
+            if ($dividerCell[0] === ':')
+            {
+                $alignment = 'left';
+            }
+
+            if (substr($dividerCell, - 1) === ':')
+            {
+                $alignment = $alignment === 'left' ? 'center' : 'right';
+            }
+
+            $alignments []= $alignment;
+        }
+
+        # ~
+
+        $HeaderElements = array();
+
+        $header = $Block['element']['handler']['argument'];
+
+        $header = trim($header);
+        $header = trim($header, '|');
+
+        $headerCells = explode('|', $header);
+
+        if (count($headerCells) !== count($alignments))
+        {
+            return;
+        }
+
+        foreach ($headerCells as $index => $headerCell)
+        {
+            $headerCell = trim($headerCell);
+
+            $HeaderElement = array(
+                'name' => 'th',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $headerCell,
+                    'destination' => 'elements',
+                )
+            );
+
+            if (isset($alignments[$index]))
+            {
+                $alignment = $alignments[$index];
+
+                $HeaderElement['attributes'] = array(
+                    'style' => "text-align: $alignment;",
+                );
+            }
+
+            $HeaderElements []= $HeaderElement;
+        }
+
+        # ~
+
+        $Block = array(
+            'alignments' => $alignments,
+            'identified' => true,
+            'element' => array(
+                'name' => 'table',
+                'elements' => array(),
+            ),
+        );
+
+        $Block['element']['elements'] []= array(
+            'name' => 'thead',
+        );
+
+        $Block['element']['elements'] []= array(
+            'name' => 'tbody',
+            'elements' => array(),
+        );
+
+        $Block['element']['elements'][0]['elements'] []= array(
+            'name' => 'tr',
+            'elements' => $HeaderElements,
+        );
+
+        return $Block;
+    }
+
+    protected function blockTableContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|'))
+        {
+            $Elements = array();
+
+            $row = $Line['text'];
+
+            $row = trim($row);
+            $row = trim($row, '|');
+
+            preg_match_all('/(?:(\\\\[|])|[^|`]|`[^`]++`|`)++/', $row, $matches);
+
+            $cells = array_slice($matches[0], 0, count($Block['alignments']));
+
+            foreach ($cells as $index => $cell)
+            {
+                $cell = trim($cell);
+
+                $Element = array(
+                    'name' => 'td',
+                    'handler' => array(
+                        'function' => 'lineElements',
+                        'argument' => $cell,
+                        'destination' => 'elements',
+                    )
+                );
+
+                if (isset($Block['alignments'][$index]))
+                {
+                    $Element['attributes'] = array(
+                        'style' => 'text-align: ' . $Block['alignments'][$index] . ';',
+                    );
+                }
+
+                $Elements []= $Element;
+            }
+
+            $Element = array(
+                'name' => 'tr',
+                'elements' => $Elements,
+            );
+
+            $Block['element']['elements'][1]['elements'] []= $Element;
+
+            return $Block;
+        }
+    }
+
+    #
+    # ~
+    #
+
+    protected function paragraph($Line)
+    {
+        return array(
+            'type' => 'Paragraph',
+            'element' => array(
+                'name' => 'p',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $Line['text'],
+                    'destination' => 'elements',
+                ),
+            ),
+        );
+    }
+
+    protected function paragraphContinue($Line, array $Block)
+    {
+        if (isset($Block['interrupted']))
+        {
+            return;
+        }
+
+        $Block['element']['handler']['argument'] .= "\n".$Line['text'];
+
+        return $Block;
+    }
+
+    #
+    # Inline Elements
+    #
+
+    protected $InlineTypes = array(
+        '!' => array('Image'),
+        '&' => array('SpecialCharacter'),
+        '*' => array('Emphasis'),
+        ':' => array('Url'),
+        '<' => array('UrlTag', 'EmailTag', 'Markup'),
+        '[' => array('Link'),
+        '_' => array('Emphasis'),
+        '`' => array('Code'),
+        '~' => array('Strikethrough'),
+        '\\' => array('EscapeSequence'),
+    );
+
+    # ~
+
+    protected $inlineMarkerList = '!*_&[:<`~\\';
+
+    #
+    # ~
+    #
+
+    public function line($text, $nonNestables = array())
+    {
+        return $this->elements($this->lineElements($text, $nonNestables));
+    }
+
+    protected function lineElements($text, $nonNestables = array())
+    {
+        # standardize line breaks
+        $text = str_replace(array("\r\n", "\r"), "\n", $text);
+
+        $Elements = array();
+
+        $nonNestables = (empty($nonNestables)
+            ? array()
+            : array_combine($nonNestables, $nonNestables)
+        );
+
+        # $excerpt is based on the first occurrence of a marker
+
+        while ($excerpt = strpbrk($text, $this->inlineMarkerList))
+        {
+            $marker = $excerpt[0];
+
+            $markerPosition = strlen($text) - strlen($excerpt);
+
+            $Excerpt = array('text' => $excerpt, 'context' => $text);
+
+            foreach ($this->InlineTypes[$marker] as $inlineType)
+            {
+                # check to see if the current inline type is nestable in the current context
+
+                if (isset($nonNestables[$inlineType]))
+                {
+                    continue;
+                }
+
+                $Inline = $this->{"inline$inlineType"}($Excerpt);
+
+                if ( ! isset($Inline))
+                {
+                    continue;
+                }
+
+                # makes sure that the inline belongs to "our" marker
+
+                if (isset($Inline['position']) and $Inline['position'] > $markerPosition)
+                {
+                    continue;
+                }
+
+                # sets a default inline position
+
+                if ( ! isset($Inline['position']))
+                {
+                    $Inline['position'] = $markerPosition;
+                }
+
+                # cause the new element to 'inherit' our non nestables
+
+
+                $Inline['element']['nonNestables'] = isset($Inline['element']['nonNestables'])
+                    ? array_merge($Inline['element']['nonNestables'], $nonNestables)
+                    : $nonNestables
+                ;
+
+                # the text that comes before the inline
+                $unmarkedText = substr($text, 0, $Inline['position']);
+
+                # compile the unmarked text
+                $InlineText = $this->inlineText($unmarkedText);
+                $Elements[] = $InlineText['element'];
+
+                # compile the inline
+                $Elements[] = $this->extractElement($Inline);
+
+                # remove the examined text
+                $text = substr($text, $Inline['position'] + $Inline['extent']);
+
+                continue 2;
+            }
+
+            # the marker does not belong to an inline
+
+            $unmarkedText = substr($text, 0, $markerPosition + 1);
+
+            $InlineText = $this->inlineText($unmarkedText);
+            $Elements[] = $InlineText['element'];
+
+            $text = substr($text, $markerPosition + 1);
+        }
+
+        $InlineText = $this->inlineText($text);
+        $Elements[] = $InlineText['element'];
+
+        foreach ($Elements as &$Element)
+        {
+            if ( ! isset($Element['autobreak']))
+            {
+                $Element['autobreak'] = false;
+            }
+        }
+
+        return $Elements;
+    }
+
+    #
+    # ~
+    #
+
+    protected function inlineText($text)
+    {
+        $Inline = array(
+            'extent' => strlen($text),
+            'element' => array(),
+        );
+
+        $Inline['element']['elements'] = self::pregReplaceElements(
+            $this->breaksEnabled ? '/[ ]*+\n/' : '/(?:[ ]*+\\\\|[ ]{2,}+)\n/',
+            array(
+                array('name' => 'br'),
+                array('text' => "\n"),
+            ),
+            $text
+        );
+
+        return $Inline;
+    }
+
+    protected function inlineCode($Excerpt)
+    {
+        $marker = $Excerpt['text'][0];
+
+        if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?<!['.$marker.'])\1(?!'.$marker.')/s', $Excerpt['text'], $matches))
+        {
+            $text = $matches[2];
+            $text = preg_replace('/[ ]*+\n/', ' ', $text);
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'code',
+                    'text' => $text,
+                ),
+            );
+        }
+    }
+
+    protected function inlineEmailTag($Excerpt)
+    {
+        $hostnameLabel = '[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?';
+
+        $commonMarkEmail = '[a-zA-Z0-9.!#$%&\'*+\/=?^_`{|}~-]++@'
+            . $hostnameLabel . '(?:\.' . $hostnameLabel . ')*';
+
+        if (strpos($Excerpt['text'], '>') !== false
+            and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches)
+        ){
+            $url = $matches[1];
+
+            if ( ! isset($matches[2]))
+            {
+                $url = "mailto:$url";
+            }
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $matches[1],
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+        }
+    }
+
+    protected function inlineEmphasis($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]))
+        {
+            return;
+        }
+
+        $marker = $Excerpt['text'][0];
+
+        if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches))
+        {
+            $emphasis = 'strong';
+        }
+        elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches))
+        {
+            $emphasis = 'em';
+        }
+        else
+        {
+            return;
+        }
+
+        return array(
+            'extent' => strlen($matches[0]),
+            'element' => array(
+                'name' => $emphasis,
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $matches[1],
+                    'destination' => 'elements',
+                )
+            ),
+        );
+    }
+
+    protected function inlineEscapeSequence($Excerpt)
+    {
+        if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters))
+        {
+            return array(
+                'element' => array('rawHtml' => $Excerpt['text'][1]),
+                'extent' => 2,
+            );
+        }
+    }
+
+    protected function inlineImage($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[')
+        {
+            return;
+        }
+
+        $Excerpt['text']= substr($Excerpt['text'], 1);
+
+        $Link = $this->inlineLink($Excerpt);
+
+        if ($Link === null)
+        {
+            return;
+        }
+
+        $Inline = array(
+            'extent' => $Link['extent'] + 1,
+            'element' => array(
+                'name' => 'img',
+                'attributes' => array(
+                    'src' => $Link['element']['attributes']['href'],
+                    'alt' => $Link['element']['handler']['argument'],
+                ),
+                'autobreak' => true,
+            ),
+        );
+
+        $Inline['element']['attributes'] += $Link['element']['attributes'];
+
+        unset($Inline['element']['attributes']['href']);
+
+        return $Inline;
+    }
+
+    protected function inlineLink($Excerpt)
+    {
+        $Element = array(
+            'name' => 'a',
+            'handler' => array(
+                'function' => 'lineElements',
+                'argument' => null,
+                'destination' => 'elements',
+            ),
+            'nonNestables' => array('Url', 'Link'),
+            'attributes' => array(
+                'href' => null,
+                'title' => null,
+            ),
+        );
+
+        $extent = 0;
+
+        $remainder = $Excerpt['text'];
+
+        if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
+        {
+            $Element['handler']['argument'] = $matches[1];
+
+            $extent += strlen($matches[0]);
+
+            $remainder = substr($remainder, $extent);
+        }
+        else
+        {
+            return;
+        }
+
+        if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches))
+        {
+            $Element['attributes']['href'] = $matches[1];
+
+            if (isset($matches[2]))
+            {
+                $Element['attributes']['title'] = substr($matches[2], 1, - 1);
+            }
+
+            $extent += strlen($matches[0]);
+        }
+        else
+        {
+            if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
+            {
+                $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument'];
+                $definition = strtolower($definition);
+
+                $extent += strlen($matches[0]);
+            }
+            else
+            {
+                $definition = strtolower($Element['handler']['argument']);
+            }
+
+            if ( ! isset($this->DefinitionData['Reference'][$definition]))
+            {
+                return;
+            }
+
+            $Definition = $this->DefinitionData['Reference'][$definition];
+
+            $Element['attributes']['href'] = $Definition['url'];
+            $Element['attributes']['title'] = $Definition['title'];
+        }
+
+        return array(
+            'extent' => $extent,
+            'element' => $Element,
+        );
+    }
+
+    protected function inlineMarkup($Excerpt)
+    {
+        if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false)
+        {
+            return;
+        }
+
+        if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'element' => array('rawHtml' => $matches[0]),
+                'extent' => strlen($matches[0]),
+            );
+        }
+
+        if ($Excerpt['text'][1] === '!' and preg_match('/^<!---?[^>-](?:-?+[^-])*-->/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'element' => array('rawHtml' => $matches[0]),
+                'extent' => strlen($matches[0]),
+            );
+        }
+
+        if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches))
+        {
+            return array(
+                'element' => array('rawHtml' => $matches[0]),
+                'extent' => strlen($matches[0]),
+            );
+        }
+    }
+
+    protected function inlineSpecialCharacter($Excerpt)
+    {
+        if (substr($Excerpt['text'], 1, 1) !== ' ' and strpos($Excerpt['text'], ';') !== false
+            and preg_match('/^&(#?+[0-9a-zA-Z]++);/', $Excerpt['text'], $matches)
+        ) {
+            return array(
+                'element' => array('rawHtml' => '&' . $matches[1] . ';'),
+                'extent' => strlen($matches[0]),
+            );
+        }
+
+        return;
+    }
+
+    protected function inlineStrikethrough($Excerpt)
+    {
+        if ( ! isset($Excerpt['text'][1]))
+        {
+            return;
+        }
+
+        if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches))
+        {
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'del',
+                    'handler' => array(
+                        'function' => 'lineElements',
+                        'argument' => $matches[1],
+                        'destination' => 'elements',
+                    )
+                ),
+            );
+        }
+    }
+
+    protected function inlineUrl($Excerpt)
+    {
+        if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/')
+        {
+            return;
+        }
+
+        if (strpos($Excerpt['context'], 'http') !== false
+            and preg_match('/\bhttps?+:[\/]{2}[^\s<]+\b\/*+/ui', $Excerpt['context'], $matches, PREG_OFFSET_CAPTURE)
+        ) {
+            $url = $matches[0][0];
+
+            $Inline = array(
+                'extent' => strlen($matches[0][0]),
+                'position' => $matches[0][1],
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $url,
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+
+            return $Inline;
+        }
+    }
+
+    protected function inlineUrlTag($Excerpt)
+    {
+        if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches))
+        {
+            $url = $matches[1];
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => array(
+                    'name' => 'a',
+                    'text' => $url,
+                    'attributes' => array(
+                        'href' => $url,
+                    ),
+                ),
+            );
+        }
+    }
+
+    # ~
+
+    protected function unmarkedText($text)
+    {
+        $Inline = $this->inlineText($text);
+        return $this->element($Inline['element']);
+    }
+
+    #
+    # Handlers
+    #
+
+    protected function handle(array $Element)
+    {
+        if (isset($Element['handler']))
+        {
+            if (!isset($Element['nonNestables']))
+            {
+                $Element['nonNestables'] = array();
+            }
+
+            if (is_string($Element['handler']))
+            {
+                $function = $Element['handler'];
+                $argument = $Element['text'];
+                unset($Element['text']);
+                $destination = 'rawHtml';
+            }
+            else
+            {
+                $function = $Element['handler']['function'];
+                $argument = $Element['handler']['argument'];
+                $destination = $Element['handler']['destination'];
+            }
+
+            $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']);
+
+            if ($destination === 'handler')
+            {
+                $Element = $this->handle($Element);
+            }
+
+            unset($Element['handler']);
+        }
+
+        return $Element;
+    }
+
+    protected function handleElementRecursive(array $Element)
+    {
+        return $this->elementApplyRecursive(array($this, 'handle'), $Element);
+    }
+
+    protected function handleElementsRecursive(array $Elements)
+    {
+        return $this->elementsApplyRecursive(array($this, 'handle'), $Elements);
+    }
+
+    protected function elementApplyRecursive($closure, array $Element)
+    {
+        $Element = call_user_func($closure, $Element);
+
+        if (isset($Element['elements']))
+        {
+            $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']);
+        }
+        elseif (isset($Element['element']))
+        {
+            $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']);
+        }
+
+        return $Element;
+    }
+
+    protected function elementApplyRecursiveDepthFirst($closure, array $Element)
+    {
+        if (isset($Element['elements']))
+        {
+            $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']);
+        }
+        elseif (isset($Element['element']))
+        {
+            $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']);
+        }
+
+        $Element = call_user_func($closure, $Element);
+
+        return $Element;
+    }
+
+    protected function elementsApplyRecursive($closure, array $Elements)
+    {
+        foreach ($Elements as &$Element)
+        {
+            $Element = $this->elementApplyRecursive($closure, $Element);
+        }
+
+        return $Elements;
+    }
+
+    protected function elementsApplyRecursiveDepthFirst($closure, array $Elements)
+    {
+        foreach ($Elements as &$Element)
+        {
+            $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element);
+        }
+
+        return $Elements;
+    }
+
+    protected function element(array $Element)
+    {
+        if ($this->safeMode)
+        {
+            $Element = $this->sanitiseElement($Element);
+        }
+
+        # identity map if element has no handler
+        $Element = $this->handle($Element);
+
+        $hasName = isset($Element['name']);
+
+        $markup = '';
+
+        if ($hasName)
+        {
+            $markup .= '<' . $Element['name'];
+
+            if (isset($Element['attributes']))
+            {
+                foreach ($Element['attributes'] as $name => $value)
+                {
+                    if ($value === null)
+                    {
+                        continue;
+                    }
+
+                    $markup .= " $name=\"".self::escape($value).'"';
+                }
+            }
+        }
+
+        $permitRawHtml = false;
+
+        if (isset($Element['text']))
+        {
+            $text = $Element['text'];
+        }
+        // very strongly consider an alternative if you're writing an
+        // extension
+        elseif (isset($Element['rawHtml']))
+        {
+            $text = $Element['rawHtml'];
+
+            $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode'];
+            $permitRawHtml = !$this->safeMode || $allowRawHtmlInSafeMode;
+        }
+
+        $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']);
+
+        if ($hasContent)
+        {
+            $markup .= $hasName ? '>' : '';
+
+            if (isset($Element['elements']))
+            {
+                $markup .= $this->elements($Element['elements']);
+            }
+            elseif (isset($Element['element']))
+            {
+                $markup .= $this->element($Element['element']);
+            }
+            else
+            {
+                if (!$permitRawHtml)
+                {
+                    $markup .= self::escape($text, true);
+                }
+                else
+                {
+                    $markup .= $text;
+                }
+            }
+
+            $markup .= $hasName ? '</' . $Element['name'] . '>' : '';
+        }
+        elseif ($hasName)
+        {
+            $markup .= ' />';
+        }
+
+        return $markup;
+    }
+
+    protected function elements(array $Elements)
+    {
+        $markup = '';
+
+        $autoBreak = true;
+
+        foreach ($Elements as $Element)
+        {
+            if (empty($Element))
+            {
+                continue;
+            }
+
+            $autoBreakNext = (isset($Element['autobreak'])
+                ? $Element['autobreak'] : isset($Element['name'])
+            );
+            // (autobreak === false) covers both sides of an element
+            $autoBreak = !$autoBreak ? $autoBreak : $autoBreakNext;
+
+            $markup .= ($autoBreak ? "\n" : '') . $this->element($Element);
+            $autoBreak = $autoBreakNext;
+        }
+
+        $markup .= $autoBreak ? "\n" : '';
+
+        return $markup;
+    }
+
+    # ~
+
+    protected function li($lines)
+    {
+        $Elements = $this->linesElements($lines);
+
+        if ( ! in_array('', $lines)
+            and isset($Elements[0]) and isset($Elements[0]['name'])
+            and $Elements[0]['name'] === 'p'
+        ) {
+            unset($Elements[0]['name']);
+        }
+
+        return $Elements;
+    }
+
+    #
+    # AST Convenience
+    #
+
+    /**
+     * Replace occurrences $regexp with $Elements in $text. Return an array of
+     * elements representing the replacement.
+     */
+    protected static function pregReplaceElements($regexp, $Elements, $text)
+    {
+        $newElements = array();
+
+        while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE))
+        {
+            $offset = $matches[0][1];
+            $before = substr($text, 0, $offset);
+            $after = substr($text, $offset + strlen($matches[0][0]));
+
+            $newElements[] = array('text' => $before);
+
+            foreach ($Elements as $Element)
+            {
+                $newElements[] = $Element;
+            }
+
+            $text = $after;
+        }
+
+        $newElements[] = array('text' => $text);
+
+        return $newElements;
+    }
+
+    #
+    # Deprecated Methods
+    #
+
+    function parse($text)
+    {
+        $markup = $this->text($text);
+
+        return $markup;
+    }
+
+    protected function sanitiseElement(array $Element)
+    {
+        static $goodAttribute = '/^[a-zA-Z0-9][a-zA-Z0-9-_]*+$/';
+        static $safeUrlNameToAtt  = array(
+            'a'   => 'href',
+            'img' => 'src',
+        );
+
+        if ( ! isset($Element['name']))
+        {
+            unset($Element['attributes']);
+            return $Element;
+        }
+
+        if (isset($safeUrlNameToAtt[$Element['name']]))
+        {
+            $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]);
+        }
+
+        if ( ! empty($Element['attributes']))
+        {
+            foreach ($Element['attributes'] as $att => $val)
+            {
+                # filter out badly parsed attribute
+                if ( ! preg_match($goodAttribute, $att))
+                {
+                    unset($Element['attributes'][$att]);
+                }
+                # dump onevent attribute
+                elseif (self::striAtStart($att, 'on'))
+                {
+                    unset($Element['attributes'][$att]);
+                }
+            }
+        }
+
+        return $Element;
+    }
+
+    protected function filterUnsafeUrlInAttribute(array $Element, $attribute)
+    {
+        foreach ($this->safeLinksWhitelist as $scheme)
+        {
+            if (self::striAtStart($Element['attributes'][$attribute], $scheme))
+            {
+                return $Element;
+            }
+        }
+
+        $Element['attributes'][$attribute] = str_replace(':', '%3A', $Element['attributes'][$attribute]);
+
+        return $Element;
+    }
+
+    #
+    # Static Methods
+    #
+
+    protected static function escape($text, $allowQuotes = false)
+    {
+        return htmlspecialchars($text, $allowQuotes ? ENT_NOQUOTES : ENT_QUOTES, 'UTF-8');
+    }
+
+    protected static function striAtStart($string, $needle)
+    {
+        $len = strlen($needle);
+
+        if ($len > strlen($string))
+        {
+            return false;
+        }
+        else
+        {
+            return strtolower(substr($string, 0, $len)) === strtolower($needle);
+        }
+    }
+
+    static function instance($name = 'default')
+    {
+        if (isset(self::$instances[$name]))
+        {
+            return self::$instances[$name];
+        }
+
+        $instance = new static();
+
+        self::$instances[$name] = $instance;
+
+        return $instance;
+    }
+
+    private static $instances = array();
+
+    #
+    # Fields
+    #
+
+    protected $DefinitionData;
+
+    #
+    # Read-Only
+
+    protected $specialCharacters = array(
+        '\\', '`', '*', '_', '{', '}', '[', ']', '(', ')', '>', '#', '+', '-', '.', '!', '|', '~'
+    );
+
+    protected $StrongRegex = array(
+        '*' => '/^[*]{2}((?:\\\\\*|[^*]|[*][^*]*+[*])+?)[*]{2}(?![*])/s',
+        '_' => '/^__((?:\\\\_|[^_]|_[^_]*+_)+?)__(?!_)/us',
+    );
+
+    protected $EmRegex = array(
+        '*' => '/^[*]((?:\\\\\*|[^*]|[*][*][^*]+?[*][*])+?)[*](?![*])/s',
+        '_' => '/^_((?:\\\\_|[^_]|__[^_]*__)+?)_(?!_)\b/us',
+    );
+
+    protected $regexHtmlAttribute = '[a-zA-Z_:][\w:.-]*+(?:\s*+=\s*+(?:[^"\'=<>`\s]+|"[^"]*+"|\'[^\']*+\'))?+';
+
+    protected $voidElements = array(
+        'area', 'base', 'br', 'col', 'command', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source',
+    );
+
+    protected $textLevelElements = array(
+        'a', 'br', 'bdo', 'abbr', 'blink', 'nextid', 'acronym', 'basefont',
+        'b', 'em', 'big', 'cite', 'small', 'spacer', 'listing',
+        'i', 'rp', 'del', 'code',          'strike', 'marquee',
+        'q', 'rt', 'ins', 'font',          'strong',
+        's', 'tt', 'kbd', 'mark',
+        'u', 'xm', 'sub', 'nobr',
+                   'sup', 'ruby',
+                   'var', 'span',
+                   'wbr', 'time',
+    );
+}

+ 686 - 0
vendor/ParsedownExtra.php

@@ -0,0 +1,686 @@
+<?php
+
+#
+#
+# Parsedown Extra
+# https://github.com/erusev/parsedown-extra
+#
+# (c) Emanuil Rusev
+# http://erusev.com
+#
+# For the full license information, view the LICENSE file that was distributed
+# with this source code.
+#
+#
+
+class ParsedownExtra extends Parsedown
+{
+    # ~
+
+    const version = '0.8.0';
+
+    # ~
+
+    function __construct()
+    {
+        if (version_compare(parent::version, '1.7.1') < 0)
+        {
+            throw new Exception('ParsedownExtra requires a later version of Parsedown');
+        }
+
+        $this->BlockTypes[':'] []= 'DefinitionList';
+        $this->BlockTypes['*'] []= 'Abbreviation';
+
+        # identify footnote definitions before reference definitions
+        array_unshift($this->BlockTypes['['], 'Footnote');
+
+        # identify footnote markers before before links
+        array_unshift($this->InlineTypes['['], 'FootnoteMarker');
+    }
+
+    #
+    # ~
+
+    function text($text)
+    {
+        $Elements = $this->textElements($text);
+
+        # convert to markup
+        $markup = $this->elements($Elements);
+
+        # trim line breaks
+        $markup = trim($markup, "\n");
+
+        # merge consecutive dl elements
+
+        $markup = preg_replace('/<\/dl>\s+<dl>\s+/', '', $markup);
+
+        # add footnotes
+
+        if (isset($this->DefinitionData['Footnote']))
+        {
+            $Element = $this->buildFootnoteElement();
+
+            $markup .= "\n" . $this->element($Element);
+        }
+
+        return $markup;
+    }
+
+    #
+    # Blocks
+    #
+
+    #
+    # Abbreviation
+
+    protected function blockAbbreviation($Line)
+    {
+        if (preg_match('/^\*\[(.+?)\]:[ ]*(.+?)[ ]*$/', $Line['text'], $matches))
+        {
+            $this->DefinitionData['Abbreviation'][$matches[1]] = $matches[2];
+
+            $Block = array(
+                'hidden' => true,
+            );
+
+            return $Block;
+        }
+    }
+
+    #
+    # Footnote
+
+    protected function blockFootnote($Line)
+    {
+        if (preg_match('/^\[\^(.+?)\]:[ ]?(.*)$/', $Line['text'], $matches))
+        {
+            $Block = array(
+                'label' => $matches[1],
+                'text' => $matches[2],
+                'hidden' => true,
+            );
+
+            return $Block;
+        }
+    }
+
+    protected function blockFootnoteContinue($Line, $Block)
+    {
+        if ($Line['text'][0] === '[' and preg_match('/^\[\^(.+?)\]:/', $Line['text']))
+        {
+            return;
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            if ($Line['indent'] >= 4)
+            {
+                $Block['text'] .= "\n\n" . $Line['text'];
+
+                return $Block;
+            }
+        }
+        else
+        {
+            $Block['text'] .= "\n" . $Line['text'];
+
+            return $Block;
+        }
+    }
+
+    protected function blockFootnoteComplete($Block)
+    {
+        $this->DefinitionData['Footnote'][$Block['label']] = array(
+            'text' => $Block['text'],
+            'count' => null,
+            'number' => null,
+        );
+
+        return $Block;
+    }
+
+    #
+    # Definition List
+
+    protected function blockDefinitionList($Line, $Block)
+    {
+        if ( ! isset($Block) or $Block['type'] !== 'Paragraph')
+        {
+            return;
+        }
+
+        $Element = array(
+            'name' => 'dl',
+            'elements' => array(),
+        );
+
+        $terms = explode("\n", $Block['element']['handler']['argument']);
+
+        foreach ($terms as $term)
+        {
+            $Element['elements'] []= array(
+                'name' => 'dt',
+                'handler' => array(
+                    'function' => 'lineElements',
+                    'argument' => $term,
+                    'destination' => 'elements'
+                ),
+            );
+        }
+
+        $Block['element'] = $Element;
+
+        $Block = $this->addDdElement($Line, $Block);
+
+        return $Block;
+    }
+
+    protected function blockDefinitionListContinue($Line, array $Block)
+    {
+        if ($Line['text'][0] === ':')
+        {
+            $Block = $this->addDdElement($Line, $Block);
+
+            return $Block;
+        }
+        else
+        {
+            if (isset($Block['interrupted']) and $Line['indent'] === 0)
+            {
+                return;
+            }
+
+            if (isset($Block['interrupted']))
+            {
+                $Block['dd']['handler']['function'] = 'textElements';
+                $Block['dd']['handler']['argument'] .= "\n\n";
+
+                $Block['dd']['handler']['destination'] = 'elements';
+
+                unset($Block['interrupted']);
+            }
+
+            $text = substr($Line['body'], min($Line['indent'], 4));
+
+            $Block['dd']['handler']['argument'] .= "\n" . $text;
+
+            return $Block;
+        }
+    }
+
+    #
+    # Header
+
+    protected function blockHeader($Line)
+    {
+        $Block = parent::blockHeader($Line);
+
+        if ($Block !== null && preg_match('/[ #]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
+        {
+            $attributeString = $matches[1][0];
+
+            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Markup
+
+    protected function blockMarkup($Line)
+    {
+        if ($this->markupEscaped or $this->safeMode)
+        {
+            return;
+        }
+
+        if (preg_match('/^<(\w[\w-]*)(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*(\/)?>/', $Line['text'], $matches))
+        {
+            $element = strtolower($matches[1]);
+
+            if (in_array($element, $this->textLevelElements))
+            {
+                return;
+            }
+
+            $Block = array(
+                'name' => $matches[1],
+                'depth' => 0,
+                'element' => array(
+                    'rawHtml' => $Line['text'],
+                    'autobreak' => true,
+                ),
+            );
+
+            $length = strlen($matches[0]);
+            $remainder = substr($Line['text'], $length);
+
+            if (trim($remainder) === '')
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    $Block['closed'] = true;
+                    $Block['void'] = true;
+                }
+            }
+            else
+            {
+                if (isset($matches[2]) or in_array($matches[1], $this->voidElements))
+                {
+                    return;
+                }
+                if (preg_match('/<\/'.$matches[1].'>[ ]*$/i', $remainder))
+                {
+                    $Block['closed'] = true;
+                }
+            }
+
+            return $Block;
+        }
+    }
+
+    protected function blockMarkupContinue($Line, array $Block)
+    {
+        if (isset($Block['closed']))
+        {
+            return;
+        }
+
+        if (preg_match('/^<'.$Block['name'].'(?:[ ]*'.$this->regexHtmlAttribute.')*[ ]*>/i', $Line['text'])) # open
+        {
+            $Block['depth'] ++;
+        }
+
+        if (preg_match('/(.*?)<\/'.$Block['name'].'>[ ]*$/i', $Line['text'], $matches)) # close
+        {
+            if ($Block['depth'] > 0)
+            {
+                $Block['depth'] --;
+            }
+            else
+            {
+                $Block['closed'] = true;
+            }
+        }
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['element']['rawHtml'] .= "\n";
+            unset($Block['interrupted']);
+        }
+
+        $Block['element']['rawHtml'] .= "\n".$Line['body'];
+
+        return $Block;
+    }
+
+    protected function blockMarkupComplete($Block)
+    {
+        if ( ! isset($Block['void']))
+        {
+            $Block['element']['rawHtml'] = $this->processTag($Block['element']['rawHtml']);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Setext
+
+    protected function blockSetextHeader($Line, ?array $Block = null)
+    {
+        $Block = parent::blockSetextHeader($Line, $Block);
+
+        if ($Block !== null && preg_match('/[ ]*{('.$this->regexAttribute.'+)}[ ]*$/', $Block['element']['handler']['argument'], $matches, PREG_OFFSET_CAPTURE))
+        {
+            $attributeString = $matches[1][0];
+
+            $Block['element']['attributes'] = $this->parseAttributeData($attributeString);
+
+            $Block['element']['handler']['argument'] = substr($Block['element']['handler']['argument'], 0, $matches[0][1]);
+        }
+
+        return $Block;
+    }
+
+    #
+    # Inline Elements
+    #
+
+    #
+    # Footnote Marker
+
+    protected function inlineFootnoteMarker($Excerpt)
+    {
+        if (preg_match('/^\[\^(.+?)\]/', $Excerpt['text'], $matches))
+        {
+            $name = $matches[1];
+
+            if ( ! isset($this->DefinitionData['Footnote'][$name]))
+            {
+                return;
+            }
+
+            $this->DefinitionData['Footnote'][$name]['count'] ++;
+
+            if ( ! isset($this->DefinitionData['Footnote'][$name]['number']))
+            {
+                $this->DefinitionData['Footnote'][$name]['number'] = ++ $this->footnoteCount; # » &
+            }
+
+            $Element = array(
+                'name' => 'sup',
+                'attributes' => array('id' => 'fnref'.$this->DefinitionData['Footnote'][$name]['count'].':'.$name),
+                'element' => array(
+                    'name' => 'a',
+                    'attributes' => array('href' => '#fn:'.$name, 'class' => 'footnote-ref'),
+                    'text' => $this->DefinitionData['Footnote'][$name]['number'],
+                ),
+            );
+
+            return array(
+                'extent' => strlen($matches[0]),
+                'element' => $Element,
+            );
+        }
+    }
+
+    private $footnoteCount = 0;
+
+    #
+    # Link
+
+    protected function inlineLink($Excerpt)
+    {
+        $Link = parent::inlineLink($Excerpt);
+
+        $remainder = $Link !== null ? substr($Excerpt['text'], $Link['extent']) : '';
+
+        if (preg_match('/^[ ]*{('.$this->regexAttribute.'+)}/', $remainder, $matches))
+        {
+            $Link['element']['attributes'] += $this->parseAttributeData($matches[1]);
+
+            $Link['extent'] += strlen($matches[0]);
+        }
+
+        return $Link;
+    }
+
+    #
+    # ~
+    #
+
+    private $currentAbreviation;
+    private $currentMeaning;
+
+    protected function insertAbreviation(array $Element)
+    {
+        if (isset($Element['text']))
+        {
+            $Element['elements'] = self::pregReplaceElements(
+                '/\b'.preg_quote($this->currentAbreviation, '/').'\b/',
+                array(
+                    array(
+                        'name' => 'abbr',
+                        'attributes' => array(
+                            'title' => $this->currentMeaning,
+                        ),
+                        'text' => $this->currentAbreviation,
+                    )
+                ),
+                $Element['text']
+            );
+
+            unset($Element['text']);
+        }
+
+        return $Element;
+    }
+
+    protected function inlineText($text)
+    {
+        $Inline = parent::inlineText($text);
+
+        if (isset($this->DefinitionData['Abbreviation']))
+        {
+            foreach ($this->DefinitionData['Abbreviation'] as $abbreviation => $meaning)
+            {
+                $this->currentAbreviation = $abbreviation;
+                $this->currentMeaning = $meaning;
+
+                $Inline['element'] = $this->elementApplyRecursiveDepthFirst(
+                    array($this, 'insertAbreviation'),
+                    $Inline['element']
+                );
+            }
+        }
+
+        return $Inline;
+    }
+
+    #
+    # Util Methods
+    #
+
+    protected function addDdElement(array $Line, array $Block)
+    {
+        $text = substr($Line['text'], 1);
+        $text = trim($text);
+
+        unset($Block['dd']);
+
+        $Block['dd'] = array(
+            'name' => 'dd',
+            'handler' => array(
+                'function' => 'lineElements',
+                'argument' => $text,
+                'destination' => 'elements'
+            ),
+        );
+
+        if (isset($Block['interrupted']))
+        {
+            $Block['dd']['handler']['function'] = 'textElements';
+
+            unset($Block['interrupted']);
+        }
+
+        $Block['element']['elements'] []= & $Block['dd'];
+
+        return $Block;
+    }
+
+    protected function buildFootnoteElement()
+    {
+        $Element = array(
+            'name' => 'div',
+            'attributes' => array('class' => 'footnotes'),
+            'elements' => array(
+                array('name' => 'hr'),
+                array(
+                    'name' => 'ol',
+                    'elements' => array(),
+                ),
+            ),
+        );
+
+        uasort($this->DefinitionData['Footnote'], 'self::sortFootnotes');
+
+        foreach ($this->DefinitionData['Footnote'] as $definitionId => $DefinitionData)
+        {
+            if ( ! isset($DefinitionData['number']))
+            {
+                continue;
+            }
+
+            $text = $DefinitionData['text'];
+
+            $textElements = parent::textElements($text);
+
+            $numbers = range(1, $DefinitionData['count']);
+
+            $backLinkElements = array();
+
+            foreach ($numbers as $number)
+            {
+                $backLinkElements[] = array('text' => ' ');
+                $backLinkElements[] = array(
+                    'name' => 'a',
+                    'attributes' => array(
+                        'href' => "#fnref$number:$definitionId",
+                        'rev' => 'footnote',
+                        'class' => 'footnote-backref',
+                    ),
+                    'rawHtml' => '&#8617;',
+                    'allowRawHtmlInSafeMode' => true,
+                    'autobreak' => false,
+                );
+            }
+
+            unset($backLinkElements[0]);
+
+            $n = count($textElements) -1;
+
+            if ($textElements[$n]['name'] === 'p')
+            {
+                $backLinkElements = array_merge(
+                    array(
+                        array(
+                            'rawHtml' => '&#160;',
+                            'allowRawHtmlInSafeMode' => true,
+                        ),
+                    ),
+                    $backLinkElements
+                );
+
+                unset($textElements[$n]['name']);
+
+                $textElements[$n] = array(
+                    'name' => 'p',
+                    'elements' => array_merge(
+                        array($textElements[$n]),
+                        $backLinkElements
+                    ),
+                );
+            }
+            else
+            {
+                $textElements[] = array(
+                    'name' => 'p',
+                    'elements' => $backLinkElements
+                );
+            }
+
+            $Element['elements'][1]['elements'] []= array(
+                'name' => 'li',
+                'attributes' => array('id' => 'fn:'.$definitionId),
+                'elements' => array_merge(
+                    $textElements
+                ),
+            );
+        }
+
+        return $Element;
+    }
+
+    # ~
+
+    protected function parseAttributeData($attributeString)
+    {
+        $Data = array();
+
+        $attributes = preg_split('/[ ]+/', $attributeString, - 1, PREG_SPLIT_NO_EMPTY);
+
+        foreach ($attributes as $attribute)
+        {
+            if ($attribute[0] === '#')
+            {
+                $Data['id'] = substr($attribute, 1);
+            }
+            else # "."
+            {
+                $classes []= substr($attribute, 1);
+            }
+        }
+
+        if (isset($classes))
+        {
+            $Data['class'] = implode(' ', $classes);
+        }
+
+        return $Data;
+    }
+
+    # ~
+
+    protected function processTag($elementMarkup) # recursive
+    {
+        # http://stackoverflow.com/q/1148928/200145
+        libxml_use_internal_errors(true);
+
+        $DOMDocument = new DOMDocument;
+
+        # http://stackoverflow.com/q/11309194/200145
+        $elementMarkup = mb_convert_encoding($elementMarkup, 'HTML-ENTITIES', 'UTF-8');
+
+        # http://stackoverflow.com/q/4879946/200145
+        $DOMDocument->loadHTML($elementMarkup);
+        $DOMDocument->removeChild($DOMDocument->doctype);
+        $DOMDocument->replaceChild($DOMDocument->firstChild->firstChild->firstChild, $DOMDocument->firstChild);
+
+        $elementText = '';
+
+        if ($DOMDocument->documentElement->getAttribute('markdown') === '1')
+        {
+            foreach ($DOMDocument->documentElement->childNodes as $Node)
+            {
+                $elementText .= $DOMDocument->saveHTML($Node);
+            }
+
+            $DOMDocument->documentElement->removeAttribute('markdown');
+
+            $elementText = "\n".$this->text($elementText)."\n";
+        }
+        else
+        {
+            foreach ($DOMDocument->documentElement->childNodes as $Node)
+            {
+                $nodeMarkup = $DOMDocument->saveHTML($Node);
+
+                if ($Node instanceof DOMElement and ! in_array($Node->nodeName, $this->textLevelElements))
+                {
+                    $elementText .= $this->processTag($nodeMarkup);
+                }
+                else
+                {
+                    $elementText .= $nodeMarkup;
+                }
+            }
+        }
+
+        # because we don't want for markup to get encoded
+        $DOMDocument->documentElement->nodeValue = 'placeholder\x1A';
+
+        $markup = $DOMDocument->saveHTML($DOMDocument->documentElement);
+        $markup = str_replace('placeholder\x1A', $elementText, $markup);
+
+        return $markup;
+    }
+
+    # ~
+
+    protected function sortFootnotes($A, $B) # callback
+    {
+        return $A['number'] - $B['number'];
+    }
+
+    #
+    # Fields
+    #
+
+    protected $regexAttribute = '(?:[#.][-\w]+[ ]*)';
+}

+ 22 - 0
vendor/autoload.php

@@ -0,0 +1,22 @@
+<?php
+
+// autoload.php @generated by Composer
+
+if (PHP_VERSION_ID < 50600) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    $err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, $err);
+        } elseif (!headers_sent()) {
+            echo $err;
+        }
+    }
+    throw new RuntimeException($err);
+}
+
+require_once __DIR__ . '/composer/autoload_real.php';
+
+return ComposerAutoloaderInit2185d2f99bcd56787481d9357a5972d3::getLoader();

+ 119 - 0
vendor/bin/generate_vcards

@@ -0,0 +1,119 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * Proxy PHP file generated by Composer
+ *
+ * This file includes the referenced bin path (../sabre/vobject/bin/generate_vcards)
+ * using a stream wrapper to prevent the shebang from being output on PHP<8
+ *
+ * @generated
+ */
+
+namespace Composer;
+
+$GLOBALS['_composer_bin_dir'] = __DIR__;
+$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
+
+if (PHP_VERSION_ID < 80000) {
+    if (!class_exists('Composer\BinProxyWrapper')) {
+        /**
+         * @internal
+         */
+        final class BinProxyWrapper
+        {
+            private $handle;
+            private $position;
+            private $realpath;
+
+            public function stream_open($path, $mode, $options, &$opened_path)
+            {
+                // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
+                $opened_path = substr($path, 17);
+                $this->realpath = realpath($opened_path) ?: $opened_path;
+                $opened_path = $this->realpath;
+                $this->handle = fopen($this->realpath, $mode);
+                $this->position = 0;
+
+                return (bool) $this->handle;
+            }
+
+            public function stream_read($count)
+            {
+                $data = fread($this->handle, $count);
+
+                if ($this->position === 0) {
+                    $data = preg_replace('{^#!.*\r?\n}', '', $data);
+                }
+
+                $this->position += strlen($data);
+
+                return $data;
+            }
+
+            public function stream_cast($castAs)
+            {
+                return $this->handle;
+            }
+
+            public function stream_close()
+            {
+                fclose($this->handle);
+            }
+
+            public function stream_lock($operation)
+            {
+                return $operation ? flock($this->handle, $operation) : true;
+            }
+
+            public function stream_seek($offset, $whence)
+            {
+                if (0 === fseek($this->handle, $offset, $whence)) {
+                    $this->position = ftell($this->handle);
+                    return true;
+                }
+
+                return false;
+            }
+
+            public function stream_tell()
+            {
+                return $this->position;
+            }
+
+            public function stream_eof()
+            {
+                return feof($this->handle);
+            }
+
+            public function stream_stat()
+            {
+                return array();
+            }
+
+            public function stream_set_option($option, $arg1, $arg2)
+            {
+                return true;
+            }
+
+            public function url_stat($path, $flags)
+            {
+                $path = substr($path, 17);
+                if (file_exists($path)) {
+                    return stat($path);
+                }
+
+                return false;
+            }
+        }
+    }
+
+    if (
+        (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+        || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+    ) {
+        return include("phpvfscomposer://" . __DIR__ . '/..'.'/sabre/vobject/bin/generate_vcards');
+    }
+}
+
+return include __DIR__ . '/..'.'/sabre/vobject/bin/generate_vcards';

+ 5 - 0
vendor/bin/generate_vcards.bat

@@ -0,0 +1,5 @@
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/generate_vcards
+SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
+php "%BIN_TARGET%" %*

+ 37 - 0
vendor/bin/naturalselection

@@ -0,0 +1,37 @@
+#!/usr/bin/env sh
+
+# Support bash to support `source` with fallback on $0 if this does not run with bash
+# https://stackoverflow.com/a/35006505/6512
+selfArg="$BASH_SOURCE"
+if [ -z "$selfArg" ]; then
+    selfArg="$0"
+fi
+
+self=$(realpath $selfArg 2> /dev/null)
+if [ -z "$self" ]; then
+    self="$selfArg"
+fi
+
+dir=$(cd "${self%[/\\]*}" > /dev/null; cd ../sabre/dav/bin && pwd)
+
+if [ -d /proc/cygdrive ]; then
+    case $(which php) in
+        $(readlink -n /proc/cygdrive)/*)
+            # We are in Cygwin using Windows php, so the path must be translated
+            dir=$(cygpath -m "$dir");
+            ;;
+    esac
+fi
+
+export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
+
+# If bash is sourcing this file, we have to source the target as well
+bashSource="$BASH_SOURCE"
+if [ -n "$bashSource" ]; then
+    if [ "$bashSource" != "$0" ]; then
+        source "${dir}/naturalselection" "$@"
+        return
+    fi
+fi
+
+exec "${dir}/naturalselection" "$@"

+ 5 - 0
vendor/bin/naturalselection.bat

@@ -0,0 +1,5 @@
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/../sabre/dav/bin/naturalselection
+SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
+python "%BIN_TARGET%" %*

+ 37 - 0
vendor/bin/sabredav

@@ -0,0 +1,37 @@
+#!/usr/bin/env sh
+
+# Support bash to support `source` with fallback on $0 if this does not run with bash
+# https://stackoverflow.com/a/35006505/6512
+selfArg="$BASH_SOURCE"
+if [ -z "$selfArg" ]; then
+    selfArg="$0"
+fi
+
+self=$(realpath $selfArg 2> /dev/null)
+if [ -z "$self" ]; then
+    self="$selfArg"
+fi
+
+dir=$(cd "${self%[/\\]*}" > /dev/null; cd ../sabre/dav/bin && pwd)
+
+if [ -d /proc/cygdrive ]; then
+    case $(which php) in
+        $(readlink -n /proc/cygdrive)/*)
+            # We are in Cygwin using Windows php, so the path must be translated
+            dir=$(cygpath -m "$dir");
+            ;;
+    esac
+fi
+
+export COMPOSER_RUNTIME_BIN_DIR="$(cd "${self%[/\\]*}" > /dev/null; pwd)"
+
+# If bash is sourcing this file, we have to source the target as well
+bashSource="$BASH_SOURCE"
+if [ -n "$bashSource" ]; then
+    if [ "$bashSource" != "$0" ]; then
+        source "${dir}/sabredav" "$@"
+        return
+    fi
+fi
+
+exec "${dir}/sabredav" "$@"

+ 5 - 0
vendor/bin/sabredav.bat

@@ -0,0 +1,5 @@
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/../sabre/dav/bin/sabredav
+SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
+sh "%BIN_TARGET%" %*

+ 119 - 0
vendor/bin/vobject

@@ -0,0 +1,119 @@
+#!/usr/bin/env php
+<?php
+
+/**
+ * Proxy PHP file generated by Composer
+ *
+ * This file includes the referenced bin path (../sabre/vobject/bin/vobject)
+ * using a stream wrapper to prevent the shebang from being output on PHP<8
+ *
+ * @generated
+ */
+
+namespace Composer;
+
+$GLOBALS['_composer_bin_dir'] = __DIR__;
+$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
+
+if (PHP_VERSION_ID < 80000) {
+    if (!class_exists('Composer\BinProxyWrapper')) {
+        /**
+         * @internal
+         */
+        final class BinProxyWrapper
+        {
+            private $handle;
+            private $position;
+            private $realpath;
+
+            public function stream_open($path, $mode, $options, &$opened_path)
+            {
+                // get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
+                $opened_path = substr($path, 17);
+                $this->realpath = realpath($opened_path) ?: $opened_path;
+                $opened_path = $this->realpath;
+                $this->handle = fopen($this->realpath, $mode);
+                $this->position = 0;
+
+                return (bool) $this->handle;
+            }
+
+            public function stream_read($count)
+            {
+                $data = fread($this->handle, $count);
+
+                if ($this->position === 0) {
+                    $data = preg_replace('{^#!.*\r?\n}', '', $data);
+                }
+
+                $this->position += strlen($data);
+
+                return $data;
+            }
+
+            public function stream_cast($castAs)
+            {
+                return $this->handle;
+            }
+
+            public function stream_close()
+            {
+                fclose($this->handle);
+            }
+
+            public function stream_lock($operation)
+            {
+                return $operation ? flock($this->handle, $operation) : true;
+            }
+
+            public function stream_seek($offset, $whence)
+            {
+                if (0 === fseek($this->handle, $offset, $whence)) {
+                    $this->position = ftell($this->handle);
+                    return true;
+                }
+
+                return false;
+            }
+
+            public function stream_tell()
+            {
+                return $this->position;
+            }
+
+            public function stream_eof()
+            {
+                return feof($this->handle);
+            }
+
+            public function stream_stat()
+            {
+                return array();
+            }
+
+            public function stream_set_option($option, $arg1, $arg2)
+            {
+                return true;
+            }
+
+            public function url_stat($path, $flags)
+            {
+                $path = substr($path, 17);
+                if (file_exists($path)) {
+                    return stat($path);
+                }
+
+                return false;
+            }
+        }
+    }
+
+    if (
+        (function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
+        || (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
+    ) {
+        return include("phpvfscomposer://" . __DIR__ . '/..'.'/sabre/vobject/bin/vobject');
+    }
+}
+
+return include __DIR__ . '/..'.'/sabre/vobject/bin/vobject';

+ 5 - 0
vendor/bin/vobject.bat

@@ -0,0 +1,5 @@
+@ECHO OFF
+setlocal DISABLEDELAYEDEXPANSION
+SET BIN_TARGET=%~dp0/vobject
+SET COMPOSER_RUNTIME_BIN_DIR=%~dp0
+php "%BIN_TARGET%" %*

+ 579 - 0
vendor/composer/ClassLoader.php

@@ -0,0 +1,579 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer\Autoload;
+
+/**
+ * ClassLoader implements a PSR-0, PSR-4 and classmap class loader.
+ *
+ *     $loader = new \Composer\Autoload\ClassLoader();
+ *
+ *     // register classes with namespaces
+ *     $loader->add('Symfony\Component', __DIR__.'/component');
+ *     $loader->add('Symfony',           __DIR__.'/framework');
+ *
+ *     // activate the autoloader
+ *     $loader->register();
+ *
+ *     // to enable searching the include path (eg. for PEAR packages)
+ *     $loader->setUseIncludePath(true);
+ *
+ * In this example, if you try to use a class in the Symfony\Component
+ * namespace or one of its children (Symfony\Component\Console for instance),
+ * the autoloader will first look for the class under the component/
+ * directory, and it will then fallback to the framework/ directory if not
+ * found before giving up.
+ *
+ * This class is loosely based on the Symfony UniversalClassLoader.
+ *
+ * @author Fabien Potencier <fabien@symfony.com>
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ * @see    https://www.php-fig.org/psr/psr-0/
+ * @see    https://www.php-fig.org/psr/psr-4/
+ */
+class ClassLoader
+{
+    /** @var \Closure(string):void */
+    private static $includeFile;
+
+    /** @var string|null */
+    private $vendorDir;
+
+    // PSR-4
+    /**
+     * @var array<string, array<string, int>>
+     */
+    private $prefixLengthsPsr4 = array();
+    /**
+     * @var array<string, list<string>>
+     */
+    private $prefixDirsPsr4 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr4 = array();
+
+    // PSR-0
+    /**
+     * List of PSR-0 prefixes
+     *
+     * Structured as array('F (first letter)' => array('Foo\Bar (full prefix)' => array('path', 'path2')))
+     *
+     * @var array<string, array<string, list<string>>>
+     */
+    private $prefixesPsr0 = array();
+    /**
+     * @var list<string>
+     */
+    private $fallbackDirsPsr0 = array();
+
+    /** @var bool */
+    private $useIncludePath = false;
+
+    /**
+     * @var array<string, string>
+     */
+    private $classMap = array();
+
+    /** @var bool */
+    private $classMapAuthoritative = false;
+
+    /**
+     * @var array<string, bool>
+     */
+    private $missingClasses = array();
+
+    /** @var string|null */
+    private $apcuPrefix;
+
+    /**
+     * @var array<string, self>
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param string|null $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+        self::initializeIncludeClosure();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixes()
+    {
+        if (!empty($this->prefixesPsr0)) {
+            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
+        }
+
+        return array();
+    }
+
+    /**
+     * @return array<string, list<string>>
+     */
+    public function getPrefixesPsr4()
+    {
+        return $this->prefixDirsPsr4;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirs()
+    {
+        return $this->fallbackDirsPsr0;
+    }
+
+    /**
+     * @return list<string>
+     */
+    public function getFallbackDirsPsr4()
+    {
+        return $this->fallbackDirsPsr4;
+    }
+
+    /**
+     * @return array<string, string> Array of classname => path
+     */
+    public function getClassMap()
+    {
+        return $this->classMap;
+    }
+
+    /**
+     * @param array<string, string> $classMap Class to filename map
+     *
+     * @return void
+     */
+    public function addClassMap(array $classMap)
+    {
+        if ($this->classMap) {
+            $this->classMap = array_merge($this->classMap, $classMap);
+        } else {
+            $this->classMap = $classMap;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix, either
+     * appending or prepending to the ones previously set for this prefix.
+     *
+     * @param string              $prefix  The prefix
+     * @param list<string>|string $paths   The PSR-0 root directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @return void
+     */
+    public function add($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            if ($prepend) {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr0
+                );
+            } else {
+                $this->fallbackDirsPsr0 = array_merge(
+                    $this->fallbackDirsPsr0,
+                    $paths
+                );
+            }
+
+            return;
+        }
+
+        $first = $prefix[0];
+        if (!isset($this->prefixesPsr0[$first][$prefix])) {
+            $this->prefixesPsr0[$first][$prefix] = $paths;
+
+            return;
+        }
+        if ($prepend) {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $paths,
+                $this->prefixesPsr0[$first][$prefix]
+            );
+        } else {
+            $this->prefixesPsr0[$first][$prefix] = array_merge(
+                $this->prefixesPsr0[$first][$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace, either
+     * appending or prepending to the ones previously set for this namespace.
+     *
+     * @param string              $prefix  The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths   The PSR-4 base directories
+     * @param bool                $prepend Whether to prepend the directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function addPsr4($prefix, $paths, $prepend = false)
+    {
+        $paths = (array) $paths;
+        if (!$prefix) {
+            // Register directories for the root namespace.
+            if ($prepend) {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $paths,
+                    $this->fallbackDirsPsr4
+                );
+            } else {
+                $this->fallbackDirsPsr4 = array_merge(
+                    $this->fallbackDirsPsr4,
+                    $paths
+                );
+            }
+        } elseif (!isset($this->prefixDirsPsr4[$prefix])) {
+            // Register directories for a new namespace.
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = $paths;
+        } elseif ($prepend) {
+            // Prepend directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $paths,
+                $this->prefixDirsPsr4[$prefix]
+            );
+        } else {
+            // Append directories for an already registered namespace.
+            $this->prefixDirsPsr4[$prefix] = array_merge(
+                $this->prefixDirsPsr4[$prefix],
+                $paths
+            );
+        }
+    }
+
+    /**
+     * Registers a set of PSR-0 directories for a given prefix,
+     * replacing any others previously set for this prefix.
+     *
+     * @param string              $prefix The prefix
+     * @param list<string>|string $paths  The PSR-0 base directories
+     *
+     * @return void
+     */
+    public function set($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr0 = (array) $paths;
+        } else {
+            $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Registers a set of PSR-4 directories for a given namespace,
+     * replacing any others previously set for this namespace.
+     *
+     * @param string              $prefix The prefix/namespace, with trailing '\\'
+     * @param list<string>|string $paths  The PSR-4 base directories
+     *
+     * @throws \InvalidArgumentException
+     *
+     * @return void
+     */
+    public function setPsr4($prefix, $paths)
+    {
+        if (!$prefix) {
+            $this->fallbackDirsPsr4 = (array) $paths;
+        } else {
+            $length = strlen($prefix);
+            if ('\\' !== $prefix[$length - 1]) {
+                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
+            }
+            $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length;
+            $this->prefixDirsPsr4[$prefix] = (array) $paths;
+        }
+    }
+
+    /**
+     * Turns on searching the include path for class files.
+     *
+     * @param bool $useIncludePath
+     *
+     * @return void
+     */
+    public function setUseIncludePath($useIncludePath)
+    {
+        $this->useIncludePath = $useIncludePath;
+    }
+
+    /**
+     * Can be used to check if the autoloader uses the include path to check
+     * for classes.
+     *
+     * @return bool
+     */
+    public function getUseIncludePath()
+    {
+        return $this->useIncludePath;
+    }
+
+    /**
+     * Turns off searching the prefix and fallback directories for classes
+     * that have not been registered with the class map.
+     *
+     * @param bool $classMapAuthoritative
+     *
+     * @return void
+     */
+    public function setClassMapAuthoritative($classMapAuthoritative)
+    {
+        $this->classMapAuthoritative = $classMapAuthoritative;
+    }
+
+    /**
+     * Should class lookup fail if not found in the current class map?
+     *
+     * @return bool
+     */
+    public function isClassMapAuthoritative()
+    {
+        return $this->classMapAuthoritative;
+    }
+
+    /**
+     * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
+     *
+     * @param string|null $apcuPrefix
+     *
+     * @return void
+     */
+    public function setApcuPrefix($apcuPrefix)
+    {
+        $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null;
+    }
+
+    /**
+     * The APCu prefix in use, or null if APCu caching is not enabled.
+     *
+     * @return string|null
+     */
+    public function getApcuPrefix()
+    {
+        return $this->apcuPrefix;
+    }
+
+    /**
+     * Registers this instance as an autoloader.
+     *
+     * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
+     */
+    public function register($prepend = false)
+    {
+        spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
+    }
+
+    /**
+     * Unregisters this instance as an autoloader.
+     *
+     * @return void
+     */
+    public function unregister()
+    {
+        spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
+    }
+
+    /**
+     * Loads the given class or interface.
+     *
+     * @param  string    $class The name of the class
+     * @return true|null True if loaded, null otherwise
+     */
+    public function loadClass($class)
+    {
+        if ($file = $this->findFile($class)) {
+            $includeFile = self::$includeFile;
+            $includeFile($file);
+
+            return true;
+        }
+
+        return null;
+    }
+
+    /**
+     * Finds the path to the file where the class is defined.
+     *
+     * @param string $class The name of the class
+     *
+     * @return string|false The path if found, false otherwise
+     */
+    public function findFile($class)
+    {
+        // class map lookup
+        if (isset($this->classMap[$class])) {
+            return $this->classMap[$class];
+        }
+        if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) {
+            return false;
+        }
+        if (null !== $this->apcuPrefix) {
+            $file = apcu_fetch($this->apcuPrefix.$class, $hit);
+            if ($hit) {
+                return $file;
+            }
+        }
+
+        $file = $this->findFileWithExtension($class, '.php');
+
+        // Search for Hack files if we are running on HHVM
+        if (false === $file && defined('HHVM_VERSION')) {
+            $file = $this->findFileWithExtension($class, '.hh');
+        }
+
+        if (null !== $this->apcuPrefix) {
+            apcu_add($this->apcuPrefix.$class, $file);
+        }
+
+        if (false === $file) {
+            // Remember that this class does not exist.
+            $this->missingClasses[$class] = true;
+        }
+
+        return $file;
+    }
+
+    /**
+     * Returns the currently registered loaders keyed by their corresponding vendor directories.
+     *
+     * @return array<string, self>
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
+    private function findFileWithExtension($class, $ext)
+    {
+        // PSR-4 lookup
+        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;
+
+        $first = $class[0];
+        if (isset($this->prefixLengthsPsr4[$first])) {
+            $subPath = $class;
+            while (false !== $lastPos = strrpos($subPath, '\\')) {
+                $subPath = substr($subPath, 0, $lastPos);
+                $search = $subPath . '\\';
+                if (isset($this->prefixDirsPsr4[$search])) {
+                    $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1);
+                    foreach ($this->prefixDirsPsr4[$search] as $dir) {
+                        if (file_exists($file = $dir . $pathEnd)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-4 fallback dirs
+        foreach ($this->fallbackDirsPsr4 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 lookup
+        if (false !== $pos = strrpos($class, '\\')) {
+            // namespaced class name
+            $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1)
+                . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR);
+        } else {
+            // PEAR-like class name
+            $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext;
+        }
+
+        if (isset($this->prefixesPsr0[$first])) {
+            foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) {
+                if (0 === strpos($class, $prefix)) {
+                    foreach ($dirs as $dir) {
+                        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                            return $file;
+                        }
+                    }
+                }
+            }
+        }
+
+        // PSR-0 fallback dirs
+        foreach ($this->fallbackDirsPsr0 as $dir) {
+            if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) {
+                return $file;
+            }
+        }
+
+        // PSR-0 include paths.
+        if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) {
+            return $file;
+        }
+
+        return false;
+    }
+
+    /**
+     * @return void
+     */
+    private static function initializeIncludeClosure()
+    {
+        if (self::$includeFile !== null) {
+            return;
+        }
+
+        /**
+         * Scope isolated include.
+         *
+         * Prevents access to $this/self from included files.
+         *
+         * @param  string $file
+         * @return void
+         */
+        self::$includeFile = \Closure::bind(static function($file) {
+            include $file;
+        }, null, null);
+    }
+}

+ 396 - 0
vendor/composer/InstalledVersions.php

@@ -0,0 +1,396 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ *
+ * @final
+ */
+class InstalledVersions
+{
+    /**
+     * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
+     * @internal
+     */
+    private static $selfDir = null;
+
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool
+     */
+    private static $installedIsLocalDir;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints((string) $constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+
+        // when using reload, we disable the duplicate protection to ensure that self::$installed data is
+        // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
+        // so we have to assume it does not, and that may result in duplicate data being returned when listing
+        // all installed packages for example
+        self::$installedIsLocalDir = false;
+    }
+
+    /**
+     * @return string
+     */
+    private static function getSelfDir()
+    {
+        if (self::$selfDir === null) {
+            self::$selfDir = strtr(__DIR__, '\\', '/');
+        }
+
+        return self::$selfDir;
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+        $copiedLocalDir = false;
+
+        if (self::$canGetVendors) {
+            $selfDir = self::getSelfDir();
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                $vendorDir = strtr($vendorDir, '\\', '/');
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                    $required = require $vendorDir.'/composer/installed.php';
+                    self::$installedByVendor[$vendorDir] = $required;
+                    $installed[] = $required;
+                    if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
+                        self::$installed = $required;
+                        self::$installedIsLocalDir = true;
+                    }
+                }
+                if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
+                    $copiedLocalDir = true;
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
+                $required = require __DIR__ . '/installed.php';
+                self::$installed = $required;
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        if (self::$installed !== array() && !$copiedLocalDir) {
+            $installed[] = self::$installed;
+        }
+
+        return $installed;
+    }
+}

+ 21 - 0
vendor/composer/LICENSE

@@ -0,0 +1,21 @@
+
+Copyright (c) Nils Adermann, Jordi Boggiano
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+

+ 10 - 0
vendor/composer/autoload_classmap.php

@@ -0,0 +1,10 @@
+<?php
+
+// autoload_classmap.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
+);

+ 16 - 0
vendor/composer/autoload_files.php

@@ -0,0 +1,16 @@
+<?php
+
+// autoload_files.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    '383eaff206634a77a1be54e64e6459c7' => $vendorDir . '/sabre/uri/lib/functions.php',
+    '2b9d0f43f9552984cfa82fee95491826' => $vendorDir . '/sabre/event/lib/coroutine.php',
+    'd81bab31d3feb45bfe2f283ea3c8fdf7' => $vendorDir . '/sabre/event/lib/Loop/functions.php',
+    'a1cce3d26cc15c00fcd0b3354bd72c88' => $vendorDir . '/sabre/event/lib/Promise/functions.php',
+    '3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
+    '93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
+    'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
+);

+ 9 - 0
vendor/composer/autoload_namespaces.php

@@ -0,0 +1,9 @@
+<?php
+
+// autoload_namespaces.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+);

+ 17 - 0
vendor/composer/autoload_psr4.php

@@ -0,0 +1,17 @@
+<?php
+
+// autoload_psr4.php @generated by Composer
+
+$vendorDir = dirname(__DIR__);
+$baseDir = dirname($vendorDir);
+
+return array(
+    'Sabre\\Xml\\' => array($vendorDir . '/sabre/xml/lib'),
+    'Sabre\\VObject\\' => array($vendorDir . '/sabre/vobject/lib'),
+    'Sabre\\Uri\\' => array($vendorDir . '/sabre/uri/lib'),
+    'Sabre\\HTTP\\' => array($vendorDir . '/sabre/http/lib'),
+    'Sabre\\Event\\' => array($vendorDir . '/sabre/event/lib'),
+    'Sabre\\' => array($vendorDir . '/sabre/dav/lib'),
+    'Psr\\Log\\' => array($vendorDir . '/psr/log/src'),
+    'PHPMailer\\PHPMailer\\' => array($vendorDir . '/phpmailer/phpmailer/src'),
+);

+ 50 - 0
vendor/composer/autoload_real.php

@@ -0,0 +1,50 @@
+<?php
+
+// autoload_real.php @generated by Composer
+
+class ComposerAutoloaderInit2185d2f99bcd56787481d9357a5972d3
+{
+    private static $loader;
+
+    public static function loadClassLoader($class)
+    {
+        if ('Composer\Autoload\ClassLoader' === $class) {
+            require __DIR__ . '/ClassLoader.php';
+        }
+    }
+
+    /**
+     * @return \Composer\Autoload\ClassLoader
+     */
+    public static function getLoader()
+    {
+        if (null !== self::$loader) {
+            return self::$loader;
+        }
+
+        require __DIR__ . '/platform_check.php';
+
+        spl_autoload_register(array('ComposerAutoloaderInit2185d2f99bcd56787481d9357a5972d3', 'loadClassLoader'), true, true);
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__));
+        spl_autoload_unregister(array('ComposerAutoloaderInit2185d2f99bcd56787481d9357a5972d3', 'loadClassLoader'));
+
+        require __DIR__ . '/autoload_static.php';
+        call_user_func(\Composer\Autoload\ComposerStaticInit2185d2f99bcd56787481d9357a5972d3::getInitializer($loader));
+
+        $loader->register(true);
+
+        $filesToLoad = \Composer\Autoload\ComposerStaticInit2185d2f99bcd56787481d9357a5972d3::$files;
+        $requireFile = \Closure::bind(static function ($fileIdentifier, $file) {
+            if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
+                $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+                require $file;
+            }
+        }, null, null);
+        foreach ($filesToLoad as $fileIdentifier => $file) {
+            $requireFile($fileIdentifier, $file);
+        }
+
+        return $loader;
+    }
+}

+ 84 - 0
vendor/composer/autoload_static.php

@@ -0,0 +1,84 @@
+<?php
+
+// autoload_static.php @generated by Composer
+
+namespace Composer\Autoload;
+
+class ComposerStaticInit2185d2f99bcd56787481d9357a5972d3
+{
+    public static $files = array (
+        '383eaff206634a77a1be54e64e6459c7' => __DIR__ . '/..' . '/sabre/uri/lib/functions.php',
+        '2b9d0f43f9552984cfa82fee95491826' => __DIR__ . '/..' . '/sabre/event/lib/coroutine.php',
+        'd81bab31d3feb45bfe2f283ea3c8fdf7' => __DIR__ . '/..' . '/sabre/event/lib/Loop/functions.php',
+        'a1cce3d26cc15c00fcd0b3354bd72c88' => __DIR__ . '/..' . '/sabre/event/lib/Promise/functions.php',
+        '3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
+        '93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
+        'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
+    );
+
+    public static $prefixLengthsPsr4 = array (
+        'S' => 
+        array (
+            'Sabre\\Xml\\' => 10,
+            'Sabre\\VObject\\' => 14,
+            'Sabre\\Uri\\' => 10,
+            'Sabre\\HTTP\\' => 11,
+            'Sabre\\Event\\' => 12,
+            'Sabre\\' => 6,
+        ),
+        'P' => 
+        array (
+            'Psr\\Log\\' => 8,
+            'PHPMailer\\PHPMailer\\' => 20,
+        ),
+    );
+
+    public static $prefixDirsPsr4 = array (
+        'Sabre\\Xml\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/xml/lib',
+        ),
+        'Sabre\\VObject\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/vobject/lib',
+        ),
+        'Sabre\\Uri\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/uri/lib',
+        ),
+        'Sabre\\HTTP\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/http/lib',
+        ),
+        'Sabre\\Event\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/event/lib',
+        ),
+        'Sabre\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/sabre/dav/lib',
+        ),
+        'Psr\\Log\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/psr/log/src',
+        ),
+        'PHPMailer\\PHPMailer\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/phpmailer/phpmailer/src',
+        ),
+    );
+
+    public static $classMap = array (
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
+    );
+
+    public static function getInitializer(ClassLoader $loader)
+    {
+        return \Closure::bind(function () use ($loader) {
+            $loader->prefixLengthsPsr4 = ComposerStaticInit2185d2f99bcd56787481d9357a5972d3::$prefixLengthsPsr4;
+            $loader->prefixDirsPsr4 = ComposerStaticInit2185d2f99bcd56787481d9357a5972d3::$prefixDirsPsr4;
+            $loader->classMap = ComposerStaticInit2185d2f99bcd56787481d9357a5972d3::$classMap;
+
+        }, null, ClassLoader::class);
+    }
+}

+ 607 - 0
vendor/composer/installed.json

@@ -0,0 +1,607 @@
+{
+    "packages": [
+        {
+            "name": "phpmailer/phpmailer",
+            "version": "v6.11.1",
+            "version_normalized": "6.11.1.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/PHPMailer/PHPMailer.git",
+                "reference": "d9e3b36b47f04b497a0164c5a20f92acb4593284"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/PHPMailer/PHPMailer/zipball/d9e3b36b47f04b497a0164c5a20f92acb4593284",
+                "reference": "d9e3b36b47f04b497a0164c5a20f92acb4593284",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-filter": "*",
+                "ext-hash": "*",
+                "php": ">=5.5.0"
+            },
+            "require-dev": {
+                "dealerdirect/phpcodesniffer-composer-installer": "^1.0",
+                "doctrine/annotations": "^1.2.6 || ^1.13.3",
+                "php-parallel-lint/php-console-highlighter": "^1.0.0",
+                "php-parallel-lint/php-parallel-lint": "^1.3.2",
+                "phpcompatibility/php-compatibility": "^9.3.5",
+                "roave/security-advisories": "dev-latest",
+                "squizlabs/php_codesniffer": "^3.7.2",
+                "yoast/phpunit-polyfills": "^1.0.4"
+            },
+            "suggest": {
+                "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication",
+                "ext-imap": "Needed to support advanced email address parsing according to RFC822",
+                "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses",
+                "ext-openssl": "Needed for secure SMTP sending and DKIM signing",
+                "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication",
+                "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication",
+                "league/oauth2-google": "Needed for Google XOAUTH2 authentication",
+                "psr/log": "For optional PSR-3 debug logging",
+                "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)",
+                "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication"
+            },
+            "time": "2025-09-30T11:54:53+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "PHPMailer\\PHPMailer\\": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "LGPL-2.1-only"
+            ],
+            "authors": [
+                {
+                    "name": "Marcus Bointon",
+                    "email": "phpmailer@synchromedia.co.uk"
+                },
+                {
+                    "name": "Jim Jagielski",
+                    "email": "jimjag@gmail.com"
+                },
+                {
+                    "name": "Andy Prevost",
+                    "email": "codeworxtech@users.sourceforge.net"
+                },
+                {
+                    "name": "Brent R. Matzelle"
+                }
+            ],
+            "description": "PHPMailer is a full-featured email creation and transfer class for PHP",
+            "support": {
+                "issues": "https://github.com/PHPMailer/PHPMailer/issues",
+                "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.11.1"
+            },
+            "funding": [
+                {
+                    "url": "https://github.com/Synchro",
+                    "type": "github"
+                }
+            ],
+            "install-path": "../phpmailer/phpmailer"
+        },
+        {
+            "name": "psr/log",
+            "version": "3.0.2",
+            "version_normalized": "3.0.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/php-fig/log.git",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=8.0.0"
+            },
+            "time": "2024-09-11T13:17:53+00:00",
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "3.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Psr\\Log\\": "src"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "PHP-FIG",
+                    "homepage": "https://www.php-fig.org/"
+                }
+            ],
+            "description": "Common interface for logging libraries",
+            "homepage": "https://github.com/php-fig/log",
+            "keywords": [
+                "log",
+                "psr",
+                "psr-3"
+            ],
+            "support": {
+                "source": "https://github.com/php-fig/log/tree/3.0.2"
+            },
+            "install-path": "../psr/log"
+        },
+        {
+            "name": "sabre/dav",
+            "version": "4.6.0",
+            "version_normalized": "4.6.0.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/dav.git",
+                "reference": "554145304b4a026477d130928d16e626939b0b2a"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/dav/zipball/554145304b4a026477d130928d16e626939b0b2a",
+                "reference": "554145304b4a026477d130928d16e626939b0b2a",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-date": "*",
+                "ext-dom": "*",
+                "ext-iconv": "*",
+                "ext-json": "*",
+                "ext-mbstring": "*",
+                "ext-pcre": "*",
+                "ext-simplexml": "*",
+                "ext-spl": "*",
+                "lib-libxml": ">=2.7.0",
+                "php": "^7.1.0 || ^8.0",
+                "psr/log": "^1.0 || ^2.0 || ^3.0",
+                "sabre/event": "^5.0",
+                "sabre/http": "^5.0.5",
+                "sabre/uri": "^2.0",
+                "sabre/vobject": "^4.2.1",
+                "sabre/xml": "^2.0.1"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^2.19",
+                "monolog/monolog": "^1.27 || ^2.0",
+                "phpstan/phpstan": "^0.12 || ^1.0",
+                "phpstan/phpstan-phpunit": "^1.0",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+            },
+            "suggest": {
+                "ext-curl": "*",
+                "ext-imap": "*",
+                "ext-pdo": "*"
+            },
+            "time": "2023-12-11T13:01:23+00:00",
+            "bin": [
+                "bin/sabredav",
+                "bin/naturalselection"
+            ],
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Sabre\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "WebDAV Framework for PHP",
+            "homepage": "http://sabre.io/",
+            "keywords": [
+                "CalDAV",
+                "CardDAV",
+                "WebDAV",
+                "framework",
+                "iCalendar"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/dav/issues",
+                "source": "https://github.com/fruux/sabre-dav"
+            },
+            "install-path": "../sabre/dav"
+        },
+        {
+            "name": "sabre/event",
+            "version": "5.1.7",
+            "version_normalized": "5.1.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/event.git",
+                "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/event/zipball/86d57e305c272898ba3c28e9bd3d65d5464587c2",
+                "reference": "86d57e305c272898ba3c28e9bd3d65d5464587c2",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.1 || ^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
+                "phpstan/phpstan": "^0.12",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+            },
+            "time": "2024-08-27T11:23:05+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "lib/coroutine.php",
+                    "lib/Loop/functions.php",
+                    "lib/Promise/functions.php"
+                ],
+                "psr-4": {
+                    "Sabre\\Event\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "sabre/event is a library for lightweight event-based programming",
+            "homepage": "http://sabre.io/event/",
+            "keywords": [
+                "EventEmitter",
+                "async",
+                "coroutine",
+                "eventloop",
+                "events",
+                "hooks",
+                "plugin",
+                "promise",
+                "reactor",
+                "signal"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/event/issues",
+                "source": "https://github.com/fruux/sabre-event"
+            },
+            "install-path": "../sabre/event"
+        },
+        {
+            "name": "sabre/http",
+            "version": "5.1.12",
+            "version_normalized": "5.1.12.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/http.git",
+                "reference": "dedff73f3995578bc942fa4c8484190cac14f139"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/http/zipball/dedff73f3995578bc942fa4c8484190cac14f139",
+                "reference": "dedff73f3995578bc942fa4c8484190cac14f139",
+                "shasum": ""
+            },
+            "require": {
+                "ext-ctype": "*",
+                "ext-curl": "*",
+                "ext-mbstring": "*",
+                "php": "^7.1 || ^8.0",
+                "sabre/event": ">=4.0 <6.0",
+                "sabre/uri": "^2.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.17.1||^3.63",
+                "phpstan/phpstan": "^0.12",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+            },
+            "suggest": {
+                "ext-curl": " to make http requests with the Client class"
+            },
+            "time": "2024-08-27T16:07:41+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "lib/functions.php"
+                ],
+                "psr-4": {
+                    "Sabre\\HTTP\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "The sabre/http library provides utilities for dealing with http requests and responses. ",
+            "homepage": "https://github.com/fruux/sabre-http",
+            "keywords": [
+                "http"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/http/issues",
+                "source": "https://github.com/fruux/sabre-http"
+            },
+            "install-path": "../sabre/http"
+        },
+        {
+            "name": "sabre/uri",
+            "version": "2.3.4",
+            "version_normalized": "2.3.4.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/uri.git",
+                "reference": "b76524c22de90d80ca73143680a8e77b1266c291"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/uri/zipball/b76524c22de90d80ca73143680a8e77b1266c291",
+                "reference": "b76524c22de90d80ca73143680a8e77b1266c291",
+                "shasum": ""
+            },
+            "require": {
+                "php": "^7.4 || ^8.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "^3.63",
+                "phpstan/extension-installer": "^1.4",
+                "phpstan/phpstan": "^1.12",
+                "phpstan/phpstan-phpunit": "^1.4",
+                "phpstan/phpstan-strict-rules": "^1.6",
+                "phpunit/phpunit": "^9.6"
+            },
+            "time": "2024-08-27T12:18:16+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "lib/functions.php"
+                ],
+                "psr-4": {
+                    "Sabre\\Uri\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "Functions for making sense out of URIs.",
+            "homepage": "http://sabre.io/uri/",
+            "keywords": [
+                "rfc3986",
+                "uri",
+                "url"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/uri/issues",
+                "source": "https://github.com/fruux/sabre-uri"
+            },
+            "install-path": "../sabre/uri"
+        },
+        {
+            "name": "sabre/vobject",
+            "version": "4.5.7",
+            "version_normalized": "4.5.7.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/vobject.git",
+                "reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/vobject/zipball/ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
+                "reference": "ff22611a53782e90c97be0d0bc4a5f98a5c0a12c",
+                "shasum": ""
+            },
+            "require": {
+                "ext-mbstring": "*",
+                "php": "^7.1 || ^8.0",
+                "sabre/xml": "^2.1 || ^3.0 || ^4.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.17.1",
+                "phpstan/phpstan": "^0.12 || ^1.12 || ^2.0",
+                "phpunit/php-invoker": "^2.0 || ^3.1",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+            },
+            "suggest": {
+                "hoa/bench": "If you would like to run the benchmark scripts"
+            },
+            "time": "2025-04-17T09:22:48+00:00",
+            "bin": [
+                "bin/vobject",
+                "bin/generate_vcards"
+            ],
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "4.0.x-dev"
+                }
+            },
+            "installation-source": "dist",
+            "autoload": {
+                "psr-4": {
+                    "Sabre\\VObject\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Dominik Tobschall",
+                    "email": "dominik@fruux.com",
+                    "homepage": "http://tobschall.de/",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Ivan Enderlin",
+                    "email": "ivan.enderlin@hoa-project.net",
+                    "homepage": "http://mnt.io/",
+                    "role": "Developer"
+                }
+            ],
+            "description": "The VObject library for PHP allows you to easily parse and manipulate iCalendar and vCard objects",
+            "homepage": "http://sabre.io/vobject/",
+            "keywords": [
+                "availability",
+                "freebusy",
+                "iCalendar",
+                "ical",
+                "ics",
+                "jCal",
+                "jCard",
+                "recurrence",
+                "rfc2425",
+                "rfc2426",
+                "rfc2739",
+                "rfc4770",
+                "rfc5545",
+                "rfc5546",
+                "rfc6321",
+                "rfc6350",
+                "rfc6351",
+                "rfc6474",
+                "rfc6638",
+                "rfc6715",
+                "rfc6868",
+                "vCalendar",
+                "vCard",
+                "vcf",
+                "xCal",
+                "xCard"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/vobject/issues",
+                "source": "https://github.com/fruux/sabre-vobject"
+            },
+            "install-path": "../sabre/vobject"
+        },
+        {
+            "name": "sabre/xml",
+            "version": "2.2.11",
+            "version_normalized": "2.2.11.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/sabre-io/xml.git",
+                "reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/sabre-io/xml/zipball/01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
+                "reference": "01a7927842abf3e10df3d9c2d9b0cc9d813a3fcc",
+                "shasum": ""
+            },
+            "require": {
+                "ext-dom": "*",
+                "ext-xmlreader": "*",
+                "ext-xmlwriter": "*",
+                "lib-libxml": ">=2.6.20",
+                "php": "^7.1 || ^8.0",
+                "sabre/uri": ">=1.0,<3.0.0"
+            },
+            "require-dev": {
+                "friendsofphp/php-cs-fixer": "~2.17.1||3.63.2",
+                "phpstan/phpstan": "^0.12",
+                "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6"
+            },
+            "time": "2024-09-06T07:37:46+00:00",
+            "type": "library",
+            "installation-source": "dist",
+            "autoload": {
+                "files": [
+                    "lib/Deserializer/functions.php",
+                    "lib/Serializer/functions.php"
+                ],
+                "psr-4": {
+                    "Sabre\\Xml\\": "lib/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "BSD-3-Clause"
+            ],
+            "authors": [
+                {
+                    "name": "Evert Pot",
+                    "email": "me@evertpot.com",
+                    "homepage": "http://evertpot.com/",
+                    "role": "Developer"
+                },
+                {
+                    "name": "Markus Staab",
+                    "email": "markus.staab@redaxo.de",
+                    "role": "Developer"
+                }
+            ],
+            "description": "sabre/xml is an XML library that you may not hate.",
+            "homepage": "https://sabre.io/xml/",
+            "keywords": [
+                "XMLReader",
+                "XMLWriter",
+                "dom",
+                "xml"
+            ],
+            "support": {
+                "forum": "https://groups.google.com/group/sabredav-discuss",
+                "issues": "https://github.com/sabre-io/xml/issues",
+                "source": "https://github.com/fruux/sabre-xml"
+            },
+            "install-path": "../sabre/xml"
+        }
+    ],
+    "dev": true,
+    "dev-package-names": []
+}

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini