Compare commits

...

10 commits

67 changed files with 2565 additions and 28 deletions

View file

@ -0,0 +1,157 @@
---
title: "Minecraft: Bedrock Edition on Waydroid"
description: "A guide to running Minecraft: Bedrock Edition on Linux with Waydroid"
published: 2024-02-29
edited: 2024-04-24T15:03:00.000+10:00
footer: cc_by
header: header.webp
header_alt: "neowofetch of my PC with Minecraft: Bedrock Edition in the background"
header_caption: "Minecraft: Bedrock Edition running on my PC"
---
## Introduction
Minecraft: Bedrock Edition is Microsoft's cross-platform version of the game. With the introduction of the Better Together Update it allowed players to play together regardless of platform through Xbox. Bedrock boasts its cross-play support for Windows, Xbox, Nintendo Switch, PlayStation, and mobile. However, macOS and Linux support are notably absent from Bedrock. While I am not aware of a workaround for macOS you can play Bedrock on Linux using [Waydroid](https://waydro.id/). This solution runs fairly well and works with cross-play because it's just *special* Android.
## Prerequisites
The following things are required to play Bedrock through this method:
- A computer running Linux and systemd
- If you're running Linux but don't know what systemd is you're probably fine :3
- A Wayland desktop environment / compositor
- GNOME
- KDE Plasma
- Sway
- river (what I'm using for this)
- COSMIC (Epoch, not tweaked GNOME)
- A graphics card compatible with Mesa
- AMD Radeon
- Intel
- NVIDIA with Nouvou+NVK (untested)
- A Google account that owns Minecraft for Android on the Play Store
## Installation
Before setting up Waydroid there are some distribution specific steps you must follow.
### Arch Linux
You will need an AUR helper or know how to manually install AUR packages. This page assumes you are using paru. You either need the binder_linux DKMS modules or the Linux Zen kernel.
#### binder_linux
To install the binder_linux DKMS modules install the binder_linux-dkms package from the AUR:
```bash
paru -S binder_linux-dkms
```
#### Linux Zen
To install the Linux Zen kernel run the following command:
```bash
sudo pacman -S linux-zen linux-zen-headers
```
You will also need to change your system to boot with Zen.
#### Waydroid AUR
You can then install Waydroid from the AUR:
```bash
paru -S waydroid
```
### Debian/Ubuntu
Install Waydroid from the offficial repository:
```bash
curl https://repo.waydro.id | sudo bash
sudo apt install waydroid
```
### Fedora
Install the Waydroid package:
```bash
sudo dnf install waydroid
```
### NixOS
In `/etc/nixos/configuration.nix` or in your flake add the following and rebuild:
```nix
virtualisation.waydroid.enable = true;
```
### Other
View the [install instructions](https://docs.waydro.id/usage/install-on-desktops) for your distribution.
## Waydroid Setup
First you need to enable and start the `waydroid-container` service:
```bash
sudo systemctl enable --now waydroid-container
```
Once the container has started you can initialize Waydroid with Google apps:
```bash
sudo waydroid init -s GAPPS
```
To install apps from Google Play you must "certify" your Waydroid installation:
```bash
waydroid session start
sudo waydroid shell
ANDROID_RUNTIME_ROOT=/apex/com.android.runtime ANDROID_DATA=/data ANDROID_TZDATA_ROOT=/apex/com.android.tzdata ANDROID_I18N_ROOT=/apex/com.android.i18n sqlite3 /data/data/com.google.android.gsf/databases/gservices.db "select * from main where name = \"android_id\";"
```
Copy the ID from the command output and enter it on the [Google Device registration](https://www.google.com/android/uncertified) page. Then restart the `waydroid-container` service:
```bash
sudo systemctl restart waydroid-container
```
## Configure Waydroid
Waydroid has a few settings you can configure from the command line. The one you will most likely want to set is the resolution, especially if you use fractional scaling. Waydroid currently doesn't appear to properly support fractional scaling so you will need to account for that when setting the resolution. To calculate the scaled resolution divide your display's resolution by the scaling factor, for 1920×1080 at 150% that's 1920 / 1.5 = 1280 and 1080 / 1.5 = 720. To set the resolution to 1280×720 run:
```bash
waydroid prop set persist.waydroid.width "1280"
waydroid prop set persist.waydroid.height "720"
```
After changing any properties you need to restart `waydroid-container`:
```bash
sudo systemctl restart waydroid-container
```
Some extra settings you might want to change in Android's System Settings are:
- Sound > Media volume: 100%
- This will make the game audio match your system volume
- System > Language & input > Physical keyboard > Use on-screen keyboard: Off
- This prevents Android's on-screen keyboard from appearing since you probably have a physical keyboard
## Play Minecraft
Open Waydroid's full system UI and navigate to the Google Play Store. Sign in with your Google account if you aren't already. Search for and download Minecraft.
You should be able to launch Minecraft from your desktop environment's application launcher or from the Waydroid full system UI. Some caveats to this solution are that render distance is limited to 32 chunks and you can't enable RTX even if you get it working on NVIDIA. Keyboard and mouse work out of the box and you may be able to pass through a controller but I couldn't get it to work in my limited testing.
This video shows Minecraft: Bedrock Edition running with 32 chunks through Waydroid on NixOS (the video is much older than the page header) with river.
<video controls preload="metadata">
<source src="/post-assets/minecraft-bedrock-linux/demo.webm" type="video/webm" />
</video>

View file

@ -0,0 +1,635 @@
---
title: Comparison of Misskey Forks
description: The Misskey family of Fediverse servers is quite large. While they all share a common origin there are some notable feature differences between them.
published: 2024-02-02
edited: 2025-02-10T19:24:00.000+10:00
footer: cc_by
header: banner.webp
header_alt: The Misskey wordmark in black on a white background with green circles
header_caption: Image from Misskey Hub, licensed under CC BY-SA 4.0.
---
This page aims to provide a rough timeline of each notable fork and a comparison of their features. The feature comparisons are for the latest stable releases of stable forks and development releases of developing/unmainted forks. If anything on this page is incorrect or out of date I'd appreciate if you could let me know: [@pancakes@meow.company](https://shrimp.meow.company/@pancakes).
## Featured Forks
| Icon | Name | Version | Account | Website | Repository |
|-----------------------------------------------------------------------------|----------------|------------------|--------------------------------------------------------------------|-------------------------------|-----------------------------------------------------|
| ![Misskey icon](/post-assets/misskey-comparison/misskey.png){.inline-icon} | Misskey | 2024.11.0 | None | <https://misskey-hub.net/en/> | <https://github.com/misskey-dev/misskey> |
| ![FoundKey icon](/post-assets/misskey-comparison/foundkey.svg){.inline-icon} | FoundKey | v13.0.0-preview6 | None | None | <https://akkoma.dev/FoundKeyGang/FoundKey> |
| ![Firefish icon](/post-assets/misskey-comparison/firefish.svg){.inline-icon} | Firefish | v20241205 | [@firefish@info.firefish.dev](https://info.firefish.dev/@firefish) | None | <https://firefish.dev/firefish/firefish> |
| ![Iceshrimp icon](/post-assets/misskey-comparison/iceshrimp.png){.inline-icon} | Iceshrimp (JS) | v2023.12.11 | None | None | <https://iceshrimp.dev/iceshrimp/iceshrimp> |
| ![Sharkey icon](/post-assets/misskey-comparison/sharkey.png){.inline-icon} | Sharkey | 2024.11.2 | [@Sharkey@sharkey.team](https://sharkey.team/@Sharkey) | <https://joinsharkey.org/> | <https://activitypub.software/TransFem-org/Sharkey> |
### Iceshrimp
In previous versions of this page I referred to Iceshrimp (JS) as just "Iceshrimp". Iceshrimp (JS) is currently under a feature freeze as most of the development effort is going towards the rewrite, Iceshrimp.NET. However, Iceshrimp (JS) is not unmaintained and still receives security updates. Please be aware that when the timeline says "Iceshrimp" it is referring to Iceshrimp (JS), not Iceshrimp.NET. Iceshrimp.NET will not be included on this page as it is not a fork of Misskey. For more information about Iceshrimp (JS) and Iceshrimp.NET see the README in the repository linked above.
### Firefish
On 2024-09-05 Firefish entered maintenance mode and will reach end-of-support at the end of 2024. Server admins may want to downgrade to `20240206/1.0.5-rc` and migrate to another *key fork. See the [announcement post](https://info.firefish.dev/notes/9xsukr38m3komd63) for more info.
## Timeline of Events
The following chart is an approximate history of ActivityPub and Misskey. As well as the forks, their versioning, and differing states of activity.
```plaintext
2016-01-28 ActivityPub W3C First Public Working Draft
2017-02-06 Mastodon v1.0 (included for reference)
2018-04-19 Misskey v0.0.5018
|
| (peace in the Misskeyverse)
|
2022-06-13 Misskey v12.111.1 ------------| (fork)
| |
2022-07-19 Misskey v12.117.1 |
| | (fork) |
2022-07-20 | Calckey v12.117.1-calc |
| | |
2022-08-05 | | FoundKey v13.0.0-preview1
| | |
2023-06-05 | | FoundKey v13.0.0-preview6
| | | (unmaintained)
| |
2023-06-25 | Calckey v14.0.0-rc3 ----| (rebrand)
| |
2023-07-20 | Firefish v1.0.0, v1.0.1 -| (fork)
| (version scheme change) | |
2023-09-14 | | Iceshrimp v2023.09.13-rc1
| | |
2023-09-21 Misskey 2023.9.0-beta.10 | |
| | (fork) | |
2023-10-07 | Sharkey 2023.9.1.beta4 | |
| | | |
2023-11-14 | | | Iceshrimp v2023.12.1
| | | | | (fork)
2023-11-28 | | Firefish v1.0.5-rc | |
| | | (unmaintained) | |
2024-01-09 | | | | Catodon 24.01-dev
| | | (transferred/revived) | |
2024-02-06 | | Firefish v20240206 | |
| | | | |
2024-09-05 | | | (enter maintenance) | |
| | | | |
2024-12-31 | | | (end-of-support) | |
| | | |
```
## Feature Comparison
<table class="no-rows">
<caption>Comparison of features between Misskey forks</caption>
<thead>
<tr>
<th scope="col"></th>
<th scope="col">Misskey</th>
<th scope="col">FoundKey</th>
<th scope="col">Firefish</th>
<th scope="col">Iceshrimp (JS)</th>
<th scope="col">Sharkey</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Status</th>
<td class="table-y">Stable</td>
<td class="table-n">Unmaintained</td>
<td class="table-n">Unmaintained</td>
<td class="table-p">Maintenance mode</td>
<td class="table-y">Stable</td>
</tr>
<tr>
<th scope="row" colspan="6">General</th>
</tr>
<tr>
<th scope="row">Authorized Fetch</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Full Text Search</th>
<td class="table-y">Built-in or MeiliSearch</td>
<td class="table-p">ElasticSearch</td>
<td class="table-p">Sonic, MeiliSearch, or ElasticSearch</td>
<td class="table-y">Built-in</td>
<td class="table-y">Built-in or MeiliSearch</td>
</tr>
<tr>
<th scope="row">Push Notifications</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Tutorial</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="6">Integration</th>
</tr>
<tr>
<th scope="row">Discord</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
</tr>
<tr>
<th scope="row">GitHub</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
</tr>
<tr>
<th scope="row">Mastodon API</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-p">Partial</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">TensorFlow</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
</tr>
<tr>
<th scope="row">Webhook</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="6">Timelines</th>
</tr>
<tr>
<th scope="row">Antennas</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Bubble/Recommended</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Channels</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Social</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">User Lists</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="7">Posts</th>
</tr>
<tr>
<th scope="row">Federation</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Editing & History</th>
<td class="table-n">Removed in 2023.10.0</td>
<td class="table-p">View only</td>
<td class="table-p">Editing only</td>
<td class="table-p">Editing only</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Favorite/Like Button</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Post Language</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
</tr>
<tr>
<th scope="row">Module Player</th>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Boost Visibility</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Report Posts</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="7">Markdown</th>
</tr>
<tr>
<th scope="row">Misskey Flavoured Markdown (MFM)</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">MFM border</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">MFM center</th>
<td class="table-p">&lt;center&gt; only</td>
<td class="table-p">&lt;center&gt; only</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-p">&lt;center&gt; only</td>
</tr>
<tr>
<th scope="row">MFM crop</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">MFM fade</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">MFM followmouse</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Per Note MFM Animation Toggle</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Markdown Lists</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
</tr>
<tr>
<th scope="row">Math Formulas</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="6">Users</th>
</tr>
<tr>
<th scope="row">Avatar Decorations</th>
<td class="table-p">Not federated</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-p">Not federated</td>
</tr>
<tr>
<th scope="row">Profile Background</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Cat Mode & Speak as Cat</th>
<td class="table-p">Cat Mode only</td>
<td class="table-p">Cat Mode only</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Link Verification</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Roles</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">ListenBrainz Status</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Feeds</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Clips</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Gallery</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Pages</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Post Backfill</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Mute Users</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Block Users</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Report Users</th>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Disable Post Indexing</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Import/Export</th>
<td class="table-y">All notes, Favorite notes, Followed users, User lists, Muted users, Blocked
users, and Antennas</td>
<td class="table-y">All posts, Followed users, User lists, Muted users, and Blocked users</td>
<td class="table-y">All posts, Followed users, User lists, Muted users, and Blocked users</td>
<td class="table-y">All posts, Followed users, User lists, Muted users, and Blocked users</td>
<td class="table-y">All notes, Favorite notes, Followed users, User lists, Muted users, Blocked
users, and Antennas</td>
</tr>
<tr>
<th scope="row">Post Import</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Mastodon+</td>
<td class="table-n">No</td>
<td class="table-y">Mastodon+, Pleroma, Akkoma, Misskey+, Firefish+, Twitter, Instagram, and
Facebook</td>
</tr>
<tr>
<th scope="row">GDPR Data Subject Access Requests</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Migration</th>
<td class="table-y">Yes</td>
<td class="table-p">Receive only</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row" colspan="6">Moderation</th>
</tr>
<tr>
<th scope="row">Approve Users</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Silence Users</th>
<td class="table-y">Yes</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
<tr>
<th scope="row">Mark User Media NSFW</th>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-n">No</td>
<td class="table-y">Yes</td>
<td class="table-y">Yes</td>
</tr>
</tbody>
</table>
## Glossary
Brief descriptions of each feature listed above. I'm keeping them vague as they vary based on software and more broadly across the Fediverse.
### Legends
- **\[Software\]+:** Software plus forks.
### General
- **Authorized Fetch:** A security feature which requires remote instances to identify themselves when requesting information from the local instance. This makes it more difficult for remote instances to access information that they are not permitted to. [Read more](https://docs.joinmastodon.org/admin/config/#authorized_fetch)
- **Full Text Search:** Allows users to perform more advanced post searches. This may include filters for things as the user that made the post or if the post has an image.
- **Push Notifications:** Allows users to receive notifications when the website is not open.
### Integration
- **Mastodon API:** The API used by Mastodon. Allows users to use apps/clients that were made for Mastodon.
- **TensorFlow:** Mainly used by Misskey+ to automatically detect NSFW content.
### Timelines
- **Antennas:** One or more timelines that only show posts that fit certain criteria/filters.
- **Bubble/Recommended:** A timeline that only shows posts from instances that the admin manually approves.
- **Social:** Combined Home and Local timeline.
- **User Lists:** One or more timelines that only show users that are added to the timeline. Note: If you receive a follow request from an account named `@instance.actor@...` it most likely means a user from that instance that doesn't follow you is trying to add you to a list.
### Posts
- **Editing & History:** Allows users to make changes to their posts and to see a history of changes.
- **Module Player:** Allows users to upload and listen to module or tracker files.
- **Boost Visibility:** Allows users to change the visibility of their boosts between Public, Unlisted, Followers-only, and Local.
### Markdown
- **Misskey Flavoured Markdown (MFM):** A flavor of Markdown created by Misskey which allows for special formatting such as scale, text color, animation, and more.
- **MFM fade:** An animation for fading in and out content.
- **Per Note MFM Animation Toggle:** By default MFM animations are disabled. Posts with MFM animations will have a play/pause button that is initially paused.
### Users
- **Avatar Decorations:** "Stickers" that can be placed and resized on your profile picture.
- **Cat Mode & Speak as Cat:** Cat Mode adds cat ears to the user's profile picture. Speak as Cat modifies the appearance of posts to be more cat-like. For example "everyone" becomes "everynyan". If Speak as Cat is not a feature of the software then it is included as part of Cat Mode. This feature is taken *very seriously*.
- **Link Verification:** Verified links will show a checkmark next to them. This can help to verify that a user is who they say they are. [How to verify links](https://docs.joinmastodon.org/user/profile/#verification")
- **Roles:** Allows admins to control what certain groups of users can and can't do. Displays a role badge on the user profile.
- **ListenBrainz Status:** Displays what song the user is listening to. Similar to Last.fm.
- **Feeds:** Automatically generate RSS, Atom, and JSON feeds for public posts from public accounts.
- **Pages:** Allows users to attach documents to their profile with extra functionality compared to posts.
- **Post Backfill:** Older posts from remote users will be automatically downloaded to the local instance.
- **Disable Post Indexing:** Prevent posts from being indexed by search engines.
- **Import/Export:** Upload and download personal data to move it between accounts.
- **Post Import:** Allows the user to upload posts that were exported from other platforms. This copies the original post dates and may import attached media.
- **Migration/Move:** Move followers from an old account to a new account. This does not move followed users or other data, see: Import/Export.
### Moderation
- **Approve Users:** If enabled users will need to provide a reason why they are registering. Moderators can then approve or deny a user's account registration.
- **Mark User Media NSFW:** Allows moderators to mark some or all of a user's media as Not Safe For Work.
## Special Thanks
### Corrections
- [@Amelia@transfem.social](https://transfem.social/@Amelia")
- [@blueb@eepy.zone](https://eepy.zone/@blueb")
- [@frost@wolfdo.gg](https://wolfdo.gg/@frost")
- [@jeder@miau.jeder.pl](https://miau.jeder.pl/@jeder")
- [@jegler@heckin.how](https://heckin.how/@jegler")
- [@lewdum@heckin.how](https://heckin.how/@lewdum")
- [@Marie@dev.joinsharkey.org](https://dev.joinsharkey.org/@Marie")
- [@netbat@catodon.social](https://catodon.social/@netbat")
- [@privateger@plasmatrap.com](https://plasmatrap.com/@privateger")
- [@vavency@kitsunes.club](https://kitsunes.club/@vavency")
- [@Weeble@bungle.online](https://bungle.online/@Weeble")
<style>
.table-y {
background-color: var(--success);
color: black;
}
.table-p {
background-color: var(--warn);
color: black;
}
.table-n {
background-color: var(--error);
color: black;
}
</style>

View file

@ -21,6 +21,8 @@
<body>
<Routes/>
<script src="@Assets["js/codeblocks.js"]"></script>
<script src="_framework/blazor.web.js"></script>
</body>

View file

@ -1,14 +1,23 @@
@using PancakesWeb.Components.UI
@using Microsoft.AspNetCore.Components.Sections
@using PancakesWeb.Components.UI
@inherits LayoutComponentBase
<a class="skip-to-content" href="#main-content">Skip to main content</a>
<header>
<Navbar/>
<SectionOutlet SectionName="Header"/>
</header>
@Body
<footer>
<div class="container">
<SectionOutlet SectionName="Footer"/>
<p>🐈‍⬛</p>
</div>
</footer>
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>

View file

@ -1,3 +1,10 @@
footer {
margin-top: 3rem;
padding: 3rem 0;
text-align: center;
}
#blazor-error-ui {
color-scheme: light only;
background: lightyellow;

View file

@ -0,0 +1,89 @@
@page "/{Slug}"
@using Microsoft.AspNetCore.Components.Sections
@if (Post != null)
{
<SectionContent SectionName="Header">
@if (Post.HeaderFilename != null)
{
<figure class="header">
<img src="@Assets[$"post-assets/{Post.Slug}/{Post.HeaderFilename}"]" alt="@Post.HeaderAlt" class="header-img">
@if (Post.HeaderCaption != null)
{
<figcaption>@Post.HeaderCaption</figcaption>
}
</figure>
}
<div id="main-content" class="container">
<h1>@Post.Title</h1>
<p>@Post.Description</p>
@if (Post.Edited != null)
{
var edited = Post.Edited ?? DateTime.Now; // this null check should never happen
<small>
Posted: <RenderDateOnly Dateonly="Post.Published"/>
&bull;
Latest edit: <RenderDateTime Datetime="@(edited)"/>
</small>
}
else
{
<small>Posted: <RenderDateOnly Dateonly="Post.Published"/></small>
}
<hr>
</div>
</SectionContent>
}
else
{
<main id="main-content" class="container">
<h1>Not found</h1>
<p>Page not found</p>
<a href="/">Go home</a>
</main>
return;
}
@if (Post?.Content != null)
{
<article class="container">
@((MarkupString)Post.Content)
</article>
}
@if (Post?.Footer != null)
{
<SectionContent SectionName="Footer">
@switch (Post.Footer)
{
case BlogPosts.PostFooter.CcBy:
<p>
<a href="https://pancakes.guy/@Post.Slug">@Post.Title</a> © @Post.Published.Year by
<a href="https://pancakes.gay">pancakes</a> is licensed under
<a href="https://creativecommons.org/licenses/by/4.0/" target="_blank">CC BY 4.0</a>
<img src="https://mirrors.creativecommons.org/presskit/icons/cc.svg" alt="CC" class="inline-icon">
<img src="https://mirrors.creativecommons.org/presskit/icons/by.svg" alt="BY" class="inline-icon">
</p>
break;
default:
break;
}
</SectionContent>
}
@code {
[Parameter] public required string Slug { get; set; }
public BlogPosts.Post? Post { get; set; }
protected override void OnInitialized()
{
if (string.IsNullOrWhiteSpace(Slug))
{
return;
}
Post = BlogPosts.Posts.FirstOrDefault(p => p.Slug == Slug);
}
}

View file

@ -0,0 +1,29 @@
.header {
max-width: var(--container-width);
margin-left: auto;
margin-right: auto;
}
.header-img {
aspect-ratio: 21/9;
width: 100%;
object-fit: cover;
}
header .container {
padding-top: 0;
padding-bottom: 0;
font-size: large;
line-height: 1.4;
}
article {
padding-top: 0;
}
@media screen and (min-width: 768px) {
.header-img {
border-radius: var(--radius);
}
}

View file

@ -1,7 +1,164 @@
@page "/"
@using System.ComponentModel.DataAnnotations
@using System.Text.Json
@using PancakesWeb.Data
@using PancakesWeb.Schema
<PageTitle>Home</PageTitle>
<h1>Hello, world!</h1>
<main id="main-content" class="container">
<h1 id="pancakes">pancakes</h1>
<p>I'm a cat <abbr title="non-binary">enby</abbr> (<abbr
title="they/them/their/theirs/themself or it/it/its/its/itself in English">they/it</abbr>) from Australia
that likes cats, Linux, and programming. You can find my links, projects, and other pages here.</p>
Welcome to your new app.
<h2 id="accounts">Accounts</h2>
<p>Other places you can find me</p>
<ul class="accounts">
@foreach (var link in Links.Accounts)
{
<li class="account card">
<img src="@Assets[$"imgs/icons/{link.Icon}"]" alt="" aria-hidden="true"
class="@(link.IconInvert ? "account-img img-invert" : "account-img")">
<span class="account-details">
@if (link.Href != null)
{
<a href="@link.Href" target="_blank">@link.Name</a>
}
else
{
<span>@link.Name</span>
}
<span class="account-username">@link.Description</span>
</span>
</li>
}
</ul>
<h2 id="blog-posts">Blog Posts</h2>
<p>Things I've written. Licensing can be found in the appropriate page footers</p>
<BlogPosts/>
<h2 id="projects">Projects</h2>
<p>Projects that I have created</p>
<ul class="links">
@foreach (var link in Links.Projects)
{
<li class="link card">
@if (link.Href != null)
{
<a href="@link.Href" target="_blank" class="link-heading">@link.Name</a>
}
else
{
<span class="link-heading">@link.Name</span>
}
<p>@link.Description</p>
</li>
}
</ul>
<h2 id="contributions">Contributions</h2>
<p>Projects that I do not maintain but have contributed to</p>
<ul class="links">
@foreach (var link in Links.Contributions)
{
<li class="link card">
@if (link.Href != null)
{
<a href="@link.Href" target="_blank" class="link-heading">@link.Name</a>
}
else
{
<span class="link-heading">@link.Name</span>
}
<p>@link.Description</p>
</li>
}
</ul>
<h2 id="buttons">Buttons</h2>
<p>Click my buttons</p>
<ImageButton Data="@Buttons.Pancakes"/>
<details>
<summary>Add my button</summary>
<pre><code>&lt;a href="@Buttons.Pancakes.Href" target="_blank"&gt;
&lt;img src="@Buttons.Pancakes.Href@Assets[$"imgs/buttons/{Buttons.Pancakes.Image}"]" alt"@Buttons.Pancakes.Title"&gt;
&lt;/a&gt;</code></pre>
<p>The image URL may change in the future. Instead of hotlinking it you should <a href="@Assets[$"imgs/buttons/{Buttons.Pancakes.Image}"]" download="pancakes.png">download the image</a> manually.</p>
</details>
<ul class="buttons card">
@foreach (var button in Buttons.Entities.OrderBy(p => p.Title))
{
<li class="button">
<ImageButton Data="button"/>
</li>
}
</ul>
<ul class="buttons card">
@foreach (var button in Buttons.Things.OrderBy(p => p.Title))
{
<li class="button">
<ImageButton Data="button"/>
</li>
}
</ul>
<h2 id="webrings">Webrings</h2>
<p>Adjacent websites</p>
<ul class="accounts">
@foreach (var ring in Webrings.Rings.OrderBy(p => p.Name))
{
<li class="account card">
<a href="@ring.PrevUrl" class="no-decorations">←</a>
<a href="@ring.MainUrl">@ring.Name</a>
<a href="@ring.NextUrl" class="no-decorations">→</a>
</li>
}
</ul>
<h2 id="notify">Notify</h2>
<p>Send me a push notification. This will be a silent notification on my phone with no popup, vibration, or sound.</p>
<EditForm Model="Ntfy" OnValidSubmit="SubmitNtfy" FormName="Ntfy">
<DataAnnotationsValidator/>
<InputText @bind-Value="Ntfy.Message" maxlength="128" placeholder="Message"/>
<button type="submit">Send</button>
<ValidationSummary/>
</EditForm>
</main>
@code {
[Inject] private HttpClient Http { get; set; } = null!;
[Inject] private IConfiguration Config { get; set; } = null!;
[SupplyParameterFromForm] private NtfyModel Ntfy { get; set; } = new NtfyModel();
private async Task SubmitNtfy()
{
var ntfy = new NtfyRequest
{
Topic = Config["Ntfy:Topic"]!,
Message = Ntfy.Message,
Title = "pancakes.gay",
Priority = 2,
Tags = ["black_cat"]
};
await Http.PostAsJsonAsync(Config["Ntfy:Url"], ntfy, JsonSerializerOptions.Web);
}
public class NtfyModel
{
[StringLength(128)]
public string Message { get; set; } = "";
}
}

View file

@ -0,0 +1,73 @@
.accounts {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 1rem;
padding-left: 0;
list-style: none;
}
.account {
display: flex;
flex-direction: row;
align-items: center;
gap: 1rem;
margin-top: 0;
}
.account-img {
width: 2em;
height: 2em;
object-fit: contain;
}
.account-details {
display: flex;
flex-direction: column;
text-wrap: wrap;
word-break: break-word;
}
.account-username {
font-size: 0.9em;
}
.links {
display: flex;
flex-direction: column;
gap: 1rem;
padding-left: 0;
list-style: none;
}
.link :last-child {
margin-bottom: 0;
}
.link-heading {
font-size: large;
font-weight: bold;
}
.buttons {
display: flex;
flex-direction: row;
flex-wrap: wrap;
gap: 0.5rem;
list-style: none;
}
.button {
display: inline-flex;
}
@media screen and (max-width: 768px) {
.accounts {
flex-direction: column;
}
.account-username {
font-size: inherit;
}
}

View file

@ -0,0 +1,22 @@
<ol class="posts">
@foreach (var post in Posts)
{
<li class="post card">
<a href="@post.Slug" data-enhance-nav="false" class="post-heading">@post.Title</a>
<p>@post.Description</p>
@if (post.Edited != null)
{
var edited = post.Edited ?? DateTime.Now; // this null check should never happen
<small>
Posted: <RenderDateOnly Dateonly="post.Published"/>
&bull;
Edited: <RenderDateTime Datetime="@(edited)"/>
</small>
}
else
{
<small>Posted: <RenderDateOnly Dateonly="post.Published"/></small>
}
</li>
}
</ol>

View file

@ -0,0 +1,109 @@
using Markdig;
using Markdig.Extensions.AutoLinks;
using Markdig.Extensions.Yaml;
using Markdig.Renderers;
using Markdig.Syntax;
using Microsoft.AspNetCore.Components;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
namespace PancakesWeb.Components.UI;
public partial class BlogPosts : ComponentBase
{
public static List<Post> Posts = new();
static BlogPosts()
{
foreach (var file in Directory.EnumerateFiles("BlogPosts"))
{
if (file.EndsWith(".md"))
{
Posts.Add(new Post(Path.GetFileNameWithoutExtension(file)));
}
}
Posts = Posts.OrderByDescending(p => p.Published).ToList();
}
public class Post
{
public readonly string Slug;
public string Content = null!;
public string Title = null!;
public string Description = null!;
public DateOnly Published = DateOnly.MinValue;
public DateTime? Edited;
public PostFooter? Footer;
public string? HeaderFilename;
public string? HeaderAlt;
public string? HeaderCaption;
public Post(string slug)
{
Slug = slug;
UpdatePost();
}
public void UpdatePost()
{
var text = File.ReadAllText($"BlogPosts/{Slug}.md");
var (metadata, html) = ParseMarkdown(text);
Content = html;
Title = metadata.Title;
Description = metadata.Description;
Published = metadata.Published;
Edited = metadata.Edited;
Footer = metadata.Footer;
HeaderFilename = metadata.Header;
HeaderAlt = metadata.HeaderAlt;
HeaderCaption = metadata.HeaderCaption;
}
private static (PostMetadata metadata, string html) ParseMarkdown(string markdown)
{
var pipeline = new MarkdownPipelineBuilder().UseAbbreviations().UseAlertBlocks().UseAutoIdentifiers()
.UseAutoLinks(new AutoLinkOptions { OpenInNewWindow = true }).UseEmphasisExtras().UseFootnotes()
.UseGenericAttributes().UseMediaLinks().UsePipeTables().UseSmartyPants().UseTaskLists()
.UseYamlFrontMatter().Build();
var writer = new StringWriter();
var renderer = new HtmlRenderer(writer);
pipeline.Setup(renderer);
var document = Markdown.Parse(markdown, pipeline);
var yaml = "";
var frontMatter = document.Descendants<YamlFrontMatterBlock>().FirstOrDefault();
if (frontMatter != null)
yaml = markdown.Substring(frontMatter.Span.Start + 4, frontMatter.Span.Length - 8);
renderer.Render(document);
writer.Flush();
var deserializer = new DeserializerBuilder().WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithEnumNamingConvention(UnderscoredNamingConvention.Instance).Build();
var metadata = deserializer.Deserialize<PostMetadata>(yaml);
return (metadata, writer.ToString());
}
}
private class PostMetadata
{
public string Title { get; set; } = null!;
public string Description { get; set; } = null!;
public string? Header { get; set; }
public string? HeaderAlt { get; set; }
public string? HeaderCaption { get; set; }
public PostFooter? Footer { get; set; }
public DateOnly Published { get; set; }
public DateTime? Edited { get; set; }
}
public enum PostFooter
{
CcBy
}
}

View file

@ -0,0 +1,12 @@
.posts {
display: flex;
flex-direction: column;
gap: 1rem;
padding-left: 0;
list-style: none;
}
.post-heading {
font-size: large;
font-weight: bold;
}

View file

@ -0,0 +1,18 @@
@using PancakesWeb.Data
@if (Data.Href != null)
{
<a href="@Data.Href" target="_blank" class="button-link">@Image</a>
}
else
{
@Image
}
@code {
[Parameter, EditorRequired] public required Button Data { get; set; }
private RenderFragment Image =>
@<img src="@Assets[$"imgs/buttons/{Data.Image}"]" alt="@Data.Title" title="@Data.Title" class="button-img"/>;
}

View file

@ -0,0 +1,15 @@
.button-link {
text-decoration: none;
}
.button-link::after {
display: none;
content: none;
}
.button-img {
width: 88px;
height: 31px;
object-fit: contain;
image-rendering: crisp-edges;
}

View file

@ -1,6 +1,6 @@
@using PancakesWeb.Data
<nav class="container">
<nav>
<ul class="nav-content">
<li class="nav-item brand">
<a class="nav-link" href="/">
@ -12,8 +12,8 @@
@foreach (var link in Links.Accounts.Where(p => p is { Href: not null, HomePageOnly: false }))
{
<li class="nav-item">
<a class="nav-link can-hide-label" href="@link.Href">
<img class="@(link.IconInvert ? "nav-img invert-icon" : "nav-img")"
<a class="nav-link can-hide-label" href="@link.Href" target="_blank">
<img class="@(link.IconInvert ? "nav-img img-invert" : "nav-img")"
src="@Assets[$"imgs/icons/{link.Icon}"]" alt="@link.Name icon" aria-hidden="true">
<span class="nav-label">@link.Name</span>
</a>

View file

@ -1,3 +1,8 @@
a::after {
display: none;
content: none;
}
.nav-content {
display: flex;
flex-direction: row;
@ -45,11 +50,11 @@
object-fit: contain;
}
.nav-img.invert-icon {
filter: invert() hue-rotate(180deg);
@media screen and (max-width: 768px) {
.nav-content {
margin-bottom: 1rem;
}
@media screen and (max-width: 768px) {
/* Hide labels while still being accessible by screen readers */
.nav-link.can-hide-label {
gap: 0;

View file

@ -1,7 +0,0 @@
<time datetime="@Date.ToString("O")">
@Date.ToUniversalTime().ToString("G")
</time>
@code {
[Parameter, EditorRequired] public required DateTime Date { get; set; }
}

View file

@ -0,0 +1,7 @@
<time datetime="@Dateonly.ToString("O")">
@Dateonly.ToString("d")
</time>
@code {
[Parameter, EditorRequired] public required DateOnly Dateonly { get; set; }
}

View file

@ -0,0 +1,7 @@
<time datetime="@Datetime.ToString("O")">
@Datetime.ToUniversalTime().ToString("G")
</time>
@code {
[Parameter, EditorRequired] public required DateTime Datetime { get; set; }
}

View file

@ -0,0 +1,7 @@
<time datetime="@Timeonly.ToString("O")">
@Timeonly.ToString("T")
</time>
@code {
[Parameter, EditorRequired] public required TimeOnly Timeonly { get; set; }
}

192
PancakesWeb/Data/Button.cs Normal file
View file

@ -0,0 +1,192 @@
namespace PancakesWeb.Data;
public class Button
{
public required string Image { get; set; }
public required string Title { get; set; }
public string? Href { get; set; }
}
public static class Buttons
{
public static readonly Button Pancakes = new Button
{
Image = "pancakes.png",
Title = "pancakes",
Href = "https://pancakes.gay/"
};
public static readonly List<Button> Entities =
[
new Button
{
Image = "aagaming.png",
Title = "aagaming",
Href = "https://aagaming.me/"
},
new Button
{
Image = "atapi.gif",
Title = "Atapi's Domain!",
Href = "https://atapi.space/"
},
new Button
{
Image = "blueb.png",
Title = "blueb's website",
Href = "https://blueb.pages.gay/"
},
new Button
{
Image = "elke.gif",
Title = "elke.cafe",
Href = "https://elke.cafe/"
},
new Button
{
Image = "houl.png",
Title = "Houl Floof :3c",
Href = "https://houl.floof.company/"
},
new Button
{
Image = "ipg.png",
Title = "InvoxiPlayGames",
Href = "https://www.invoxiplaygames.uk/"
},
new Button
{
Image = "lilysthings.png",
Title = "Lily's Things at The Cool Site",
Href = "https://lilysthings.org/"
},
new Button
{
Image = "micro.avif",
Title = "microsite",
Href = "https://microspinny.zip/"
},
new Button
{
Image = "nelleobserver.gif",
Title = "nelle.observer",
Href = "https://nelle.observer/"
},
new Button
{
Image = "notfire.gif",
Title = "notfire.cc",
Href = "https://notfire.cc/"
},
new Button
{
Image = "olivia.png",
Title = "olivia",
Href = "https://floof.gay/"
},
new Button
{
Image = "sneexy.svg",
Title = "sneexy",
Href = "https://sneexy.synth.download/"
},
new Button
{
Image = "synthdownload.svg",
Title = "synth download!",
Href = "https://synth.download/"
},
new Button
{
Image = "voidrehab.png",
Title = "void.rehab",
Href = "https://void.rehab/"
},
new Button
{
Image = "kopper.png",
Title = "Won't Work!",
Href = "https://w.on-t.work/"
},
new Button
{
Image = "yassie.gif",
Title = "yassie_j",
Href = "https://labyrinth.zone/users/yassie_j"
},
new Button
{
Image = "zvava.png",
Title = "zvava.org",
Href = "https://zvava.org/"
}
];
public static readonly List<Button> Things =
[
new Button
{
Image = "archlinux.gif",
Title = "Arch Linux",
Href = "https://archlinux.org/"
},
new Button
{
Image = "iceshrimp_now_dark.webp",
Title = "Iceshrimp Now!"
},
new Button
{
Image = "join_the_fediverse.gif",
Title = "Join the Fediverse!!!"
},
new Button
{
Image = "linux_now_xenia.gif",
Title = "Linux NOW!"
},
new Button
{
Image = "made_with_my_own_two_paws.webp",
Title = "made with MY OWN TWO PAWS"
},
new Button
{
Image = "missing_texture.webp",
Title = "Missing Texture"
},
new Button
{
Image = "nonbinary.webp",
Title = "Non-binary"
},
new Button
{
Image = "powered_by_debian.webp",
Title = "Powered by Debian",
Href = "https://www.debian.org/"
},
new Button
{
Image = "scp.webp",
Title = "SCP",
Href = "https://scp-wiki.wikidot.com/"
},
new Button
{
Image = "team_fortress_get_it.gif",
Title = "Team Fortress GET IT!"
},
new Button
{
Image = "ublock_origin_now.webp",
Title = "uBlock Origin Now!",
Href = "https://ublockorigin.com/"
},
new Button
{
Image = "wii.webp",
Title = "Wii"
}
];
}

View file

@ -4,8 +4,8 @@ public class Link
{
public required string Name { get; set; }
public required string? Href { get; set; }
public required string Username { get; set; }
public required string Icon { get; set; }
public required string Description { get; set; }
public string? Icon { get; set; }
public bool IconInvert { get; set; }
public bool HomePageOnly { get; set; } = true;
}
@ -18,28 +18,28 @@ public static class Links
{
Name = "Bluesky",
Href = "https://bsky.app/profile/pancakes.gay",
Username = "@pancakes.gay",
Description = "@pancakes.gay",
Icon = "bluesky.webp"
},
new Link
{
Name = "Codeberg",
Href = "https://codeberg.org/pancakes",
Username = "pancakes",
Description = "pancakes",
Icon = "codeberg.webp"
},
new Link
{
Name = "Discord",
Href = null,
Username = "pancakesmeow",
Description = "pancakesmeow",
Icon = "discord.webp"
},
new Link
{
Name = "Fediverse",
Href = "https://shrimp.meow.company/@pancakes",
Username = "@pancakes@meow.company",
Description = "@pancakes@meow.company",
Icon = "iceshrimp.webp",
IconInvert = true,
HomePageOnly = false
@ -48,7 +48,7 @@ public static class Links
{
Name = "Forgejo",
Href = "https://git.meow.company/pancakes",
Username = "pancakes",
Description = "pancakes",
Icon = "forgejo.webp",
HomePageOnly = false
},
@ -56,7 +56,7 @@ public static class Links
{
Name = "GitHub",
Href = "https://github.com/pancakesmeow",
Username = "pancakesmeow",
Description = "pancakesmeow",
Icon = "github.webp",
IconInvert = true
},
@ -64,14 +64,14 @@ public static class Links
{
Name = "pronouns.cc",
Href = "https://pronouns.cc/@pancakes",
Username = "@pancakes",
Description = "@pancakes",
Icon = "pronouns_cc.webp"
},
new Link
{
Name = "Signal",
Href = null,
Username = "pancakes.80",
Description = "pancakes.80",
Icon = "signal.webp",
IconInvert = true
},
@ -79,9 +79,43 @@ public static class Links
{
Name = "YouTube",
Href = "https://youtube.com/@trypancakes",
Username = "@trypancakes",
Description = "@trypancakes",
Icon = "youtube.webp",
HomePageOnly = false
}
];
public static readonly List<Link> Projects =
[
new Link
{
Name = "fedifeeds",
Href = "https://git.meow.company/pancakes/fedifeeds",
Description =
"RSS feeds to Mastodon API. Supports multiple feeds per account, Markdown, and automatic threading (split into multiple notes to fit character limit)."
},
new Link
{
Name = "Iceshrimp Updates Bot",
Href = "https://shrimp.meow.company/@iceshrimp_updates",
Description =
"This bot automatically checks for Iceshrimp-js and Iceshrimp.NET releases and posts them. Based on fedifeeds."
},
new Link
{
Name = "SCP Wiki Pages Bot",
Href = "https://shrimp.meow.company/@scp_pages",
Description = "Posts random pages from the SCP Foundation wiki on the Fediverse."
}
];
public static readonly List<Link> Contributions =
[
new Link
{
Name = "Iceshrimp.NET",
Href = "https://iceshrimp.dev/iceshrimp/Iceshrimp.NET",
Description = "The .NET rewrite of the Iceshrimp server software for the Fediverse."
}
];
}

View file

@ -0,0 +1,23 @@
namespace PancakesWeb.Data;
public class Webring
{
public required string Name { get; set; }
public required string MainUrl { get; set; }
public required string PrevUrl { get; set; }
public required string NextUrl { get; set; }
}
public static class Webrings
{
public static readonly List<Webring> Rings =
[
new Webring
{
Name = "Fediring",
MainUrl = "https://fediring.net/",
PrevUrl = "https://fediring.net/previous?host=pancakes.gay",
NextUrl = "https://fediring.net/next?host=pancakes.gay"
}
];
}

View file

@ -6,4 +6,9 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Markdig" Version="0.42.0" />
<PackageReference Include="YamlDotNet" Version="16.3.0" />
</ItemGroup>
</Project>

View file

@ -5,6 +5,14 @@ var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddHttpClient();
builder.Configuration.AddEnvironmentVariables();
if (builder.Configuration["Ntfy:Url"] == null)
throw new Exception("Ntfy:Url is unset");
if (builder.Configuration["Ntfy:Topic"] == null)
throw new Exception("Ntfy:Topic is unset");
var app = builder.Build();

View file

@ -0,0 +1,88 @@
using System.Text.Json.Serialization;
namespace PancakesWeb.Schema;
public class NtfyRequest
{
public required string Topic { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Message { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Title { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<string>? Tags { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public int? Priority { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<NtfyAction>? Actions { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Click { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Attach { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Markdown { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Icon { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Filename { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Delay { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Email { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Call { get; set; }
}
[JsonDerivedType(typeof(NtfyViewAction), "view")]
[JsonDerivedType(typeof(NtfyBroadcastAction), "broadcast")]
[JsonDerivedType(typeof(NtfyHttpAction), "http")]
[JsonPolymorphic(TypeDiscriminatorPropertyName = "action",
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
public abstract class NtfyAction
{
public required string Label { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public bool? Clear { get; set; }
}
public class NtfyViewAction : NtfyAction
{
public required string Url { get; set; }
}
public class NtfyBroadcastAction : NtfyAction
{
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Intent { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string>? Extras { get; set; }
}
public class NtfyHttpAction : NtfyAction
{
public required string Url { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Method { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public Dictionary<string, string>? Headers { get; set; }
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public string? Body { get; set; }
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 762 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 758 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 561 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -0,0 +1,478 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xml:space="preserve"
width="88"
height="31"
version="1.1"
id="svg53"
sodipodi:docname="synth.download.svg"
inkscape:export-filename="synth.download.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
id="namedview53"
pagecolor="#505050"
bordercolor="#eeeeee"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#505050"
inkscape:zoom="6.4431818"
inkscape:cx="44"
inkscape:cy="15.520282"
inkscape:window-width="1366"
inkscape:window-height="742"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg53" /><defs
id="defs20"><linearGradient
id="d"><stop
offset="0"
stop-color="#ffcc67"
id="stop1" /><stop
offset="1"
stop-color="#e6b450"
id="stop2" /></linearGradient><linearGradient
id="a"><stop
offset="0"
stop-color="#fcff67"
stop-opacity=".8"
id="stop3" /><stop
offset="1"
stop-color="#fcff67"
stop-opacity=".15"
id="stop4" /></linearGradient><linearGradient
id="b"><stop
offset="0"
stop-color="#fcff67"
id="stop5" /><stop
offset="1"
stop-color="#fcff67"
stop-opacity=".446"
id="stop6" /></linearGradient><linearGradient
id="c"><stop
offset="0"
stop-color="#fcff67"
id="stop7" /><stop
offset="1"
stop-color="#fcff67"
stop-opacity=".161"
id="stop8" /></linearGradient><linearGradient
xlink:href="#a"
id="t"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="n"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="o"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="p"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="q"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="r"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#b"
id="s"
x1="43.328"
x2="41.277"
y1="20.257"
y2="-7.551"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="u"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="v"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="w"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="x"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="y"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="z"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="A"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="B"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="C"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#c"
id="D"
x1="156.858"
x2="155.607"
y1="18.337"
y2="-9.624"
gradientUnits="userSpaceOnUse" /><linearGradient
id="g"><stop
offset="0"
stop-color="#202026"
id="stop9" /><stop
offset="1"
stop-color="#34343e"
id="stop10" /></linearGradient><linearGradient
id="h"><stop
offset="0"
stop-color="#202026"
id="stop11" /><stop
offset="1"
stop-color="#383843"
id="stop12" /></linearGradient><linearGradient
id="f"><stop
offset="0"
stop-color="#ccc"
id="stop13" /><stop
offset="1"
stop-color="#f7f7f7"
id="stop14" /></linearGradient><linearGradient
xlink:href="#d"
id="M"
x1="4.854"
x2="65.285"
y1="15.839"
y2="15.112"
gradientUnits="userSpaceOnUse" /><linearGradient
id="l"><stop
offset="0"
stop-color="#242424"
id="stop15" /><stop
offset="1"
stop-color="#474747"
id="stop16" /></linearGradient><linearGradient
id="e"><stop
offset="0"
stop-color="#424242"
id="stop17" /><stop
offset="1"
stop-color="#525252"
id="stop18" /></linearGradient><linearGradient
xlink:href="#e"
id="G"
x1="41.278"
x2="366.983"
y1="271.883"
y2="271.883"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#f"
id="I"
x1="205.962"
x2="209.182"
y1="321.817"
y2="96.927"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#g"
id="J"
x1="212.845"
x2="275.596"
y1="245.785"
y2="82.997"
gradientUnits="userSpaceOnUse" /><linearGradient
xlink:href="#h"
id="K"
x1="104.883"
x2="131.463"
y1="254.656"
y2="132.872"
gradientTransform="translate(.436 -1.744)"
gradientUnits="userSpaceOnUse" /><clipPath
id="m"
clipPathUnits="userSpaceOnUse"><use
xlink:href="#i"
id="use18" /></clipPath><clipPath
id="E"
clipPathUnits="userSpaceOnUse"><use
xlink:href="#j"
id="use19" /></clipPath><clipPath
id="H"
clipPathUnits="userSpaceOnUse"><use
xlink:href="#k"
id="use20" /></clipPath><radialGradient
xlink:href="#l"
id="F"
cx="225.05"
cy="449.441"
r="144.721"
fx="225.05"
fy="449.441"
gradientTransform="matrix(1.664 -.182 .31213 2.85388 -288.97 -772.044)"
gradientUnits="userSpaceOnUse" /><filter
id="L"
width="1.5485064"
height="1.6621233"
x="-0.27425318"
y="-0.33106163"
color-interpolation-filters="sRGB"><feGaussianBlur
stdDeviation="9.479"
id="feGaussianBlur20" /></filter></defs><g
clip-path="url(#m)"
transform="skewX(-12.059) scale(1 .97793)"
id="g53"><path
id="i"
fill="#0d1017"
fill-rule="evenodd"
stroke-linecap="round"
d="M14.291 1.02h66.036a8.408 8.408 0 0 1 8.426 8.427v12.784a8.408 8.408 0 0 1-8.426 8.426H14.29a8.408 8.408 0 0 1-8.426-8.426V9.447a8.408 8.408 0 0 1 8.426-8.426z" /><g
fill-rule="evenodd"
id="g35"><g
fill="url(#n)"
transform="matrix(.63915 0 -.01988 .5832 6.463 2.456)"
id="g24"><path
fill="url(#o)"
d="M41.237 2.748c-.899 0-1.718.332-2.345.96l-1.328 1.329a3.26 3.26 0 0 0-.96 2.344v14.211c0 .625.165 1.187.558 1.58.393.392.955.556 1.58.556h.443c.624 0 1.188-.164 1.58-.556.393-.393.558-.955.558-1.58V8.55c0-.45.103-.692.247-.836.144-.144.386-.248.835-.248h2.658c.45 0 .69.104.834.248.144.144.248.387.248.836v13.042c0 .625.163 1.187.555 1.58.393.392.96.556 1.584.556h.402c.625 0 1.186-.164 1.579-.556.393-.393.557-.955.557-1.58V7.382c0-.9-.332-1.718-.96-2.345l-1.279-1.28a3.2 3.2 0 0 0-2.355-1.01Z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path20" /><path
fill="url(#p)"
d="M21.507 2.748c-.624 0-1.186.163-1.578.556-.393.393-.558.957-.558 1.582v7.729c0 .899.332 1.72.959 2.346l1.328 1.328a3.263 3.263 0 0 0 2.345.959h3.745c.45 0 .69.104.834.248.144.144.248.385.248.833v3.182c0 .45-.104.69-.248.834-.144.144-.385.248-.834.248h-5.876c-.625 0-1.19.165-1.583.558-.392.393-.557.957-.557 1.582v.44c0 .625.164 1.19.557 1.582.393.393.957.558 1.582.558h7.044c.9 0 1.718-.334 2.345-.961l1.328-1.328c.627-.627.96-1.447.96-2.346V4.886c0-.625-.163-1.19-.555-1.582-.393-.393-.96-.556-1.584-.556h-.44c-.625 0-1.19.163-1.583.556s-.555.957-.555 1.582l-.001 6.56c0 .45-.104.694-.248.838-.145.144-.386.248-.835.248h-2.575c-.45 0-.692-.104-.836-.248-.144-.144-.248-.389-.248-.838v-6.56c0-.625-.164-1.19-.556-1.582-.393-.393-.955-.556-1.58-.556Z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path21" /><path
fill="url(#q)"
d="M7.574 2.748c-.9 0-1.716.332-2.343.96L3.942 4.994a3.422 3.422 0 0 0-.754 1.09 3.06 3.06 0 0 0-.246 1.257v3.945c0 .484.075.93.241 1.33l.004.006.002.008c.175.385.46.728.828 1.034l1.204 1.042.004.004c.35.291.694.52 1.044.678l.02.009.019.006a3.59 3.59 0 0 0 1.227.194h3.782c.45 0 .694.104.838.248.144.144.248.385.248.833v1.21c0 .449-.105.69-.249.833-.144.144-.387.248-.836.248H8.742c-.438 0-.73-.083-.91-.194-.14-.1-.172-.154-.172-.325 0-.479-.278-.937-.682-1.199-.395-.272-.887-.374-1.456-.374H5.08c-.625 0-1.19.166-1.583.558-.392.393-.557.955-.557 1.58v.04c0 .899.333 1.717.96 2.344l1.33 1.33a3.257 3.257 0 0 0 2.343.959h4.913c.9 0 1.718-.333 2.345-.96l1.328-1.33a3.257 3.257 0 0 0 .959-2.344v-3.18c0-.9-.333-1.72-.96-2.347L14.832 12.2a3.263 3.263 0 0 0-2.347-.96H8.743c-.45 0-.692-.103-.836-.247-.144-.145-.248-.387-.248-.836v-1.65c0-.449.105-.691.249-.835.144-.144.385-.248.834-.248h2.576c.445 0 .725.088.865.186l.01.008.01.006c.18.115.2.16.2.32 0 .485.233.961.627 1.237l.01.006.009.007c.41.261.916.364 1.49.364h.443c.625 0 1.188-.164 1.58-.557.394-.392.558-.955.558-1.58l-.001-.08c0-.9-.332-1.72-.96-2.347l-1.288-1.288c-.63-.63-1.462-.96-2.384-.96Z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path22" /><path
fill="url(#r)"
d="M58.39-1.923c-.625 0-1.186.166-1.579.558-.392.393-.557.957-.557 1.582v1.45c0 .41-.1.654-.262.837-.15.146-.37.244-.78.244-.612 0-1.178.195-1.58.597-.387.387-.558.938-.558 1.54v.404c0 .625.165 1.187.558 1.58.392.392.955.556 1.58.556.408 0 .62.098.776.27l.008.011.01.008c.15.15.249.377.249.794v10.547c0 .9.331 1.718.958 2.345l1.328 1.33a3.263 3.263 0 0 0 2.345.959h2.779c.624 0 1.186-.164 1.579-.557.392-.392.559-.957.559-1.581v-.44c0-.625-.167-1.192-.56-1.584-.392-.393-.955-.557-1.58-.557h-1.608c-.449 0-.691-.104-.836-.248-.144-.144-.248-.384-.248-.833v-9.38c0-.45.103-.692.248-.836.144-.144.387-.248.836-.248h1.608c.625 0 1.188-.164 1.581-.556.393-.393.56-.955.56-1.58l-.002-.403c0-.625-.164-1.19-.557-1.582-.393-.393-.957-.556-1.582-.556h-1.609c-.449 0-.691-.104-.835-.248-.144-.144-.248-.385-.248-.834L60.97.217c0-.625-.163-1.189-.555-1.582-.393-.392-.956-.558-1.58-.558z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path23" /><path
fill="url(#s)"
d="M69.385-6.188c-.624 0-1.188.165-1.58.558-.393.393-.557.955-.557 1.58v25.642c0 .625.165 1.187.557 1.58.393.392.955.556 1.58.556h.443c.624 0 1.188-.164 1.58-.556.393-.393.558-.955.558-1.58V8.55c0-.45.103-.692.248-.836.144-.144.387-.248.836-.248h2.535c.449 0 .691.104.835.248.144.144.248.387.248.836v13.042c0 .625.163 1.187.556 1.58.392.392.957.556 1.581.556h.443c.625 0 1.188-.164 1.58-.556.393-.393.556-.955.556-1.58l.001-14.21c0-.9-.332-1.718-.958-2.345l-1.329-1.33a3.263 3.263 0 0 0-2.347-.96H73.05c-.45 0-.692-.103-.836-.247-.144-.144-.248-.385-.248-.834V-4.05c0-.625-.164-1.187-.557-1.58s-.955-.558-1.58-.558z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path24" /></g><g
fill="url(#t)"
transform="matrix(.61232 0 -.0138 .55872 -46.167 17.002)"
id="g34"><path
fill="url(#u)"
d="M227.473 21.87c-.632 0-1.222.32-1.74.839l-.284.28c-.518.52-.838 1.112-.838 1.744 0 .632.32 1.22.839 1.739l.283.283c.518.518 1.109.84 1.74.84.632 0 1.223-.322 1.741-.84l.282-.283c.518-.519.84-1.107.84-1.74 0-.631-.32-1.224-.84-1.742l-.282-.281c-.518-.519-1.109-.84-1.74-.84z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path25" /><path
fill="url(#v)"
d="M193.166 2.748c-.625 0-1.188.163-1.58.556-.393.393-.556.957-.556 1.582v.442c0 .625.162 1.187.555 1.58s.957.558 1.582.558h6.199c.449 0 .691.104.835.248.144.144.249.387.249.836l-.001.12c0 .448-.103.69-.247.835-.144.144-.387.248-.836.248h-3.825c-.899 0-1.716.332-2.343.96L191.908 12a3.422 3.422 0 0 0-.754 1.09 3.036 3.036 0 0 0-.245 1.255v4.67c0 .9.332 1.719.959 2.346l1.33 1.329c.627.627 1.444.96 2.343.96h7.489c.625 0 1.186-.166 1.579-.559.392-.393.557-.955.557-1.58V7.381c0-.9-.331-1.717-.958-2.344l-1.329-1.33a3.263 3.263 0 0 0-2.346-.96zm3.542 11.36h2.657c.449 0 .691.103.835.247.144.145.248.387.248.836l.001 3.02c0 .45-.104.69-.248.834-.144.144-.387.248-.836.248h-2.655c-.45 0-.692-.104-.836-.248-.144-.144-.248-.385-.248-.833l.001-3.021c0-.45.104-.691.248-.836.145-.144.384-.248.833-.248z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path26" /><path
fill="url(#w)"
d="M219.779-6.269c-.625 0-1.186.164-1.579.556-.393.393-.557.957-.557 1.582v5.797c0 .45-.105.69-.249.834-.144.144-.385.248-.834.248h-3.705c-.922 0-1.754.33-2.384.96l-1.29 1.287a3.262 3.262 0 0 0-.958 2.347v11.713c0 .9.332 1.718.959 2.345l1.33 1.33a3.259 3.259 0 0 0 2.343.959h7.367c.625 0 1.188-.164 1.58-.557.393-.392.558-.957.558-1.581l-.001-25.682c0-.625-.163-1.19-.555-1.582-.393-.392-.956-.556-1.58-.556zm-5.755 13.735h2.537c.449 0 .69.104.834.248.144.144.248.387.248.836v9.339c0 .449-.104.69-.248.833-.144.144-.386.248-.834.248h-2.537c-.45 0-.69-.104-.834-.248-.144-.144-.248-.384-.248-.833v-9.34c0-.448.104-.69.248-.835.144-.144.385-.248.834-.248z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path27" /><path
fill="url(#x)"
d="M178.67 2.748c-.9 0-1.719.332-2.345.96l-1.329 1.329a3.26 3.26 0 0 0-.96 2.344l.001 11.674c0 .9.332 1.718.959 2.345l1.329 1.33a3.26 3.26 0 0 0 2.344.959h4.993c.9 0 1.718-.333 2.345-.96l1.294-1.292.004-.006c.62-.652.95-1.476.95-2.376V7.381c0-.9-.329-1.724-.95-2.375l-.003-.007-1.295-1.292a3.259 3.259 0 0 0-2.345-.96zm1.169 4.718h2.616c.449 0 .69.104.833.248.145.144.249.387.249.836v9.339c0 .449-.104.69-.248.833-.145.144-.386.248-.835.248h-2.616c-.449 0-.691-.104-.835-.248-.144-.144-.248-.384-.248-.833v-9.34c0-.448.103-.69.248-.835.144-.144.387-.248.836-.248z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path28" /><path
fill="url(#y)"
d="M112.795 2.748c-.9 0-1.718.332-2.345.96l-1.328 1.329a3.26 3.26 0 0 0-.962 2.344v11.674c0 .9.334 1.718.961 2.345l1.329 1.33a3.262 3.262 0 0 0 2.345.959h4.99c.9 0 1.719-.333 2.345-.96l1.294-1.292.004-.006c.62-.652.95-1.476.95-2.376l.001-11.674c0-.9-.33-1.724-.95-2.375l-.006-.007-1.293-1.292a3.259 3.259 0 0 0-2.345-.96Zm1.165 4.718h2.618c.449 0 .69.104.834.248.144.144.25.387.25.836v9.339c0 .449-.106.69-.25.833-.144.144-.386.248-.834.248h-2.617c-.448 0-.69-.104-.833-.248-.144-.144-.248-.384-.248-.833v-9.34c0-.448.104-.69.248-.835.144-.144.383-.248.832-.248z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path29" /><path
fill="url(#z)"
d="M102.927-6.269c-.624 0-1.188.164-1.58.556-.393.393-.558.957-.558 1.582v5.797c0 .45-.104.69-.248.834-.144.144-.386.248-.834.248h-3.703c-.923 0-1.756.33-2.386.96L92.33 4.994a3.264 3.264 0 0 0-.96 2.347v11.713c0 .9.333 1.718.96 2.345l1.329 1.33a3.262 3.262 0 0 0 2.345.959h7.365c.624 0 1.19-.164 1.582-.557.393-.392.557-.957.557-1.581V-4.131c0-.625-.163-1.19-.556-1.582-.393-.392-.957-.556-1.582-.556zM97.17 7.466h2.539c.449 0 .69.104.833.248.144.144.248.387.248.836v9.339c0 .449-.103.69-.248.833-.144.144-.385.248-.834.248H97.17c-.449 0-.69-.104-.833-.248-.144-.144-.248-.384-.248-.833v-9.34c0-.448.103-.69.248-.835.144-.144.383-.248.832-.248z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path30" /><path
fill="url(#A)"
d="M154.065 2.748c-.9 0-1.72.332-2.347.96l-1.328 1.329a3.257 3.257 0 0 0-.958 2.344v14.211c0 .625.163 1.187.556 1.58.392.392.955.556 1.58.556h.444c.625 0 1.186-.164 1.579-.556.393-.393.557-.955.557-1.58V8.55c0-.45.106-.692.25-.836.144-.144.385-.248.834-.248h2.657c.449 0 .691.104.835.248.145.144.249.387.249.836v13.042c0 .625.164 1.187.557 1.58.393.392.955.556 1.58.556h.402c.625 0 1.188-.164 1.58-.556.393-.393.558-.955.558-1.58V7.382c0-.9-.335-1.718-.962-2.345l-1.277-1.28a3.2 3.2 0 0 0-2.355-1.01z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path31" /><path
fill="url(#B)"
d="M126.245 2.748c-.573 0-1.083.122-1.463.45-.38.329-.556.836-.556 1.367 0 .237.01.42.057.608l-.006-.02 3.424 16.75v.005c.126.563.375 1.043.771 1.375.397.332.915.486 1.475.486h2.576c.584 0 1.12-.154 1.522-.5.402-.346.636-.85.73-1.43v-.005l1.007-6.656 1.01 6.656v.004c.094.581.33 1.085.731 1.43.402.347.937.501 1.522.501h2.537c.56 0 1.078-.154 1.475-.486.396-.332.645-.812.77-1.375l.002-.008 3.297-16.727a2.3 2.3 0 0 0 .056-.568c0-.514-.204-1.004-.572-1.342-.366-.359-.88-.515-1.447-.515h-.442c-.582 0-1.113.149-1.516.486-.403.337-.64.836-.734 1.406l-2.22 12.44-1.856-9.355v-.002c-.11-.566-.353-1.056-.753-1.39-.4-.334-.924-.486-1.495-.486h-.847c-.593 0-1.13.148-1.541.484-.41.336-.654.836-.75 1.408l-1.775 9.457-2.256-12.556v-.002c-.096-.572-.34-1.073-.75-1.408-.41-.336-.947-.482-1.54-.482Z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path32" /><path
fill="url(#C)"
d="M168.844-6.308c-.625 0-1.188.163-1.581.556s-.557.955-.557 1.58l-.001 25.723c0 .624.166 1.189.559 1.581.392.393.955.557 1.58.557h.402c.624 0 1.188-.164 1.58-.557.393-.392.558-.957.558-1.581V-4.172c0-.625-.165-1.187-.558-1.58s-.955-.556-1.58-.556z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path33" /><path
fill="url(#D)"
d="M227.272-10.094c-.624 0-1.188.163-1.58.556-.393.393-.558.96-.558 1.584v27.01c0 .624.165 1.188.557 1.58.393.393.955.557 1.58.557h.402c.625 0 1.19-.164 1.583-.556.392-.393.557-.957.557-1.582v-27.01c0-.624-.164-1.19-.557-1.583s-.957-.556-1.582-.556z"
style="-inkscape-font-specification:&quot;ZCOOL QingKe HuangYou&quot;"
id="path34" /></g></g><path
fill-rule="evenodd"
d="M78.308.15c-.212-.039-.357-.031-.518-.046a8.622 8.622 0 0 0-.916-.03 25.9 25.9 0 0 0-2.008.12 57.58 57.58 0 0 0-4.213.549c-3.792.651-10.016 2.716-10.016 2.716l.255-.066c-.395.079-.807.363-1.13.727-1.677.564-3.178 1.268-4.603 1.755-1.55.529-4.682.46-6.452 3.007-1.309 1.884-1.275 4.372-.59 6.62a6.573 6.573 0 0 0 1.404 2.478l.023.05c.352.706.624 1.55 1.93 2.596 3.64 2.916 7.744 3.08 10.602 3.451.844.11 1.66.7 2.095 1.392.817 1.49.431 3.853.081 7.053-.155 1.413-.26 2.616-.26 2.616-.137 1.56 1.218 3.17 2.842 3.378l19.897 2.547c1.4.179 2.463-.768 2.415-2.152 0 0-.123-4.66-.622-7.564-.55-3.196-1.16-6.683-2.709-10.22-.967-2.21-2.562-4.714-4.012-6.89.091-.006.217-.013.296-.02.13-.01.243-.02.353-.032.11-.013.092.036.52-.107.798-.266.719-.392.942-.57.222-.178.46-.378.708-.58.499-.407 1.094-.826 1.317-.928.095-.044.734-.171 1.456-.266.362-.047.748-.096 1.178-.212.43-.116.973-.246 1.468-.898.711-.937.432-1.634.318-2.158a6.854 6.854 0 0 0-.507-1.456c-.435-.938-.814-1.857-2.162-2.801-1.02-.714-1.308-.607-1.84-.726a22.177 22.177 0 0 0-1.774-.304 58.333 58.333 0 0 0-2.252-.255l-.075-.113c-.54-.792-.95-1.577-2.525-2.331-.426-.204-.704-.29-.916-.33z"
id="path35" /><g
stroke-linecap="round"
stroke-linejoin="round"
clip-path="url(#E)"
transform="matrix(.08334 .0108 .00676 .08292 46.061 -6.178)"
id="g42"><path
id="j"
fill="url(#F)"
fill-rule="evenodd"
d="m158.18 302.059 197.116-139.576s54.127 75.593 70.295 118.615c13.372 35.583 17.38 74.26 20.863 112.113 2.653 28.833-.04 86.864-.04 86.864l-238.762.38s2.426-14.561 5.632-31.677c6.894-36.811 18.977-74.204 3.198-105.74-10.63-21.242-58.301-40.98-58.301-40.98z" /><path
fill="none"
stroke="#121212"
stroke-width="13.018"
d="m156.547 285.177 175.02-103.926s51.517 112.76 20.018 153.723c-41.976 54.589-204.835 26.833-204.835 26.833z"
id="path36" /><path
fill="none"
stroke="#121212"
stroke-width="13.018"
d="m77.228 260.86 273.936-1.596s39.458 117.524 3.853 154.973C307.568 464.143 148.5 419.53 148.5 419.53z"
id="path37" /><g
fill="#ccc"
fill-rule="evenodd"
stroke="#adadad"
stroke-width="20"
id="g41"><path
d="m329.334 275.894 32.54-146.372 85.534 123.915s-59.658 82.003-98.498 70.776c-16.694-4.826-19.576-48.319-19.576-48.319z"
id="path38" /><path
d="M360.355 327.998c5.868-13.773 28.578-12.385 37.9-24.101 15.045-18.913 24.155-68.36 24.155-68.36l42.165 110.662s-61.144 59.63-91.57 44.574c-19.133-9.466-21.017-43.138-12.65-62.775z"
id="path39" /><path
d="M374.963 397.41c7.604-12.897 29.943-8.577 40.704-18.986 17.37-16.803 32.813-64.653 32.813-64.653l27.465 115.195s-68.357 51.2-96.576 32.329c-17.743-11.867-15.247-45.498-4.406-63.886z"
id="path40" /><path
d="M377.337 460.965c10.037-11.108 31.055-2.394 43.685-10.432 20.389-12.976 45.118-56.754 45.118-56.754l3.794 118.362s-77.24 36.444-101.099 12.294c-15.002-15.185-5.808-47.632 8.502-63.47z"
id="path41" /></g></g><g
fill-rule="evenodd"
stroke="#b2b2b2"
id="g52"><path
fill="url(#G)"
stroke="none"
d="M47.278 229.89h319.705s-4.6 38.81-18.235 50.311c-47.778 40.304-113.126 33.103-176.354 33.282-17.56.05-53.24 1.519-78.201-6.28-18.237-5.7-42.664-12.202-49.583-28.76-1.706-4.083-3.332-12.757-3.332-12.757z"
transform="matrix(.08334 .0108 .00676 .08292 46.061 -6.178)"
id="path42" /><path
fill="#999"
stroke="none"
d="M61.94 6.064s6.341-2.062 9.699-2.638c2.221-.382 6.225-.84 6.863-.535.704.337 1.718 1.861 1.554 2.238-.223.51-1.697.686-2.39 1.236-.691.548-1.487 1.875-1.71 2.014-.319.197-10.006 2.68-10.006 2.68s-4.715-4.855-4.01-4.995z"
id="path43" /><g
clip-path="url(#H)"
transform="matrix(.08334 .0108 .00676 .08292 46.061 -6.178)"
id="g48"><path
id="k"
fill="url(#I)"
stroke="none"
d="M451.527 80.508c-24.517-.201-58.666 1.671-81.673 3.828-49.924 4.68-138.985 24.158-157.57 28.287a143.46 143.46 0 0 0-7.159 1.572l-.207.045c-.04.005-.061.033-.1.04-33.16 8.303-64.012 27.909-95.324 42.327-24.309 11.194-55.203 16.256-70.674 38.094-11.765 16.607-14.097 40.486-9.504 60.313 2.033 8.773 7.888 17.327 15.512 22.12 7.133 4.485 24.942 4.108 24.942 4.108s14.083 30.055 26.47 30.055c11.852 0 26.469-29.947 26.469-29.947l154.846-.198s15.208-25.75 27.041-34.176c19.23-13.694 66.855-5.524 66.855-23.363 0-20.22.802-24.834-3.48-39.392a285.822 285.822 0 0 0-3.13-9.883c26.755-4.742 49.73-8.936 51.778-9.865 4.142-1.88 22.513-21.274 35.828-28.895 13.353-7.642 37.435-8.754 43.182-16.3 4.243-5.573-5.175-29.893-14.86-35.784-3.287-2-14.531-2.866-29.242-2.986z" /><g
stroke="none"
id="g45"><path
fill="url(#J)"
d="m129.787 126.622 173.997-47.788s30.029 94.415 11.284 116.769c-14.792 17.64-136.494 46.484-151.18 40.108-7.74-3.36-16.374-16.952-25.253-51.287-7.873-30.446-8.848-57.802-8.848-57.802z"
id="path44" /><path
fill="url(#K)"
d="m73.563 138.422 43.267-17.926s-1.766 9.63-.353 31.264c1.778 27.2 6.02 38.8 10.186 53.19 6.079 21 12.952 32.068 10.24 35.97-4.186 6.022-26.623 13.02-33.626 8.751-6.732-4.103-29.714-111.25-29.714-111.25z"
id="path45" /></g><g
fill="#fea737"
stroke="#dbdbdb"
stroke-width="5.118"
transform="matrix(1.23897 -.09348 .08855 1.2754 -103.893 -40.789)"
id="g47"><rect
width="18.502"
height="33.168"
x="350.327"
y="131.413"
ry="9.251"
id="rect45" /><rect
width="18.502"
height="33.168"
x="381.124"
y="130.211"
ry="9.251"
id="rect46" /><rect
width="18.502"
height="33.168"
x="411.86"
y="129.85"
ry="9.251"
id="rect47" /></g></g><g
fill="#fda737"
stroke="none"
id="g49"><path
fill-opacity=".5"
d="M210.998 152.104c-6.333.132-13.08 2.133-19.528 5.37a11.127 11.127 0 0 1 3.393 8.024v34.088c0 6.188-4.98 11.169-11.168 11.169a11.144 11.144 0 0 1-11.17-11.169V171.65c-11.282 12.036-16.886 27.282-8.135 38.683 14.328 18.666 53.496 9.457 69.973-4.783 14.814-12.801 11.261-44.895-15.101-52.436-2.624-.75-5.402-1.069-8.264-1.01z"
filter="url(#L)"
transform="matrix(.08334 .0108 .00676 .08292 46.061 -6.178)"
id="path48" /><path
d="M64.675 8.713c-.527-.058-1.076.036-1.591.234.188.191.315.44.337.702l.23 2.827c.042.513-.34.872-.855.805-.516-.066-.965-.533-1.006-1.046l-.19-2.317c-.858.876-1.222 2.08-.416 3.12 1.32 1.702 4.523 1.362 5.8.359 1.148-.902.635-3.601-1.613-4.511a2.601 2.601 0 0 0-.696-.173z"
id="path49" /></g><g
fill-opacity=".9"
stroke="none"
transform="matrix(.0825 .03055 -.01362 .07784 46.019 -6.07)"
id="g51"><circle
cx="116.027"
cy="212.236"
r="8.19"
id="circle49" /><circle
cx="85.813"
cy="223.026"
r="8.19"
id="circle50" /><circle
cx="96.129"
cy="190.225"
r="8.19"
id="circle51" /></g></g></g><path
fill="url(#M)"
fill-rule="evenodd"
d="M14.291.01c-5.21 0-9.437 4.226-9.437 9.437V22.23c0 5.211 4.226 9.438 9.437 9.438h66.035c5.211 0 9.438-4.227 9.438-9.438V9.447c0-5.21-4.227-9.437-9.438-9.437zm0 2.021h66.035a7.378 7.378 0 0 1 7.416 7.416V22.23a7.378 7.378 0 0 1-7.416 7.416H14.291a7.377 7.377 0 0 1-7.414-7.416V9.447a7.377 7.377 0 0 1 7.414-7.416z"
transform="skewX(-12.059) scale(1 .97793)"
id="path53" /></svg>

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -0,0 +1,17 @@
function addCopyButtons() {
for (const code of document.getElementsByTagName("code")) {
if (code.parentElement.tagName !== "PRE") continue;
const button = document.createElement("button");
button.className = "copy-code-button";
button.innerText = "Copy";
button.addEventListener("click", ev => {
navigator.clipboard.writeText(code.innerText);
});
code.parentElement.appendChild(button);
}
}
document.addEventListener("DOMContentLoaded", addCopyButtons);

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

View file

@ -0,0 +1,153 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1792 1792">
<style>
/* Eyes */
@keyframes firefish-logo-blink {
95% {
transform: scaleY(1);
}
97.5% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
#firefish-logo-eye-l,
#firefish-logo-eye-r {
transform-origin: center;
transform-box: fill-box;
animation-name: firefish-logo-blink;
animation-duration: 3s;
animation-iteration-count: infinite;
animation-timing-function: ease-in-out;
}
/* Bubbles */
@keyframes firefish-logo-bloop {
0% {
transform: translateY(400px);
opacity: 0;
}
20% {
opacity: 0;
}
30% {
opacity: 1;
}
80% {
opacity: 1;
}
90% {
opacity: 0;
}
100% {
transform: translateY(-400px);
}
}
.firefish-logo-bubble {
animation-name: firefish-logo-bloop;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-duration: 2s;
animation-timing-function: ease-out;
fill: #31748f;
opacity: 0;
}
#firefish-logo-bubble-1 {
animation-delay: 0s;
}
#firefish-logo-bubble-2 {
animation-delay: .2s;
}
#firefish-logo-bubble-3 {
animation-delay: .6s;
}
#firefish-logo-bubble-4 {
animation-delay: .8s;
}
#firefish-logo-bubble-5 {
animation-delay: 1s;
}
</style>
<defs>
<linearGradient id="firefish-logo-linear-gradient" x1="26.41" y1="1765.71" x2="1492.49" y2="299.62"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#ec476d" />
<stop offset="1" stop-color="#f6ae4a" />
</linearGradient>
<linearGradient id="firefish-logo-linear-gradient-purple" x1="512" y1="1788" x2="512" y2="1398.68"
gradientUnits="userSpaceOnUse">
<stop offset="0" stop-color="#db44db" stop-opacity=".3" />
<stop offset=".11" stop-color="#db44db" stop-opacity=".21" />
<stop offset=".27" stop-color="#db44db" stop-opacity=".12" />
<stop offset=".44" stop-color="#db44db" stop-opacity=".05" />
<stop offset=".65" stop-color="#db44db" stop-opacity=".01" />
<stop offset="1" stop-color="#db44db" stop-opacity="0" />
</linearGradient>
<linearGradient id="firefish-logo-linear-gradient-3" x1="-351.25" y1="1382.82" x2="1150.74" y2="-119.17"
xlink:href="#firefish-logo-linear-gradient" />
<linearGradient id="firefish-logo-linear-gradient-4" x1="412.3" y1="2147.8" x2="1915.31" y2="644.8"
xlink:href="#firefish-logo-linear-gradient" />
<linearGradient id="firefish-logo-linear-gradient-5" x1="28.25" y1="1763.75" x2="1531.25" y2="260.75"
xlink:href="#firefish-logo-linear-gradient" />
</defs>
<!-- Bubbles -->
<circle id="firefish-logo-bubble-1" class="firefish-logo-bubble" cx="205" cy="500" r="40" />
<circle id="firefish-logo-bubble-2" class="firefish-logo-bubble" cx="140" cy="500" r="80" />
<circle id="firefish-logo-bubble-3" class="firefish-logo-bubble" cx="95" cy="500" r="70" />
<circle id="firefish-logo-bubble-4" class="firefish-logo-bubble" cx="200" cy="500" r="40" />
<circle id="firefish-logo-bubble-5" class="firefish-logo-bubble" cx="95" cy="500" r="20" />
<g id="firefish-logo-firefish">
<g id="firefish-logo-fish">
<g id="firefish-logo-body">
<path
d="m928,768.11H96c-53.02,0-96,42.98-96,96v832c0,53.02,42.98,96,96,96h832c53.02,0,96-42.98,96-96v-832c0-53.02-42.98-96-96-96Z"
fill="url(#firefish-logo-linear-gradient)" />
</g>
<g id="firefish-logo-body-purple-grad">
<path
d="m928,768.11H96C42.98,768.11,0,811.09,0,864.11v832c0,53.02,42.98,96,96,96h832c53.02,0,96-42.98,96-96v-832c0-53.02-42.98-96-96-96Z"
fill="url(#firefish-logo-linear-gradient-purple)" />
</g>
<g id="firefish-logo-fin-l">
<path
d="m927.66,639.79h-439.94c-52.98,0-95.94-42.95-95.94-95.94V103.91c0-85.47,103.34-128.27,163.77-67.84l439.94,439.94c60.44,60.44,17.63,163.77-67.84,163.77Z"
fill="url(#firefish-logo-linear-gradient-3)" />
</g>
<g id="firefish-logo-fin-b">
<path
d="m1694.66,1408.11h-445.51c-53.65,0-97.15-43.5-97.15-97.15v-445.51c0-86.55,104.64-129.9,165.84-68.7l445.51,445.51c61.2,61.2,17.86,165.84-68.7,165.84Z"
fill="url(#firefish-logo-linear-gradient-4)" />
</g>
<g id="firefish-logo-fin-r">
<path
d="m1694.66,640h-445.51c-53.65,0-97.15-43.5-97.15-97.15V97.34c0-86.55,104.64-129.9,165.84-68.7l445.51,445.51c61.2,61.2,17.86,165.84-68.7,165.84Z"
fill="url(#firefish-logo-linear-gradient-5)" />
</g>
<g id="firefish-logo-eye-l">
<circle cx="256" cy="1408.11" r="128" fill="#fff" />
</g>
<g id="firefish-logo-eye-r">
<circle cx="576" cy="1408.11" r="128" fill="#fff" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

View file

@ -0,0 +1 @@
<svg width="1000" height="1000" viewBox="0 0 264.583 264.583" xml:space="preserve" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a"><stop style="stop-color:#92191c;stop-opacity:1" offset="0"/><stop style="stop-color:#a11c38;stop-opacity:1" offset="1"/></linearGradient><linearGradient xlink:href="#a" id="b" gradientUnits="userSpaceOnUse" x1="100.048" y1="229.172" x2="97.548" y2="233.865"/></defs><path style="opacity:1;fill:url(#b);fill-opacity:1;stroke-width:1.47155;stroke-linecap:round;stroke-linejoin:round" d="M98.99 228.83a7.578 7.578 0 0 1-1.807-.246c.031.156.115.316.2.451.07.11.186.195.284.28a2 2 0 0 0-.185 1.383.853.853 0 0 0-.49.283.93.93 0 0 0-.214.532c-.014.194.029.39.113.566.087.181.22.342.388.452.168.11.372.167.572.149a.829.829 0 0 0 .424-.165c.425.21.942.225 1.378.039.402-.172.729-.51.887-.917l.478-.203a.626.626 0 0 0 .13-.069.276.276 0 0 0 .092-.114.267.267 0 0 0 .014-.146.562.562 0 0 0-.049-.14l-.1-.22a.664.664 0 0 0 .066-.035.405.405 0 0 0 .092-.07.25.25 0 0 0 .058-.1.24.24 0 0 0 .004-.114.467.467 0 0 0-.04-.108l-.171-.374a.545.545 0 0 0-.069-.119.257.257 0 0 0-.11-.08.263.263 0 0 0-.135-.01.624.624 0 0 0-.13.043l-.203.085c-.024-.121-.041-.24-.091-.353-.044-.098-.055-.124-.117-.212a.928.928 0 0 0 .341-.27.974.974 0 0 0 .194-.438c-.633.195-1.264.243-1.805.24z" transform="translate(-5365.976 -12670.019) scale(55.51197)"/><path style="opacity:1;fill:#fff;fill-opacity:1;stroke-width:1.47155;stroke-linecap:round;stroke-linejoin:round" d="M100.778 230.08v.001c-.008 0-.013.002-.02.003a.29.29 0 0 0-.038.014l-1.987.872a.364.364 0 0 1-.108.36c-.046.04-.1.07-.16.087a.597.597 0 0 1-.18.024.795.795 0 0 1-.385-.105.83.83 0 0 1-.317-.325.517.517 0 0 0-.35.15.599.599 0 0 0-.164.393.85.85 0 0 0 .097.418.816.816 0 0 0 .326.354c.071.04.15.065.232.072a.5.5 0 0 0 .24-.036.56.56 0 0 0 .286-.304.69.69 0 0 0 .042-.387l1.236-.546.18.408a.37.37 0 0 0 .02.035c.007.01.018.02.029.025.012.006.026.007.039.006a.09.09 0 0 0 .037-.012l.268-.12a.199.199 0 0 0 .031-.018.063.063 0 0 0 .02-.03.062.062 0 0 0 0-.035c-.002-.012-.008-.022-.012-.033l-.186-.417.31-.135.19.444a.14.14 0 0 0 .019.033c.008.011.018.019.031.023a.07.07 0 0 0 .038 0 .154.154 0 0 0 .036-.012l.27-.118c.01-.005.022-.01.03-.018a.055.055 0 0 0 .018-.03.07.07 0 0 0-.001-.035.152.152 0 0 0-.013-.033l-.2-.436.25-.112.03-.015a.07.07 0 0 0 .022-.023.05.05 0 0 0 .007-.032c0-.01-.006-.02-.01-.03l-.128-.267c-.006-.012-.011-.024-.021-.034a.077.077 0 0 0-.054-.023zm-3.136 1.36h.01c.091 0 .183.07.23.171.061.135.024.285-.083.334-.107.048-.243-.022-.305-.157-.061-.135-.024-.284.082-.333a.206.206 0 0 1 .066-.015zM99.434 229.088a.552.552 0 0 0-.476.278.535.535 0 0 0-.514-.273.546.546 0 0 0-.494.53.625.625 0 0 0 .09.338c.06.101.146.187.244.252.198.13.44.176.676.177.244.002.496-.043.7-.178a.773.773 0 0 0 .248-.264c.06-.107.09-.23.08-.352a.559.559 0 0 0-.535-.508h-.019zm-.946.173a.4.4 0 0 1 .268.104c.077.07.125.173.129.277l-.267.006a.135.135 0 0 0-.032-.108.136.136 0 0 0-.103-.045.135.135 0 0 0-.1.048.136.136 0 0 0-.028.108l-.262.006c0-.1.04-.199.11-.27a.404.404 0 0 1 .284-.124zm.956 0a.4.4 0 0 1 .397.38l-.267.007a.135.135 0 0 0-.031-.108.137.137 0 0 0-.103-.045.135.135 0 0 0-.1.048.135.135 0 0 0-.029.108l-.261.006c0-.1.04-.199.109-.27a.404.404 0 0 1 .285-.124zm-.48.422.289.423-.262.225-.294-.203z" transform="translate(-5365.976 -12670.019) scale(55.51197)"/></svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7 KiB

View file

@ -20,6 +20,7 @@
--background: light-dark(#f3dffc, #16051d);
--foreground: light-dark(#faf0ff, #08020b);
--text: light-dark(#0f0f0f, #efefef);
--border-color: color-mix(in srgb, var(--background) 70%, var(--text));
--success: #9ece6a;
--warn: #e0af68;
@ -60,8 +61,191 @@ a:visited {
color: var(--accent);
}
a[target="_blank"]:not(:has(>img))::after {
display: inline-block;
margin-left: 0.25em;
content: "↗";
}
abbr {
cursor: help;
}
details {
margin: 1rem 0;
padding: 0 1rem;
border: 2px dotted var(--border-color);
border-radius: var(--radius);
summary {
padding: 1rem 0;
font-weight: bold;
cursor: pointer;
}
}
details[open] {
padding-bottom: 1rem;
}
article {
font-size: large;
line-height: 1.4;
}
hr {
color: var(--border-color);
}
code {
padding: 0.1em 0.2em;
font-family: var(--font-mono), monospace;
background-color: var(--foreground);
border: 1px solid var(--border-color);
border-radius: var(--radius);
}
pre {
position: relative;
}
pre code {
display: block;
padding: 1rem;
overflow-x: auto;
text-wrap: nowrap;
line-height: 1;
}
figcaption {
text-align: center;
}
img, video {
max-width: 100%;
}
table {
display: block;
max-width: 100%;
overflow-x: auto;
}
table:not(.no-rows) tbody tr:nth-child(2n-1) {
background-color: var(--foreground);
}
button, input[type=submit] {
display: inline-block;
padding: 0.5em 1em;
font-size: small;
font-weight: bold;
line-height: 1;
background-color: var(--accent);
color: var(--background);
border: none;
border-radius: var(--radius);
cursor: pointer;
transition: 0.2s background-color;
}
button:hover, button:focus, input[type=submit]:hover, input[type=submit]:focus {
background-color: color-mix(in srgb, var(--accent) 80%, var(--background));
}
input, textarea {
padding: 0.5rem;
outline: none;
background-color: var(--foreground);
color: var(--text);
border: 1px solid var(--border-color);
border-radius: var(--radius);
transition: 0.2s border;
}
input, textarea:focus {
border-color: var(--accent);
}
.copy-code-button {
position: absolute;
top: 0;
right: 0;
opacity: 0;
font-size: x-small;
border-top-left-radius: 0;
border-bottom-right-radius: 0;
transition: 0.2s opacity;
}
pre code:hover + .copy-code-button, .copy-code-button:hover {
opacity: 1;
}
.container {
margin-left: auto;
margin-right: auto;
padding: 1rem;
max-width: var(--container-width);
}
.card {
display: block;
padding: 1rem;
background-color: var(--foreground);
border-radius: var(--radius);
}
.img-invert {
filter: invert() hue-rotate(180deg);
}
.no-decorations {
text-decoration: none;
}
.validation-errors {
color: var(--error);
}
.inline-icon {
display: inline-block;
vertical-align: middle;
height: 1.5em;
}
@keyframes title-fade-in {
0% {
margin-top: 0;
opacity: 0;
}
}
@media (pointer: coarse), (hover: none) {
[title]:hover::after, [title]:focus::after {
display: block;
position: absolute;
width: max-content;
margin-top: 0.5rem;
padding: 0.5rem;
content: attr(title);
z-index: +10;
background-color: var(--foreground);
border: 1px solid var(--border-color);
border-radius: var(--radius);
animation: 0.3s ease-out title-fade-in;
}
.copy-code-button {
opacity: 1;
}
}