initial commit
14
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
# dotenv environment variable files
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# Fresh build directory
|
||||||
|
_fresh/
|
||||||
|
# npm + other dependencies
|
||||||
|
node_modules/
|
||||||
|
vendor/
|
||||||
|
|
||||||
|
sync.sh
|
||||||
17
README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Fresh project
|
||||||
|
|
||||||
|
Your new Fresh project is ready to go. You can follow the Fresh "Getting
|
||||||
|
Started" guide here: https://fresh.deno.dev/docs/getting-started
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
|
||||||
|
Make sure to install Deno:
|
||||||
|
https://docs.deno.com/runtime/getting_started/installation
|
||||||
|
|
||||||
|
Then start the project in development mode:
|
||||||
|
|
||||||
|
```
|
||||||
|
deno task dev
|
||||||
|
```
|
||||||
|
|
||||||
|
This will watch the project directory and restart as necessary.
|
||||||
BIN
assets/favicon.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
552
assets/styles.css
Normal file
|
|
@ -0,0 +1,552 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: "Jersey 15";
|
||||||
|
src: url("/fonts/jersey15.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "Share Tech";
|
||||||
|
src: url("/fonts/str.ttf") format("truetype");
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--theme-bg: #16191c;
|
||||||
|
--theme-bg-secondary: #252a2e;
|
||||||
|
--theme-bg-tertiary: #1b1f22;
|
||||||
|
--theme-fg: #a3aaaf;
|
||||||
|
--theme-fg-alt-rgb: 106, 111, 121;
|
||||||
|
--theme-fg-alt: rgb(var(--theme-fg-alt-rgb));
|
||||||
|
--theme-accent-rgb: 163, 170, 175; /* 147, 184, 217; */
|
||||||
|
--theme-accent: rgb(var(--theme-accent-rgb));
|
||||||
|
--theme-accent-alt: #2c3237; /* #2a353e; */
|
||||||
|
--theme-accent-third: rgb(140, 155, 189); /* #2a353e; */
|
||||||
|
--theme-accent-title-rgb: var(--theme-accent-rgb);
|
||||||
|
--theme-accent-title: rgb(var(--theme-accent-title-rgb));
|
||||||
|
--theme-border-rgb: 56, 60, 66;
|
||||||
|
--theme-border: rgb(var(--theme-border-rgb));
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.5;
|
||||||
|
font-family: "Jersey 15", sans-serif, "Share Tech", monospace;
|
||||||
|
font-size: 22px;
|
||||||
|
-webkit-font-smoothing: none;
|
||||||
|
font-smooth: never;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 16px;
|
||||||
|
margin: auto;
|
||||||
|
background-color: var(--theme-bg);
|
||||||
|
color: var(--theme-fg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2.4rem;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 8px),
|
||||||
|
8px calc(100% - 8px),
|
||||||
|
8px 100%,
|
||||||
|
calc(100% - 8px) 100%,
|
||||||
|
calc(100% - 8px) calc(100% - 8px),
|
||||||
|
100% calc(100% - 8px),
|
||||||
|
100% 8px,
|
||||||
|
calc(100% - 8px) 8px,
|
||||||
|
calc(100% - 8px) 0px,
|
||||||
|
8px 0px,
|
||||||
|
8px 8px,
|
||||||
|
0px 8px
|
||||||
|
);
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--theme-bg-secondary);
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section,
|
||||||
|
#header {
|
||||||
|
max-width: 36rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt {
|
||||||
|
color: var(--theme-fg-alt);
|
||||||
|
}
|
||||||
|
|
||||||
|
.alt-font {
|
||||||
|
font-size: 16px;
|
||||||
|
font-family: "Share Tech", monospace;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
hyphens: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intro > .logo {
|
||||||
|
height: 1em;
|
||||||
|
margin-right: 0.5ch;
|
||||||
|
vertical-align: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.centered {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
color: var(--theme-accent-title);
|
||||||
|
padding-bottom: 0.25em;
|
||||||
|
border-bottom: 2px solid var(--theme-border);
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 0px;
|
||||||
|
padding-bottom: 0.5rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-1 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-3 {
|
||||||
|
color: var(--theme-fg-alt);
|
||||||
|
padding-bottom: 0.16rem;
|
||||||
|
border-bottom: none;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title.title-3::after {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(var(--theme-border-rgb), 0) 0,
|
||||||
|
rgba(var(--theme-fg-alt-rgb), 255)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.title::after {
|
||||||
|
background-image: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(var(--theme-accent-title-rgb), 0) 0,
|
||||||
|
rgba(var(--theme-accent-title-rgb), 255)
|
||||||
|
);
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
flex: 1;
|
||||||
|
margin-left: 0.5ch;
|
||||||
|
height: 0.3rem;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title .icon {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
shape-rendering: crispEdges;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
background-color: var(--theme-accent-alt);
|
||||||
|
color: var(--theme-accent);
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: fit-content;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 6px),
|
||||||
|
6px calc(100% - 6px),
|
||||||
|
6px 100%,
|
||||||
|
calc(100% - 6px) 100%,
|
||||||
|
calc(100% - 6px) calc(100% - 6px),
|
||||||
|
100% calc(100% - 6px),
|
||||||
|
100% 6px,
|
||||||
|
calc(100% - 6px) 6px,
|
||||||
|
calc(100% - 6px) 0px,
|
||||||
|
6px 0px,
|
||||||
|
6px 6px,
|
||||||
|
0px 6px
|
||||||
|
);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
a:visited,
|
||||||
|
a:active {
|
||||||
|
color: var(--theme-accent-third);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: 2px underline;
|
||||||
|
text-underline-offset: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: space-between;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header,
|
||||||
|
#header #left,
|
||||||
|
#header #links {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header #left,
|
||||||
|
#header #links {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header img {
|
||||||
|
width: auto;
|
||||||
|
height: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
nav .module {
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 6px),
|
||||||
|
6px calc(100% - 6px),
|
||||||
|
6px 100%,
|
||||||
|
calc(100% - 6px) 100%,
|
||||||
|
calc(100% - 6px) calc(100% - 6px),
|
||||||
|
100% calc(100% - 6px),
|
||||||
|
100% 6px,
|
||||||
|
calc(100% - 6px) 6px,
|
||||||
|
calc(100% - 6px) 0px,
|
||||||
|
6px 0px,
|
||||||
|
6px 6px,
|
||||||
|
0px 6px
|
||||||
|
);
|
||||||
|
padding: 0.5em 1.25em;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background-color: var(--theme-bg-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#logo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.35rem;
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.full {
|
||||||
|
flex-direction: column;
|
||||||
|
& > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
& .box {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 {
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 a {
|
||||||
|
display: flex;
|
||||||
|
width: 88px;
|
||||||
|
height: 31px;
|
||||||
|
background: var(--theme-bg-tertiary);
|
||||||
|
color: var(--theme-fg-alt);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 a,
|
||||||
|
#muxiepuff {
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 3px),
|
||||||
|
3px calc(100% - 3px),
|
||||||
|
3px 100%,
|
||||||
|
calc(100% - 3px) 100%,
|
||||||
|
calc(100% - 3px) calc(100% - 3px),
|
||||||
|
100% calc(100% - 3px),
|
||||||
|
100% 3px,
|
||||||
|
calc(100% - 3px) 3px,
|
||||||
|
calc(100% - 3px) 0px,
|
||||||
|
3px 0px,
|
||||||
|
3px 3px,
|
||||||
|
0px 3px
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 li {
|
||||||
|
transition: filter 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 li:hover {
|
||||||
|
filter: brightness(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 img {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labeled-icons.b88x31 img {
|
||||||
|
image-rendering: pixelated;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extra {
|
||||||
|
display: flex;
|
||||||
|
list-style: none;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extra svg {
|
||||||
|
width: 15rem;
|
||||||
|
max-width: 100%;
|
||||||
|
height: 15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
html {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
html {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.section {
|
||||||
|
padding: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.section {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.box {
|
||||||
|
padding: 0.4rem 0.8rem;
|
||||||
|
font-size: 0.9em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.title {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
#extra {
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#extra svg {
|
||||||
|
width: 8rem;
|
||||||
|
height: 8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
#extra svg {
|
||||||
|
width: 6rem;
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.alt-font {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#muxiepuff {
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-scroller {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
margin-top: -3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-wrapper {
|
||||||
|
transition:
|
||||||
|
transform 0.3s ease-in-out,
|
||||||
|
opacity 0.3s ease-in-out;
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-wrapper.animating {
|
||||||
|
transform: translateY(-100%);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-wrapper:not(.animating) {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-text {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-underline {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 2px;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 2px;
|
||||||
|
background: currentColor;
|
||||||
|
box-shadow: 0 0 8px currentColor;
|
||||||
|
animation: pulse 2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-code {
|
||||||
|
font-family: "Share Tech Mono", "Share Tech", monospace;
|
||||||
|
font-size: 0.75em;
|
||||||
|
background-color: var(--theme-border);
|
||||||
|
padding: 1px;
|
||||||
|
display: inline-block;
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 2px),
|
||||||
|
2px calc(100% - 2px),
|
||||||
|
2px 100%,
|
||||||
|
calc(100% - 2px) 100%,
|
||||||
|
calc(100% - 2px) calc(100% - 2px),
|
||||||
|
100% calc(100% - 2px),
|
||||||
|
100% 2px,
|
||||||
|
calc(100% - 2px) 2px,
|
||||||
|
calc(100% - 2px) 0px,
|
||||||
|
2px 0px,
|
||||||
|
2px 2px,
|
||||||
|
0px 2px
|
||||||
|
);
|
||||||
|
line-height: 1.6;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-code > span {
|
||||||
|
display: block;
|
||||||
|
background-color: var(--theme-bg-tertiary);
|
||||||
|
color: var(--theme-fg);
|
||||||
|
padding: 0.15em 0.4em;
|
||||||
|
clip-path: polygon(
|
||||||
|
0px calc(100% - 2px),
|
||||||
|
2px calc(100% - 2px),
|
||||||
|
2px 100%,
|
||||||
|
calc(100% - 2px) 100%,
|
||||||
|
calc(100% - 2px) calc(100% - 2px),
|
||||||
|
100% calc(100% - 2px),
|
||||||
|
100% 2px,
|
||||||
|
calc(100% - 2px) 2px,
|
||||||
|
calc(100% - 2px) 0px,
|
||||||
|
2px 0px,
|
||||||
|
2px 2px,
|
||||||
|
0px 2px
|
||||||
|
);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
p,
|
||||||
|
.title {
|
||||||
|
transform: translateZ(0px);
|
||||||
|
-webkit-transform: translateZ(0px);
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-top: 2px solid var(--theme-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
footer.section {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1ch;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer p {
|
||||||
|
text-align: center;
|
||||||
|
gap: 1ch;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer a {
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
footer #f-heart {
|
||||||
|
color: var(--theme-accent-third);
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
7
client.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Import CSS files here for hot module reloading to work.
|
||||||
|
import "./assets/styles.css";
|
||||||
22
components/Box.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentChildren, h } from "preact";
|
||||||
|
import { LinkIcon } from "./Icon.tsx";
|
||||||
|
|
||||||
|
interface PolygonBoxProps {
|
||||||
|
as?: keyof HTMLElementTagNameMap;
|
||||||
|
children?: ComponentChildren;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Box(
|
||||||
|
{ as: Tag = "span", class: className, children, ...props }: PolygonBoxProps,
|
||||||
|
) {
|
||||||
|
const content = Tag === "a"
|
||||||
|
? [children, h(LinkIcon, { size: 16, class: "link-icon" })]
|
||||||
|
: children;
|
||||||
|
return h(Tag, { class: `box ${className || ""}`, ...props }, content);
|
||||||
|
}
|
||||||
29
components/Footer.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HeartFilledIcon } from "./Icon.tsx";
|
||||||
|
import Link from "./Link.tsx";
|
||||||
|
|
||||||
|
export default function Footer() {
|
||||||
|
return (
|
||||||
|
<footer class="section">
|
||||||
|
<p class="alt alt-font">
|
||||||
|
Made with <HeartFilledIcon id="f-heart" /> · Source code available at{" "}
|
||||||
|
<Link href="https://git.acpi.at/mux/web">git.acpi.at</Link> under the{" "}
|
||||||
|
<Link href="https://spdx.org/licenses/AGPL-3.0-or-later.html">
|
||||||
|
GNU Affero General Public License v3.0
|
||||||
|
</Link>
|
||||||
|
, with all site content licensed under{" "}
|
||||||
|
<Link href="https://creativecommons.org/licenses/by-sa/4.0/">
|
||||||
|
CC BY-SA 4.0
|
||||||
|
</Link>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p class="alt alt-font">
|
||||||
|
© 2025 muxiepuff • Powered by FreeBSD and pixels
|
||||||
|
</p>
|
||||||
|
</footer>
|
||||||
|
);
|
||||||
|
}
|
||||||
35
components/Header.tsx
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Header() {
|
||||||
|
return (
|
||||||
|
<nav id="header">
|
||||||
|
<div id="left">
|
||||||
|
<a id="logo" href="/" class="module">
|
||||||
|
<img
|
||||||
|
src="/bnuy.webp"
|
||||||
|
width="380"
|
||||||
|
height="260"
|
||||||
|
alt="acpi.at"
|
||||||
|
style="filter: grayscale(0.6)"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
{
|
||||||
|
// <ul id="links">
|
||||||
|
// <li class="module">
|
||||||
|
// <a href="/projects">Projects</a>
|
||||||
|
// </li>
|
||||||
|
// <li class="module">
|
||||||
|
// <a href="/blog">Blog</a>
|
||||||
|
// </li>
|
||||||
|
// </ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
{
|
||||||
|
// <div id="right" class="module" />
|
||||||
|
}
|
||||||
|
</nav>
|
||||||
|
);
|
||||||
|
}
|
||||||
442
components/Icon.tsx
Normal file
|
|
@ -0,0 +1,442 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface IconProps {
|
||||||
|
size?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LinkIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21 11V3h-8v2h4v2h-2v2h-2v2h-2v2H9v2h2v-2h2v-2h2V9h2V7h2v4h2zM11 5H3v16h16v-8h-2v6H5V7h6V5z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function PersonIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M10 2h4v4h-4V2zM7 7h10v2h-2v13h-2v-6h-2v6H9V9H7V7zM5 5v2h2V5H5zm0 0H3V3h2v2zm14 0v2h-2V5h2zm0 0V3h2v2h-2z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeartIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9 2H5v2H3v2H1v6h2v2h2v2h2v2h2v2h2v2h2v-2h2v-2h2v-2h2v-2h2v-2h2V6h-2V4h-2V2h-4v2h-2v2h-2V4H9V2zm0 2v2h2v2h2V6h2V4h4v2h2v6h-2v2h-2v2h-2v2h-2v2h-2v-2H9v-2H7v-2H5v-2H3V6h2V4h4z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function CommentIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M22 2H2v14h2V4h16v12h-8v2h-2v2H8v-4H2v2h4v4h4v-2h2v-2h10V2z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function AttachmentIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M7 5v14H5V3h14v18H9V7h6v10h-2V9h-2v10h6V5H7z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LabelIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M12 2H2v10h2v2h2v2h2v2h2v2h2v2h2v-2h2v-2h2v-2h2v-2h2v-2h-2v-2h-2V8h-2V6h-2V4h-2V2zm0 2v2h2v2h2v2h2v2h2v2h-2v2h-2v2h-2v2h-2v-2h-2v-2H8v-2H6v-2H4V4h8zM6 6h2v2H6V6z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DiscordIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Discord</title>
|
||||||
|
<path
|
||||||
|
d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function SignalIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Signal</title>
|
||||||
|
<path
|
||||||
|
d="M12 0q-.934 0-1.83.139l.17 1.111a11 11 0 0 1 3.32 0l.172-1.111A12 12 0 0 0 12 0M9.152.34A12 12 0 0 0 5.77 1.742l.584.961a10.8 10.8 0 0 1 3.066-1.27zm5.696 0-.268 1.094a10.8 10.8 0 0 1 3.066 1.27l.584-.962A12 12 0 0 0 14.848.34M12 2.25a9.75 9.75 0 0 0-8.539 14.459c.074.134.1.292.064.441l-1.013 4.338 4.338-1.013a.62.62 0 0 1 .441.064A9.7 9.7 0 0 0 12 21.75c5.385 0 9.75-4.365 9.75-9.75S17.385 2.25 12 2.25m-7.092.068a12 12 0 0 0-2.59 2.59l.909.664a11 11 0 0 1 2.345-2.345zm14.184 0-.664.909a11 11 0 0 1 2.345 2.345l.909-.664a12 12 0 0 0-2.59-2.59M1.742 5.77A12 12 0 0 0 .34 9.152l1.094.268a10.8 10.8 0 0 1 1.269-3.066zm20.516 0-.961.584a10.8 10.8 0 0 1 1.27 3.066l1.093-.268a12 12 0 0 0-1.402-3.383M.138 10.168A12 12 0 0 0 0 12q0 .934.139 1.83l1.111-.17A11 11 0 0 1 1.125 12q0-.848.125-1.66zm23.723.002-1.111.17q.125.812.125 1.66c0 .848-.042 1.12-.125 1.66l1.111.172a12.1 12.1 0 0 0 0-3.662M1.434 14.58l-1.094.268a12 12 0 0 0 .96 2.591l-.265 1.14 1.096.255.36-1.539-.188-.365a10.8 10.8 0 0 1-.87-2.35m21.133 0a10.8 10.8 0 0 1-1.27 3.067l.962.584a12 12 0 0 0 1.402-3.383zm-1.793 3.848a11 11 0 0 1-2.345 2.345l.664.909a12 12 0 0 0 2.59-2.59zm-19.959 1.1L.357 21.48a1.8 1.8 0 0 0 2.162 2.161l1.954-.455-.256-1.095-1.953.455a.675.675 0 0 1-.81-.81l.454-1.954zm16.832 1.769a10.8 10.8 0 0 1-3.066 1.27l.268 1.093a12 12 0 0 0 3.382-1.402zm-10.94.213-1.54.36.256 1.095 1.139-.266c.814.415 1.683.74 2.591.961l.268-1.094a10.8 10.8 0 0 1-2.35-.869zm3.634 1.24-.172 1.111a12.1 12.1 0 0 0 3.662 0l-.17-1.111q-.812.125-1.66.125a11 11 0 0 1-1.66-.125"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function LastfmIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Last.fm</title>
|
||||||
|
<path
|
||||||
|
d="M10.584 17.21l-.88-2.392s-1.43 1.594-3.573 1.594c-1.897 0-3.244-1.649-3.244-4.288 0-3.382 1.704-4.591 3.381-4.591 2.42 0 3.189 1.567 3.849 3.574l.88 2.749c.88 2.666 2.529 4.81 7.285 4.81 3.409 0 5.718-1.044 5.718-3.793 0-2.227-1.265-3.381-3.63-3.931l-1.758-.385c-1.21-.275-1.567-.77-1.567-1.595 0-.934.742-1.484 1.952-1.484 1.32 0 2.034.495 2.144 1.677l2.749-.33c-.22-2.474-1.924-3.492-4.729-3.492-2.474 0-4.893.935-4.893 3.932 0 1.87.907 3.051 3.189 3.601l1.87.44c1.402.33 1.869.907 1.869 1.704 0 1.017-.99 1.43-2.86 1.43-2.776 0-3.93-1.457-4.59-3.464l-.907-2.75c-1.155-3.573-2.997-4.893-6.653-4.893C2.144 5.333 0 7.89 0 12.233c0 4.18 2.144 6.434 5.993 6.434 3.106 0 4.591-1.457 4.591-1.457z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function BlueskyIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Bluesky</title>
|
||||||
|
<path
|
||||||
|
d="M5.202 2.857C7.954 4.922 10.913 9.11 12 11.358c1.087-2.247 4.046-6.436 6.798-8.501C20.783 1.366 24 .213 24 3.883c0 .732-.42 6.156-.667 7.037-.856 3.061-3.978 3.842-6.755 3.37 4.854.826 6.089 3.562 3.422 6.299-5.065 5.196-7.28-1.304-7.847-2.97-.104-.305-.152-.448-.153-.327 0-.121-.05.022-.153.327-.568 1.666-2.782 8.166-7.847 2.97-2.667-2.737-1.432-5.473 3.422-6.3-2.777.473-5.899-.308-6.755-3.369C.42 10.04 0 4.615 0 3.883c0-3.67 3.217-2.517 5.202-1.026"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MastodonIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Mastodon / Fediverse</title>
|
||||||
|
<path
|
||||||
|
d="M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ForgejoIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Forgejo</title>
|
||||||
|
<path
|
||||||
|
d="M16.7773 0c1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.1175a7.0759 7.0759 0 0 1 4.148-1.4205l.1176-.001 1.3385.0002c.4973-.8827 1.4434-1.4788 2.5288-1.4788 1.6018 0 2.9004 1.2986 2.9004 2.9005s-1.2986 2.9004-2.9004 2.9004c-1.0854 0-2.0315-.596-2.5288-1.4787H12.91c-2.3322 0-4.2272 1.8718-4.2649 4.195l-.0007 2.319c.8827.4973 1.4788 1.4434 1.4788 2.5287 0 1.602-1.2986 2.9005-2.9005 2.9005-1.6018 0-2.9004-1.2986-2.9004-2.9005 0-1.0853.596-2.0314 1.4788-2.5287l-.0002-9.9831c0-3.887 3.1195-7.0453 6.9915-7.108l.1176-.001h1.3385C14.7458.5962 15.692 0 16.7773 0ZM7.2227 19.9052c-.6596 0-1.1943.5347-1.1943 1.1943s.5347 1.1943 1.1943 1.1943 1.1944-.5347 1.1944-1.1943-.5348-1.1943-1.1944-1.1943Zm9.5546-10.4644c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Zm0-7.7346c-.6596 0-1.1944.5347-1.1944 1.1943s.5348 1.1943 1.1944 1.1943c.6596 0 1.1943-.5347 1.1943-1.1943s-.5347-1.1943-1.1943-1.1943Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function CodebergIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Codeberg</title>
|
||||||
|
<path
|
||||||
|
d="M11.999.747A11.974 11.974 0 0 0 0 12.75c0 2.254.635 4.465 1.833 6.376L11.837 6.19c.072-.092.251-.092.323 0l4.178 5.402h-2.992l.065.239h3.113l.882 1.138h-3.674l.103.374h3.86l.777 1.003h-4.358l.135.483h4.593l.695.894h-5.038l.165.589h5.326l.609.785h-5.717l.182.65h6.038l.562.727h-6.397l.183.65h6.717A12.003 12.003 0 0 0 24 12.75 11.977 11.977 0 0 0 11.999.747zm3.654 19.104.182.65h5.326c.173-.204.353-.433.513-.65zm.385 1.377.18.65h3.563c.233-.198.485-.428.712-.65zm.383 1.377.182.648h1.203c.356-.204.685-.412 1.042-.648zz"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
export function GitHubIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>GitHub</title>
|
||||||
|
<path
|
||||||
|
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MailIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M3 4a2 2 0 0 0-2 2v1.161l8.441 4.221a1.25 1.25 0 0 0 1.118 0L19 7.162V6a2 2 0 0 0-2-2H3Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
d="m19 8.839-7.77 3.885a2.75 2.75 0 0 1-2.46 0L1 8.839V14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V8.839Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TwitterIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M21.543 7.104c.015.211.015.423.015.636 0 6.507-4.954 14.01-14.01 14.01v-.003A13.94 13.94 0 0 1 0 19.539a9.88 9.88 0 0 0 7.287-2.041 4.93 4.93 0 0 1-4.6-3.42 4.916 4.916 0 0 0 2.223-.084A4.926 4.926 0 0 1 .96 9.167v-.062a4.887 4.887 0 0 0 2.235.616A4.928 4.928 0 0 1 1.67 3.148 13.98 13.98 0 0 0 11.82 8.292a4.929 4.929 0 0 1 8.39-4.49 9.868 9.868 0 0 0 3.128-1.196 4.941 4.941 0 0 1-2.165 2.724A9.828 9.828 0 0 0 24 4.555a10.019 10.019 0 0 1-2.457 2.549z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function KofiIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Ko-fi</title>
|
||||||
|
<path
|
||||||
|
d="M11.351 2.715c-2.7 0-4.986.025-6.83.26C2.078 3.285 0 5.154 0 8.61c0 3.506.182 6.13 1.585 8.493 1.584 2.701 4.233 4.182 7.662 4.182h.83c4.209 0 6.494-2.234 7.637-4a9.5 9.5 0 0 0 1.091-2.338C21.792 14.688 24 12.22 24 9.208v-.415c0-3.247-2.13-5.507-5.792-5.87-1.558-.156-2.65-.208-6.857-.208m0 1.947c4.208 0 5.09.052 6.571.182 2.624.311 4.13 1.584 4.13 4v.39c0 2.156-1.792 3.844-3.87 3.844h-.935l-.156.649c-.208 1.013-.597 1.818-1.039 2.546-.909 1.428-2.545 3.064-5.922 3.064h-.805c-2.571 0-4.831-.883-6.078-3.195-1.09-2-1.298-4.155-1.298-7.506 0-2.181.857-3.402 3.012-3.714 1.533-.233 3.559-.26 6.39-.26m6.547 2.287c-.416 0-.65.234-.65.546v2.935c0 .311.234.545.65.545 1.324 0 2.051-.754 2.051-2s-.727-2.026-2.052-2.026m-10.39.182c-1.818 0-3.013 1.48-3.013 3.142 0 1.533.858 2.857 1.949 3.897.727.701 1.87 1.429 2.649 1.896a1.47 1.47 0 0 0 1.507 0c.78-.467 1.922-1.195 2.623-1.896 1.117-1.039 1.974-2.364 1.974-3.897 0-1.662-1.247-3.142-3.039-3.142-1.065 0-1.792.545-2.338 1.298-.493-.753-1.246-1.298-2.312-1.298"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BitcoinIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Bitcoin</title>
|
||||||
|
<path
|
||||||
|
d="M23.638 14.904c-1.602 6.43-8.113 10.34-14.542 8.736C2.67 22.05-1.244 15.525.362 9.105 1.962 2.67 8.475-1.243 14.9.358c6.43 1.605 10.342 8.115 8.738 14.548v-.002zm-6.35-4.613c.24-1.59-.974-2.45-2.64-3.03l.54-2.153-1.315-.33-.525 2.107c-.345-.087-.705-.167-1.064-.25l.526-2.127-1.32-.33-.54 2.165c-.285-.067-.565-.132-.84-.2l-1.815-.45-.35 1.407s.975.225.955.236c.535.136.63.486.615.766l-1.477 5.92c-.075.166-.24.406-.614.314.015.02-.96-.24-.96-.24l-.66 1.51 1.71.426.93.242-.54 2.19 1.32.327.54-2.17c.36.1.705.19 1.05.273l-.51 2.154 1.32.33.545-2.19c2.24.427 3.93.257 4.64-1.774.57-1.637-.03-2.58-1.217-3.196.854-.193 1.5-.76 1.68-1.93h.01zm-3.01 4.22c-.404 1.64-3.157.75-4.05.53l.72-2.9c.896.23 3.757.67 3.33 2.37zm.41-4.24c-.37 1.49-2.662.735-3.405.55l.654-2.64c.744.18 3.137.524 2.75 2.084v.006z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function BitcoinCashIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Bitcoin Cash</title>
|
||||||
|
<path
|
||||||
|
d="m10.84 11.22-.688-2.568c.728-.18 2.839-1.051 3.39.506.27 1.682-1.978 1.877-2.702 2.062zm.289 1.313.755 2.829c.868-.228 3.496-.46 3.241-2.351-.433-1.666-3.125-.706-3.996-.478zM24 12c0 6.627-5.373 12-12 12S0 18.627 0 12 5.373 0 12 0s12 5.373 12 12zm-6.341.661c-.183-1.151-1.441-2.095-2.485-2.202.643-.57.969-1.401.57-2.488-.603-1.368-1.989-1.66-3.685-1.377l-.546-2.114-1.285.332.536 2.108c-.338.085-.685.158-1.029.256L9.198 5.08l-1.285.332.545 2.114c-.277.079-2.595.673-2.595.673l.353 1.377s.944-.265.935-.244c.524-.137.771.125.886.372l1.498 5.793c.018.168-.012.454-.372.551.021.012-.935.241-.935.241l.14 1.605s2.296-.588 2.598-.664l.551 2.138 1.285-.332-.551-2.153c.353-.082.697-.168 1.032-.256l.548 2.141 1.285-.332-.551-2.135c1.982-.482 3.38-1.73 3.094-3.64z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MoneroIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Monero</title>
|
||||||
|
<path
|
||||||
|
d="M12 0C5.365 0 0 5.373 0 12.015c0 1.335.228 2.607.618 3.81h3.577V5.729L12 13.545l7.805-7.815v10.095h3.577c.389-1.203.618-2.475.618-3.81C24 5.375 18.635 0 12 0zm-1.788 15.307l-3.417-3.421v6.351H1.758C3.87 21.689 7.678 24 12 24s8.162-2.311 10.245-5.764h-5.04v-6.351l-3.386 3.421-1.788 1.79-1.814-1.79h-.005z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NanoIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Nano</title>
|
||||||
|
<path
|
||||||
|
d="m3.723 0 6.875 10.76H4.775v1.365h5.881l-1.76 2.73h-4.12v1.364h3.242L3.006 24h1.85l5.068-7.781h4.215L19.129 24h1.865l-4.941-7.781h3.232v-1.364h-4.1l-1.732-2.73h5.832V10.76h-5.803L20.45 0h-1.785l-6.588 10.107L5.627 0H3.723zm8.324 12.959 1.217 1.896h-2.451l1.234-1.896z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function LitecoinIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Litecoin</title>
|
||||||
|
<path
|
||||||
|
d="M12 0a12 12 0 1012 12A12 12 0 0012 0zm-.2617 3.6777h2.584a.3425.3425 0 01.33.4356l-2.0312 6.918 1.9062-.582-.4082 1.3847-1.9238.5605-1.248 4.213h6.6757a.3425.3425 0 01.3282.4374l-.582 2a.4586.4586 0 01-.4395.3301H6.7324l1.7227-5.8223-1.9063.5801.42-1.3613 1.9101-.58 2.4219-8.1798a.4557.4557 0 01.4375-.334Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function EthereumIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
role="img"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<title>Ethereum</title>
|
||||||
|
<path
|
||||||
|
d="M11.944 17.97L4.58 13.62 11.943 24l7.37-10.38-7.372 4.35h.003zM12.056 0L4.69 12.223l7.365 4.354 7.365-4.35L12.056 0z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function HeartFilledIcon({ size = 24, ...props }: IconProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
fill="currentColor"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path d="M9 2H5v2H3v2H1v6h2v2h2v2h2v2h2v2h2v2h2v-2h2v-2h2v-2h2v-2h2v-2h2V6h-2V4h-2V2h-4v2h-2v2h-2V4H9V2z" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
32
components/Layout.tsx
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentChildren } from "preact";
|
||||||
|
|
||||||
|
interface LayoutProps {
|
||||||
|
children: ComponentChildren;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Section({ children, class: className, ...props }: LayoutProps) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
{...props}
|
||||||
|
class={`section ${className || ""}`}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Centered(
|
||||||
|
{ children, class: className, ...props }: LayoutProps,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<div class={`centered ${className || ""}`} {...props}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
24
components/Link.tsx
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { HTMLAttributes, JSX } from "preact";
|
||||||
|
import { LinkIcon } from "./Icon.tsx";
|
||||||
|
|
||||||
|
interface LinkProps extends HTMLAttributes<HTMLAnchorElement> {
|
||||||
|
href: string;
|
||||||
|
children: JSX.Element | JSX.Element[] | string;
|
||||||
|
noIcon?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Link(
|
||||||
|
{ children, noIcon = false, ...props }: LinkProps,
|
||||||
|
) {
|
||||||
|
return (
|
||||||
|
<a {...props}>
|
||||||
|
{children}
|
||||||
|
{!noIcon && <LinkIcon size={16} class="link-icon" />}
|
||||||
|
</a>
|
||||||
|
);
|
||||||
|
}
|
||||||
60
components/Tinted.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface TintedImageProps {
|
||||||
|
src: string;
|
||||||
|
colour: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function TintedImage({
|
||||||
|
colour,
|
||||||
|
src,
|
||||||
|
...props
|
||||||
|
}: TintedImageProps) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<filter
|
||||||
|
id="tint"
|
||||||
|
x="0%"
|
||||||
|
y="0%"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
colorInterpolationFilters="sRGB"
|
||||||
|
>
|
||||||
|
<feColorMatrix
|
||||||
|
type="saturate"
|
||||||
|
values="0"
|
||||||
|
result="grayscale"
|
||||||
|
/>
|
||||||
|
<feFlood flood-color={colour} result="tint" />
|
||||||
|
<feComposite
|
||||||
|
in="tint"
|
||||||
|
in2="grayscale"
|
||||||
|
operator="arithmetic"
|
||||||
|
k1="1"
|
||||||
|
k2="0"
|
||||||
|
k3="0"
|
||||||
|
k4="0"
|
||||||
|
/>
|
||||||
|
</filter>
|
||||||
|
</defs>
|
||||||
|
<image
|
||||||
|
x="0"
|
||||||
|
y="0"
|
||||||
|
width="100%"
|
||||||
|
height="100%"
|
||||||
|
preserveAspectRatio="xMidYMid meet"
|
||||||
|
filter={`url(#tint)`}
|
||||||
|
href={src}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
components/Title.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentChildren, h } from "preact";
|
||||||
|
|
||||||
|
interface TitleProps {
|
||||||
|
level?: 1 | 2 | 3 | 4 | 5 | 6;
|
||||||
|
children: ComponentChildren;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Title({
|
||||||
|
level = 1,
|
||||||
|
children,
|
||||||
|
class: className,
|
||||||
|
...props
|
||||||
|
}: TitleProps) {
|
||||||
|
const Heading = `h${Math.min(Math.max(level, 1), 6)}`;
|
||||||
|
return h(
|
||||||
|
Heading,
|
||||||
|
{
|
||||||
|
class: `title title-${level} ${className || ""}`,
|
||||||
|
...props,
|
||||||
|
},
|
||||||
|
children,
|
||||||
|
);
|
||||||
|
}
|
||||||
58
deno.json
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
{
|
||||||
|
"nodeModulesDir": "auto",
|
||||||
|
"tasks": {
|
||||||
|
"check": "deno fmt --check . && deno lint . && deno check",
|
||||||
|
"dev": "vite",
|
||||||
|
"build": "vite build",
|
||||||
|
"start": "deno serve -A _fresh/server.js",
|
||||||
|
"update": "deno run -A -r jsr:@fresh/update ."
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"rules": {
|
||||||
|
"tags": [
|
||||||
|
"fresh",
|
||||||
|
"recommended"
|
||||||
|
],
|
||||||
|
"exclude": ["no-explicit-any", "no-window", "no-window-prefix"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"**/_fresh/*"
|
||||||
|
],
|
||||||
|
"imports": {
|
||||||
|
"fresh": "jsr:@fresh/core@^2.1.2",
|
||||||
|
"preact": "npm:preact@^10.27.2",
|
||||||
|
"@preact/signals": "npm:@preact/signals@^2.3.1",
|
||||||
|
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.5",
|
||||||
|
"vite": "npm:vite@^7.1.3"
|
||||||
|
},
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": [
|
||||||
|
"dom",
|
||||||
|
"dom.asynciterable",
|
||||||
|
"dom.iterable",
|
||||||
|
"deno.ns"
|
||||||
|
],
|
||||||
|
"jsx": "precompile",
|
||||||
|
"jsxImportSource": "preact",
|
||||||
|
"jsxPrecompileSkipElements": [
|
||||||
|
"a",
|
||||||
|
"img",
|
||||||
|
"source",
|
||||||
|
"body",
|
||||||
|
"html",
|
||||||
|
"head",
|
||||||
|
"title",
|
||||||
|
"meta",
|
||||||
|
"script",
|
||||||
|
"link",
|
||||||
|
"style",
|
||||||
|
"base",
|
||||||
|
"noscript",
|
||||||
|
"template"
|
||||||
|
],
|
||||||
|
"types": [
|
||||||
|
"vite/client"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
30
islands/Code.tsx
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { ComponentChildren } from "preact";
|
||||||
|
|
||||||
|
interface CodeProps {
|
||||||
|
children: ComponentChildren;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Code({ children, ...props }: CodeProps) {
|
||||||
|
const handleClick = () => {
|
||||||
|
if (typeof children === "string") {
|
||||||
|
navigator.clipboard.writeText(children);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<code
|
||||||
|
class="inline-code"
|
||||||
|
onClick={handleClick}
|
||||||
|
style={{ cursor: "pointer" }}
|
||||||
|
title={"Click to copy"}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>{children}</span>
|
||||||
|
</code>
|
||||||
|
);
|
||||||
|
}
|
||||||
19
islands/Meow.tsx
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function Meow() {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
id="muxiepuff"
|
||||||
|
onClick={() =>
|
||||||
|
navigator.clipboard.writeText(
|
||||||
|
'<a href="https://acpi.at"><img src="https://acpi.at/88x31.gif" title="muxiepuff" alt="acpi.at" /></a>',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<img src="/88x31.gif" title="muxiepuff (Click to copy to clipboard!)" alt="acpi.at" />
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
islands/Name.tsx
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
|
||||||
|
export default function Name() {
|
||||||
|
const names = ["muxiepuff", "lívia", "aury"];
|
||||||
|
const ipas = ["/ˈmuˌksipʌf/", "/li.vjɐ/", "/ˈaʊ̯ˌɾi/"]
|
||||||
|
;
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [isAnimating, setIsAnimating] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setIsAnimating(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setCurrentIndex((prev) => (prev + 1) % names.length);
|
||||||
|
setIsAnimating(false);
|
||||||
|
}, 200);
|
||||||
|
}, 2000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<span class="name-scroller">
|
||||||
|
<span class={`name-wrapper ${isAnimating ? "animating" : ""}`}>
|
||||||
|
<span class="name-text">
|
||||||
|
{names[currentIndex]}
|
||||||
|
<span class="name-underline"></span>
|
||||||
|
</span>{" "}
|
||||||
|
<span class="alt alt-font">({ipas[currentIndex]}, she/her)</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
374
islands/Rain.tsx
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "preact/hooks";
|
||||||
|
|
||||||
|
interface Particle {
|
||||||
|
offsetX: number;
|
||||||
|
offsetY: number;
|
||||||
|
velocityX: number;
|
||||||
|
velocityY: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Raindrop {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
length: number;
|
||||||
|
speed: number;
|
||||||
|
opacity: number;
|
||||||
|
floorHeight: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Splash {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
age: number;
|
||||||
|
maxAge: number;
|
||||||
|
particles: Particle[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Star {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
size: number;
|
||||||
|
brightness: number;
|
||||||
|
twinkleSpeed: number;
|
||||||
|
twinkleOffset: number;
|
||||||
|
age: number;
|
||||||
|
maxAge: number;
|
||||||
|
canVanish: boolean;
|
||||||
|
color: { r: number; g: number; b: number };
|
||||||
|
colorIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PIXEL_SIZE = 4;
|
||||||
|
const DROP_COUNT = 300;
|
||||||
|
const RAIN_COLOR = { r: 87, g: 97, b: 100 };
|
||||||
|
const BACKGROUND_COLOUR = "#16191C";
|
||||||
|
const STAR_COUNT = 150;
|
||||||
|
const RAIN_SPEED = 0.75;
|
||||||
|
|
||||||
|
const SPLASH_CHANCE = 0.3;
|
||||||
|
const FLOOR_HEIGHT_MIN = 0.55;
|
||||||
|
const FLOOR_HEIGHT_RANGE = 0.95;
|
||||||
|
|
||||||
|
const PARTICLE_OFFSET_RANGE = 4;
|
||||||
|
const PARTICLE_Y_RANGE = 2;
|
||||||
|
const PARTICLE_Y_MIN = 1;
|
||||||
|
const PARTICLE_VX_RANGE = 0.8;
|
||||||
|
const PARTICLE_VY_RANGE = 0.5;
|
||||||
|
const PARTICLE_VY_MIN = 0.3;
|
||||||
|
const PARTICLE_GRAVITY = 0.15;
|
||||||
|
|
||||||
|
const STAR_BRIGHTNESS_MIN = 0.6;
|
||||||
|
const STAR_BRIGHTNESS_RANGE = 0.4;
|
||||||
|
const STAR_LARGE_THRESHOLD = 0.7;
|
||||||
|
const STAR_SIZE_SMALL = 4;
|
||||||
|
const STAR_SIZE_LARGE = 8;
|
||||||
|
const STAR_TWINKLE_MIN = 0.002;
|
||||||
|
const STAR_TWINKLE_RANGE = 0.005;
|
||||||
|
const STAR_AGE_MIN = 60;
|
||||||
|
const STAR_AGE_RANGE = 120;
|
||||||
|
const STAR_TWINKLE_AMPLITUDE = 0.3;
|
||||||
|
const STAR_TWINKLE_BASE = 0.7;
|
||||||
|
const STAR_GLOW_THRESHOLD = 0.7;
|
||||||
|
const STAR_GLOW_ALPHA = 0.5;
|
||||||
|
|
||||||
|
const RAIN_COLORS: string[] = [];
|
||||||
|
const STAR_COLORS: string[] = [];
|
||||||
|
const ALPHA_STAR_COLORS: string[] = [];
|
||||||
|
const NUM_COLOR_VARIATIONS = 20;
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_COLOR_VARIATIONS; i++) {
|
||||||
|
const brightness = 1 - (i / NUM_COLOR_VARIATIONS) * 0.5;
|
||||||
|
RAIN_COLORS.push(
|
||||||
|
`rgb(${Math.floor(RAIN_COLOR.r * brightness)},${
|
||||||
|
Math.floor(RAIN_COLOR.g * brightness)
|
||||||
|
},${Math.floor(RAIN_COLOR.b * brightness)})`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < NUM_COLOR_VARIATIONS; i++) {
|
||||||
|
const brightness = STAR_BRIGHTNESS_MIN +
|
||||||
|
(i / NUM_COLOR_VARIATIONS) * STAR_BRIGHTNESS_RANGE;
|
||||||
|
STAR_COLORS.push(
|
||||||
|
`rgb(${Math.floor(RAIN_COLOR.r * brightness)},${
|
||||||
|
Math.floor(RAIN_COLOR.g * brightness)
|
||||||
|
},${Math.floor(RAIN_COLOR.b * brightness)})`,
|
||||||
|
);
|
||||||
|
ALPHA_STAR_COLORS.push(
|
||||||
|
`rgba(${Math.floor(RAIN_COLOR.r * brightness)},${
|
||||||
|
Math.floor(RAIN_COLOR.g * brightness)
|
||||||
|
},${Math.floor(RAIN_COLOR.b * brightness)},`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const SPLASH_PARTICLE_COLOR = `rgb(${RAIN_COLOR.r * 0.9},${
|
||||||
|
RAIN_COLOR.g * 0.95
|
||||||
|
},${RAIN_COLOR.b * 1.1})`;
|
||||||
|
const SPLASH_RIPPLE_COLOR = `rgb(${RAIN_COLOR.r * 0.7},${RAIN_COLOR.g * 0.8},${
|
||||||
|
RAIN_COLOR.b * 0.95
|
||||||
|
})`;
|
||||||
|
|
||||||
|
function createParticle(): Particle {
|
||||||
|
return {
|
||||||
|
offsetX: (Math.random() - 0.5) * PARTICLE_OFFSET_RANGE,
|
||||||
|
offsetY: -(Math.random() * PARTICLE_Y_RANGE + PARTICLE_Y_MIN),
|
||||||
|
velocityX: (Math.random() - 0.5) * PARTICLE_VX_RANGE,
|
||||||
|
velocityY: -(Math.random() * PARTICLE_VY_RANGE + PARTICLE_VY_MIN),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSplash(x: number, y: number): Splash {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
age: 0,
|
||||||
|
maxAge: 12,
|
||||||
|
particles: Array.from(
|
||||||
|
{ length: Math.floor(Math.random() * 3) + 3 },
|
||||||
|
createParticle,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createRaindrop(gridWidth: number, gridHeight: number): Raindrop {
|
||||||
|
const bias = Math.random() * Math.random();
|
||||||
|
return {
|
||||||
|
x: Math.floor(Math.random() * gridWidth),
|
||||||
|
y: Math.floor(Math.random() * gridHeight) - gridHeight,
|
||||||
|
length: Math.floor(Math.random() * 4) + 2,
|
||||||
|
speed: RAIN_SPEED * (Math.random() * 2 + 1),
|
||||||
|
opacity: Math.random() * 0.3 + 0.6,
|
||||||
|
floorHeight: window.innerHeight *
|
||||||
|
(FLOOR_HEIGHT_MIN + bias * FLOOR_HEIGHT_RANGE),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createStar(): Star {
|
||||||
|
const brightness = Math.random() * STAR_BRIGHTNESS_RANGE +
|
||||||
|
STAR_BRIGHTNESS_MIN;
|
||||||
|
const colorIndex = Math.floor(
|
||||||
|
(brightness - STAR_BRIGHTNESS_MIN) / STAR_BRIGHTNESS_RANGE *
|
||||||
|
(NUM_COLOR_VARIATIONS - 1),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: Math.random() * window.innerWidth,
|
||||||
|
y: Math.random() * window.innerHeight,
|
||||||
|
size: Math.random() > STAR_LARGE_THRESHOLD
|
||||||
|
? STAR_SIZE_LARGE
|
||||||
|
: STAR_SIZE_SMALL,
|
||||||
|
twinkleSpeed: Math.random() * STAR_TWINKLE_RANGE + STAR_TWINKLE_MIN,
|
||||||
|
twinkleOffset: Math.random() * Math.PI * 2,
|
||||||
|
brightness,
|
||||||
|
age: 0,
|
||||||
|
maxAge: STAR_AGE_MIN + Math.random() * STAR_AGE_RANGE,
|
||||||
|
canVanish: false,
|
||||||
|
colorIndex,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateParticle(p: Particle) {
|
||||||
|
p.offsetX += p.velocityX;
|
||||||
|
p.offsetY += p.velocityY;
|
||||||
|
p.velocityY += PARTICLE_GRAVITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSplash(splash: Splash) {
|
||||||
|
splash.age++;
|
||||||
|
splash.particles.forEach(updateParticle);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateRaindrop(
|
||||||
|
drop: Raindrop,
|
||||||
|
splashes: Splash[],
|
||||||
|
gridWidth: number,
|
||||||
|
gridHeight: number,
|
||||||
|
) {
|
||||||
|
drop.y += drop.speed;
|
||||||
|
const dropY = drop.y * PIXEL_SIZE;
|
||||||
|
|
||||||
|
if (dropY >= drop.floorHeight && Math.random() < SPLASH_CHANCE) {
|
||||||
|
splashes.push(createSplash(drop.x, Math.floor(dropY / PIXEL_SIZE)));
|
||||||
|
Object.assign(drop, createRaindrop(gridWidth, gridHeight));
|
||||||
|
} else if (dropY > window.innerHeight) {
|
||||||
|
Object.assign(drop, createRaindrop(gridWidth, gridHeight));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateStar(star: Star): void {
|
||||||
|
star.age++;
|
||||||
|
const phase = (star.age * star.twinkleSpeed + star.twinkleOffset) %
|
||||||
|
(2 * Math.PI);
|
||||||
|
|
||||||
|
if (!star.canVanish && phase < star.twinkleSpeed) {
|
||||||
|
star.canVanish = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (star.canVanish && star.age > star.maxAge) {
|
||||||
|
Object.assign(star, createStar());
|
||||||
|
} else if (star.x > window.innerWidth || star.y > window.innerHeight) {
|
||||||
|
Object.assign(star, createStar());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isSplashDead = (splash: Splash): boolean => splash.age >= splash.maxAge;
|
||||||
|
|
||||||
|
const drawRaindrop = (ctx: CanvasRenderingContext2D, drop: Raindrop): void => {
|
||||||
|
ctx.globalAlpha = drop.opacity;
|
||||||
|
const xPos = drop.x * PIXEL_SIZE;
|
||||||
|
|
||||||
|
for (let i = 0; i < drop.length; i++) {
|
||||||
|
const colorIndex = Math.floor(
|
||||||
|
(i / drop.length) * NUM_COLOR_VARIATIONS * 0.5,
|
||||||
|
);
|
||||||
|
ctx.fillStyle = RAIN_COLORS[colorIndex];
|
||||||
|
ctx.fillRect(xPos, (drop.y - i) * PIXEL_SIZE, PIXEL_SIZE, PIXEL_SIZE);
|
||||||
|
}
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
function drawSplash(ctx: CanvasRenderingContext2D, splash: Splash) {
|
||||||
|
if (splash.age >= splash.maxAge) return;
|
||||||
|
|
||||||
|
const progress = splash.age / splash.maxAge;
|
||||||
|
ctx.globalAlpha = 1 - progress;
|
||||||
|
|
||||||
|
ctx.fillStyle = SPLASH_PARTICLE_COLOR;
|
||||||
|
splash.particles.forEach((p) => {
|
||||||
|
const px = Math.floor(splash.x + p.offsetX) * PIXEL_SIZE;
|
||||||
|
const py = Math.floor(splash.y + p.offsetY) * PIXEL_SIZE;
|
||||||
|
ctx.fillRect(px, py, PIXEL_SIZE, PIXEL_SIZE);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (splash.age < 6) {
|
||||||
|
const rippleSize = Math.floor(splash.age / 2) + 1;
|
||||||
|
const splashY = splash.y * PIXEL_SIZE;
|
||||||
|
ctx.fillStyle = SPLASH_RIPPLE_COLOR;
|
||||||
|
ctx.fillRect(
|
||||||
|
(splash.x - rippleSize) * PIXEL_SIZE,
|
||||||
|
splashY,
|
||||||
|
PIXEL_SIZE,
|
||||||
|
PIXEL_SIZE,
|
||||||
|
);
|
||||||
|
ctx.fillRect(
|
||||||
|
(splash.x + rippleSize) * PIXEL_SIZE,
|
||||||
|
splashY,
|
||||||
|
PIXEL_SIZE,
|
||||||
|
PIXEL_SIZE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.globalAlpha = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
function drawStar(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
star: Star,
|
||||||
|
time: number,
|
||||||
|
) {
|
||||||
|
const twinkle = Math.sin(time * star.twinkleSpeed + star.twinkleOffset) *
|
||||||
|
STAR_TWINKLE_AMPLITUDE + STAR_TWINKLE_BASE;
|
||||||
|
const alpha = star.brightness * twinkle;
|
||||||
|
|
||||||
|
const x = Math.floor(star.x);
|
||||||
|
const y = Math.floor(star.y);
|
||||||
|
const color = ALPHA_STAR_COLORS[star.colorIndex];
|
||||||
|
|
||||||
|
ctx.fillStyle = color + alpha;
|
||||||
|
ctx.fillRect(x, y, star.size, star.size);
|
||||||
|
|
||||||
|
if (star.size === STAR_SIZE_LARGE && alpha > STAR_GLOW_THRESHOLD) {
|
||||||
|
ctx.fillStyle = color + (alpha * STAR_GLOW_ALPHA);
|
||||||
|
ctx.fillRect(x - 4, y + 2, 4, 4);
|
||||||
|
ctx.fillRect(x + 8, y + 2, 4, 4);
|
||||||
|
ctx.fillRect(x + 2, y - 4, 4, 4);
|
||||||
|
ctx.fillRect(x + 2, y + 8, 4, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Rain() {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const prefersReducedMotion =
|
||||||
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||||
|
if (prefersReducedMotion) return;
|
||||||
|
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
if (!canvas) return;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
if (!ctx) return;
|
||||||
|
|
||||||
|
let gridWidth = 0;
|
||||||
|
let gridHeight = 0;
|
||||||
|
|
||||||
|
const resizeCanvas = () => {
|
||||||
|
canvas.width = window.innerWidth;
|
||||||
|
canvas.height = window.innerHeight;
|
||||||
|
gridWidth = Math.floor(window.innerWidth / PIXEL_SIZE);
|
||||||
|
gridHeight = Math.floor(window.innerHeight / PIXEL_SIZE);
|
||||||
|
};
|
||||||
|
|
||||||
|
resizeCanvas();
|
||||||
|
window.addEventListener("resize", resizeCanvas);
|
||||||
|
|
||||||
|
const stars = Array.from({ length: STAR_COUNT }, createStar);
|
||||||
|
const raindrops = Array.from(
|
||||||
|
{ length: DROP_COUNT },
|
||||||
|
() => createRaindrop(gridWidth, gridHeight),
|
||||||
|
);
|
||||||
|
const splashes: Splash[] = [];
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
const animate = (time: number) => {
|
||||||
|
ctx.fillStyle = BACKGROUND_COLOUR;
|
||||||
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
for (let i = 0; i < stars.length; i++) {
|
||||||
|
updateStar(stars[i]);
|
||||||
|
drawStar(ctx, stars[i], time - startTime);
|
||||||
|
}
|
||||||
|
raindrops.forEach((drop) =>
|
||||||
|
updateRaindrop(drop, splashes, gridWidth, gridHeight)
|
||||||
|
);
|
||||||
|
for (let i = splashes.length - 1; i >= 0; i--) {
|
||||||
|
updateSplash(splashes[i]);
|
||||||
|
if (isSplashDead(splashes[i])) {
|
||||||
|
splashes.splice(i, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < stars.length; i++) {
|
||||||
|
updateStar(stars[i]);
|
||||||
|
drawStar(ctx, stars[i], performance.now() - startTime);
|
||||||
|
}
|
||||||
|
raindrops.forEach((drop) => drawRaindrop(ctx, drop));
|
||||||
|
splashes.forEach((splash) => drawSplash(ctx, splash));
|
||||||
|
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
};
|
||||||
|
|
||||||
|
animate(0);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<canvas
|
||||||
|
ref={canvasRef}
|
||||||
|
style={{
|
||||||
|
position: "fixed",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100vw",
|
||||||
|
height: "100vh",
|
||||||
|
imageRendering: "pixelated",
|
||||||
|
background: BACKGROUND_COLOUR,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
49
islands/Time.tsx
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useEffect, useState } from "preact/hooks";
|
||||||
|
import Box from "../components/Box.tsx";
|
||||||
|
|
||||||
|
export default function Time() {
|
||||||
|
const formatter = new Intl.DateTimeFormat("en-US", {
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
hour12: true,
|
||||||
|
timeZone: "America/Sao_Paulo",
|
||||||
|
});
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const time = formatter.format(now);
|
||||||
|
const [offset, setOffset] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const br = new Date(
|
||||||
|
now.toLocaleString("en-US", {
|
||||||
|
timeZone: "America/Sao_Paulo",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const local = new Date(now.toLocaleString("en-US"));
|
||||||
|
const diff = br.getTime() - local.getTime();
|
||||||
|
const ms = Math.abs(diff);
|
||||||
|
const hours = ~~(ms / 36e5);
|
||||||
|
const minutes = ~~((ms / 6e4) % 60);
|
||||||
|
|
||||||
|
let output = " · ";
|
||||||
|
if (hours) output += `You're ${hours} hour${hours > 1 ? "s" : ""} `;
|
||||||
|
if (hours && minutes) output += "and ";
|
||||||
|
if (minutes) output += `${minutes} minute${minutes > 1 ? "s" : ""} `;
|
||||||
|
if (hours || minutes) output += diff > 0 ? "behind" : "ahead";
|
||||||
|
else output = " · Hey, we're in the same time zone!";
|
||||||
|
|
||||||
|
setOffset(output);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
Brasília Time, {time}
|
||||||
|
{offset}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
12
main.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { App, staticFiles } from "fresh";
|
||||||
|
import { type State } from "./utils.ts";
|
||||||
|
|
||||||
|
export const app = new App<State>();
|
||||||
|
|
||||||
|
app.use(staticFiles());
|
||||||
|
app.fsRoutes();
|
||||||
31
routes/_app.tsx
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { define } from "../utils.ts";
|
||||||
|
|
||||||
|
export default define.page(function App({ Component }) {
|
||||||
|
return (
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<link rel="icon" type="image/png" href="/favicon.png" />
|
||||||
|
<meta property="og:site_name" content="acpi.at" />
|
||||||
|
<meta property="og:type" content="profile" />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content="one of the girls of all time"
|
||||||
|
/>
|
||||||
|
<meta property="og:image" content="https://acpi.at/bnuy.webp" />
|
||||||
|
<meta property="og:title" content="mux's winterhouse" />
|
||||||
|
<meta name="theme-color" content="#a0bdfc" />
|
||||||
|
<title>mux's winterhouse</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<Component />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
});
|
||||||
308
routes/index.tsx
Normal file
|
|
@ -0,0 +1,308 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { define } from "../utils.ts";
|
||||||
|
import Time from "../islands/Time.tsx";
|
||||||
|
import Rain from "../islands/Rain.tsx";
|
||||||
|
import { Centered, Section } from "../components/Layout.tsx";
|
||||||
|
import Title from "../components/Title.tsx";
|
||||||
|
import TintedImage from "../components/Tinted.tsx";
|
||||||
|
import Link from "../components/Link.tsx";
|
||||||
|
import Header from "../components/Header.tsx";
|
||||||
|
import Footer from "../components/Footer.tsx";
|
||||||
|
import Box from "../components/Box.tsx";
|
||||||
|
import Meow from "../islands/Meow.tsx";
|
||||||
|
import Name from "../islands/Name.tsx";
|
||||||
|
import Code from "../islands/Code.tsx";
|
||||||
|
import {
|
||||||
|
AttachmentIcon,
|
||||||
|
BlueskyIcon,
|
||||||
|
CodebergIcon,
|
||||||
|
DiscordIcon,
|
||||||
|
ForgejoIcon,
|
||||||
|
GitHubIcon,
|
||||||
|
LabelIcon,
|
||||||
|
LastfmIcon,
|
||||||
|
MailIcon,
|
||||||
|
SignalIcon,
|
||||||
|
TwitterIcon,
|
||||||
|
CommentIcon,
|
||||||
|
HeartIcon,
|
||||||
|
KofiIcon,
|
||||||
|
BitcoinIcon,
|
||||||
|
BitcoinCashIcon,
|
||||||
|
MoneroIcon,
|
||||||
|
NanoIcon,
|
||||||
|
LitecoinIcon,
|
||||||
|
EthereumIcon,
|
||||||
|
} from "../components/Icon.tsx";
|
||||||
|
|
||||||
|
export default define.page(() => {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Rain />
|
||||||
|
<Centered>
|
||||||
|
<Header />
|
||||||
|
<Section>
|
||||||
|
<Title>
|
||||||
|
<HeartIcon class="icon" />
|
||||||
|
About me
|
||||||
|
</Title>
|
||||||
|
<p class="intro">
|
||||||
|
<span>
|
||||||
|
{" "}
|
||||||
|
muxiepuff{" "}
|
||||||
|
<span class="alt alt-font">(/ˈmuˌksipʌf/, she/her)</span>
|
||||||
|
{" · "}
|
||||||
|
</span>
|
||||||
|
I'm just your average advocate for open access to information and
|
||||||
|
knowledge and aspiring electrical engineer with complementary
|
||||||
|
interests in systems programming and linguistics. Neurodivergent
|
||||||
|
student passionate about libre software.
|
||||||
|
</p>
|
||||||
|
<p class="intro">
|
||||||
|
On the side, I maintain a modest FreeBSD server where I self-host
|
||||||
|
this website and various services. This is nothing particularly
|
||||||
|
impressive, just a growing curiosity about systems administration.
|
||||||
|
Service status and uptime available{" "}
|
||||||
|
<Link href="https://glance.acpi.at">here</Link>.
|
||||||
|
</p>
|
||||||
|
<Time />
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<Title>
|
||||||
|
<LabelIcon class="icon" />
|
||||||
|
Links
|
||||||
|
</Title>
|
||||||
|
<div>
|
||||||
|
<Title level={3}>Social platforms</Title>
|
||||||
|
<ul class="labeled-icons">
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://bsky.app/profile/acpi.at">
|
||||||
|
<BlueskyIcon /> @acpi.at
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://last.fm/user/auwora">
|
||||||
|
<LastfmIcon /> @auwora
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Title level={3}>Software and code</Title>
|
||||||
|
<ul class="labeled-icons">
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://git.acpi.at/mux">
|
||||||
|
<ForgejoIcon /> mux
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://codeberg.org/ex">
|
||||||
|
<CodebergIcon /> ex
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://github.com/xwra">
|
||||||
|
<GitHubIcon /> xwra
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Title level={3}>Congenial folks</Title>
|
||||||
|
<ul class="labeled-icons b88x31">
|
||||||
|
<li>
|
||||||
|
<Meow />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://worf.win">
|
||||||
|
<img
|
||||||
|
src="https://worf.win/images/worfwin.gif"
|
||||||
|
title="worf"
|
||||||
|
alt="worf"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://codeberg.org/paige">
|
||||||
|
<img src="/88x31/paige.gif" title="paige" alt="paige" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://mugman.tech">
|
||||||
|
<img
|
||||||
|
src="https://mugman.tech/88x31/me.gif"
|
||||||
|
title="mugman"
|
||||||
|
alt="mugman"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://f.dog">
|
||||||
|
<img
|
||||||
|
src="https://x86.pet/~strawberry/june_88x31.png"
|
||||||
|
title="june"
|
||||||
|
alt="june"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
rel="noopener"
|
||||||
|
referrerpolicy="strict-origin"
|
||||||
|
href="https://rushii.dev"
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src="https://rushii.dev/88x31/rushii.webp"
|
||||||
|
title="rushii's site"
|
||||||
|
alt="rushii's site"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://www.juwuba.xyz">
|
||||||
|
<img
|
||||||
|
src="https://www.juwuba.xyz/88x31.gif"
|
||||||
|
title="Júlia"
|
||||||
|
alt="Júlia"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://katelyn.moe/">
|
||||||
|
<img
|
||||||
|
src="https://katelyn.moe/8831.png"
|
||||||
|
title="katelyn"
|
||||||
|
alt="katelyn"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://meow-d.github.io">
|
||||||
|
<img src="/88x31/meow_d.webp" title="meow_d" alt="meow_d" />
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://caitlyn.moe">
|
||||||
|
<img
|
||||||
|
src="https://caitlyn.moe/88x31.png"
|
||||||
|
title="caitlyn"
|
||||||
|
alt="caitlyn"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<Title>
|
||||||
|
<CommentIcon class="icon" />
|
||||||
|
Contact
|
||||||
|
</Title>
|
||||||
|
<p class="intro">
|
||||||
|
Feel free to reach out through any of the platforms listed above.
|
||||||
|
For email correspondence, you can reach me at{" "}
|
||||||
|
<Code>base64 -d <<< bXV4QGFjcGkuYXQK</Code>.
|
||||||
|
</p>
|
||||||
|
<p class="intro">
|
||||||
|
Psst! When discussing sensitive matters over email or other insecure
|
||||||
|
communication channels, I'd really appreciate it if you could
|
||||||
|
encrypt your message with my <Link href="/pgp-key.asc">PGP key</Link>{" "}
|
||||||
|
(fingerprint:{" "}
|
||||||
|
<Code>AC14 9A39 5013 C572 CA74 8799 BCD2 117C 99E6 9817</Code>).
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<Title level={3}>Communication</Title>
|
||||||
|
<ul class="labeled-icons">
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://discord.com/users/797566974024351745">
|
||||||
|
<DiscordIcon /> @muxiepuff
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box
|
||||||
|
as="a"
|
||||||
|
href="https://signal.me/#eu/gdveRFng4iFhFkB4m5Esr2ciQ5FWZTeSFrCGa2osQ7ZrSu2d48RCdqgDc2nEzWQq"
|
||||||
|
>
|
||||||
|
<SignalIcon /> @mux.01
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
<p class="intro">
|
||||||
|
If you enjoy throwing money at people on the internet, please
|
||||||
|
consider me! It keeps the server alive, fuels my tinkering with
|
||||||
|
esoteric technology, and helps me navigate some rough financial
|
||||||
|
patches and stay afloat while things are tight.
|
||||||
|
</p>
|
||||||
|
<div>
|
||||||
|
<Title level={3}>Donations</Title>
|
||||||
|
<ul class="labeled-icons full">
|
||||||
|
<li>
|
||||||
|
<Box as="a" href="https://ko-fi.com/west">
|
||||||
|
<KofiIcon /> west
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<MoneroIcon />{" "}
|
||||||
|
456tmp4CU158sV95Pjh75QfMcANNsRCFTdpmc86G31eKg9urvcYunnuBVyUmazAbuihUXwRDVojkhFTbzg6X2GuAJ7t3MCT
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<LitecoinIcon /> ltc1ql30jtr2r0pkr0wshspf465rdupvlpmwacql7wp
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<NanoIcon />{" "}nano_1qbbwrzxnutw53kuykdo6zcwteawf3befpbf95zrz66b33yxpz5ans6wrw4w
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<BitcoinIcon />{" "} bc1qj3l6cpxz604vlfz5zqlnhzr7g0aq7xlslnmzcm
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<BitcoinCashIcon />{" "}bitcoincash:qrs8y70dwzvz2kvxnv30c9k4pxa0vatzvudle5aaa3
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<Box as="span">
|
||||||
|
<EthereumIcon />{" "}0x9bb671035Dde19C674769592AbC20E63f36b5Aa9
|
||||||
|
</Box>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</Section>
|
||||||
|
<Section>
|
||||||
|
<Title>
|
||||||
|
<AttachmentIcon class="icon" />
|
||||||
|
Extra
|
||||||
|
</Title>
|
||||||
|
<ul id="extra">
|
||||||
|
<li>
|
||||||
|
<TintedImage
|
||||||
|
src="/extra/mumengo.gif"
|
||||||
|
colour="var(--theme-accent-title)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<TintedImage
|
||||||
|
src="/extra/nenezil.gif"
|
||||||
|
colour="var(--theme-accent-title)"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</Section>
|
||||||
|
<Footer />
|
||||||
|
</Centered>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
31
scripts/copyright.ts
Executable file
|
|
@ -0,0 +1,31 @@
|
||||||
|
#!/usr/bin/env -S deno run --allow-read --allow-write
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { walk } from "https://deno.land/std/fs/walk.ts";
|
||||||
|
|
||||||
|
const copyrightHeader = `/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
`;
|
||||||
|
|
||||||
|
const dir = "./";
|
||||||
|
|
||||||
|
for await (
|
||||||
|
const entry of walk(dir, {
|
||||||
|
exts: [".ts", ".tsx"],
|
||||||
|
includeDirs: false,
|
||||||
|
skip: [/node_modules/, /copyright\.ts$/],
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
const filePath = entry.path;
|
||||||
|
const content = await Deno.readTextFile(filePath);
|
||||||
|
|
||||||
|
if (!content.startsWith(copyrightHeader)) {
|
||||||
|
await Deno.writeTextFile(filePath, copyrightHeader + "\n" + content);
|
||||||
|
console.log(`Added header to ${filePath}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
static/88x31.gif
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
static/88x31.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
BIN
static/88x31/meow_d.webp
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
static/88x31/paige.gif
Normal file
|
After Width: | Height: | Size: 1,021 B |
BIN
static/88x31@1000.gif
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
static/88x31@1000.png
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
static/bnuy.webp
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
static/extra/mumengo.gif
Normal file
|
After Width: | Height: | Size: 2.7 MiB |
BIN
static/extra/nenezil.gif
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
static/favicon.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
static/fonts/jersey15.ttf
Normal file
BIN
static/fonts/m6x11.ttf
Normal file
BIN
static/fonts/m6x11plus.ttf
Normal file
BIN
static/fonts/str.ttf
Normal file
BIN
static/icons/smiley.gif
Normal file
|
After Width: | Height: | Size: 36 KiB |
243
static/logo.svg
Normal file
|
|
@ -0,0 +1,243 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg
|
||||||
|
version="1.1"
|
||||||
|
width="380"
|
||||||
|
height="380"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
shape-rendering="crispEdges"
|
||||||
|
>
|
||||||
|
<rect x="80" y="70" width="30" height="10" fill="#1E2545" />
|
||||||
|
<rect x="270" y="70" width="30" height="10" fill="#1E2545" />
|
||||||
|
<rect x="60" y="80" width="20" height="10" fill="#1E2545" />
|
||||||
|
<rect x="80" y="80" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="90" y="80" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="100" y="80" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="110" y="80" width="20" height="10" fill="#1E2545" />
|
||||||
|
<rect x="250" y="80" width="20" height="10" fill="#1E2545" />
|
||||||
|
<rect x="270" y="80" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="280" y="80" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="290" y="80" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="300" y="80" width="20" height="10" fill="#1E2545" />
|
||||||
|
<rect x="50" y="90" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="60" y="90" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="70" y="90" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="100" y="90" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="120" y="90" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="130" y="90" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="240" y="90" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="250" y="90" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="260" y="90" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="290" y="90" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="310" y="90" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="320" y="90" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="40" y="100" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="50" y="100" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="60" y="100" width="40" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="100" y="100" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="110" y="100" width="30" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="140" y="100" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="230" y="100" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="240" y="100" width="30" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="270" y="100" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="280" y="100" width="40" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="320" y="100" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="330" y="100" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="30" y="110" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="40" y="110" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="50" y="110" width="10" height="70" fill="#DFF2FE" />
|
||||||
|
<rect x="100" y="110" width="10" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="110" y="110" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="120" y="110" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="130" y="110" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="150" y="110" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="220" y="110" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="230" y="110" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="250" y="110" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="260" y="110" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="270" y="110" width="10" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="320" y="110" width="10" height="70" fill="#DFF2FE" />
|
||||||
|
<rect x="330" y="110" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="340" y="110" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="20" y="120" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="30" y="120" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="40" y="120" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="110" y="120" width="20" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="130" y="120" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="140" y="120" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="150" y="120" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="160" y="120" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="210" y="120" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="220" y="120" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="230" y="120" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="240" y="120" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="250" y="120" width="20" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="330" y="120" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="340" y="120" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="350" y="120" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="10" y="130" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="20" y="130" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="30" y="130" width="10" height="40" fill="#DFF2FE" />
|
||||||
|
<rect x="130" y="130" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="140" y="130" width="10" height="30" fill="#FAFEFF" />
|
||||||
|
<rect x="150" y="130" width="20" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="170" y="130" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="200" y="130" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="210" y="130" width="20" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="230" y="130" width="10" height="30" fill="#FAFEFF" />
|
||||||
|
<rect x="240" y="130" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="340" y="130" width="10" height="40" fill="#DFF2FE" />
|
||||||
|
<rect x="350" y="130" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="360" y="130" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="0" y="140" width="10" height="30" fill="#1E2545" />
|
||||||
|
<rect x="10" y="140" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="170" y="140" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="180" y="140" width="20" height="10" fill="#1E2545" />
|
||||||
|
<rect x="200" y="140" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="360" y="140" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="370" y="140" width="10" height="30" fill="#1E2545" />
|
||||||
|
<rect x="10" y="150" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="170" y="150" width="10" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="180" y="150" width="20" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="200" y="150" width="10" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="360" y="150" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="10" y="160" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="60" y="160" width="10" height="10" fill="#DFF2FE" />
|
||||||
|
<rect x="70" y="160" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="80" y="160" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="90" y="160" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="130" y="160" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="140" y="160" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="180" y="160" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="230" y="160" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="240" y="160" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="270" y="160" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="290" y="160" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="300" y="160" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="310" y="160" width="10" height="10" fill="#DFF2FE" />
|
||||||
|
<rect x="360" y="160" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="10" y="170" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="30" y="170" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="60" y="170" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="70" y="170" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="90" y="170" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="100" y="170" width="40" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="180" y="170" width="20" height="70" fill="#DFF2FE" />
|
||||||
|
<rect x="240" y="170" width="40" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="280" y="170" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="290" y="170" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="310" y="170" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="340" y="170" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="360" y="170" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="20" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="40" y="180" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="50" y="180" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="60" y="180" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="80" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="100" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="110" y="180" width="30" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="170" y="180" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="200" y="180" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="240" y="180" width="30" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="270" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="290" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="300" y="180" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="320" y="180" width="10" height="10" fill="#FAFEFF" />
|
||||||
|
<rect x="330" y="180" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="350" y="180" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="30" y="190" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="40" y="190" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="50" y="190" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="70" y="190" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="90" y="190" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="100" y="190" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="150" y="190" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="160" y="190" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="210" y="190" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="220" y="190" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="270" y="190" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="280" y="190" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="300" y="190" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="310" y="190" width="20" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="330" y="190" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="340" y="190" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="40" y="200" width="30" height="10" fill="#1E2545" />
|
||||||
|
<rect x="100" y="200" width="10" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="150" y="200" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="160" y="200" width="10" height="120" fill="#DFF2FE" />
|
||||||
|
<rect x="210" y="200" width="10" height="120" fill="#DFF2FE" />
|
||||||
|
<rect x="220" y="200" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="270" y="200" width="10" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="310" y="200" width="30" height="10" fill="#1E2545" />
|
||||||
|
<rect x="80" y="210" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="90" y="210" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="110" y="210" width="20" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="130" y="210" width="20" height="10" fill="#333C64" />
|
||||||
|
<rect x="150" y="210" width="10" height="110" fill="#DFF2FE" />
|
||||||
|
<rect x="220" y="210" width="10" height="110" fill="#DFF2FE" />
|
||||||
|
<rect x="230" y="210" width="20" height="10" fill="#333C64" />
|
||||||
|
<rect x="250" y="210" width="20" height="30" fill="#A0BDFC" />
|
||||||
|
<rect x="280" y="210" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="290" y="210" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="90" y="220" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="130" y="220" width="20" height="30" fill="#1E2545" />
|
||||||
|
<rect x="230" y="220" width="20" height="30" fill="#1E2545" />
|
||||||
|
<rect x="280" y="220" width="10" height="50" fill="#A0BDFC" />
|
||||||
|
<rect x="70" y="230" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="80" y="230" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="290" y="230" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="300" y="230" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="80" y="240" width="10" height="80" fill="#A0BDFC" />
|
||||||
|
<rect x="110" y="240" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="120" y="240" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="170" y="240" width="40" height="10" fill="#FC9797" />
|
||||||
|
<rect x="250" y="240" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="260" y="240" width="10" height="10" fill="#A0BDFC" />
|
||||||
|
<rect x="290" y="240" width="10" height="80" fill="#A0BDFC" />
|
||||||
|
<rect x="60" y="250" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="70" y="250" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="110" y="250" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="120" y="250" width="10" height="70" fill="#DFF2FE" />
|
||||||
|
<rect x="130" y="250" width="20" height="10" fill="#EBF5FF" />
|
||||||
|
<rect x="170" y="250" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="180" y="250" width="20" height="10" fill="#FC9797" />
|
||||||
|
<rect x="200" y="250" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="230" y="250" width="20" height="10" fill="#EBF5FF" />
|
||||||
|
<rect x="250" y="250" width="10" height="70" fill="#DFF2FE" />
|
||||||
|
<rect x="260" y="250" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="300" y="250" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="310" y="250" width="10" height="20" fill="#1E2545" />
|
||||||
|
<rect x="70" y="260" width="10" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="100" y="260" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="110" y="260" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="130" y="260" width="20" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="180" y="260" width="20" height="10" fill="#DFF2FE" />
|
||||||
|
<rect x="230" y="260" width="20" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="260" y="260" width="10" height="60" fill="#DFF2FE" />
|
||||||
|
<rect x="270" y="260" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="300" y="260" width="10" height="60" fill="#A0BDFC" />
|
||||||
|
<rect x="50" y="270" width="10" height="40" fill="#1E2545" />
|
||||||
|
<rect x="60" y="270" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="90" y="270" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="100" y="270" width="10" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="180" y="270" width="20" height="10" fill="#9EAEBA" />
|
||||||
|
<rect x="270" y="270" width="10" height="50" fill="#DFF2FE" />
|
||||||
|
<rect x="280" y="270" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="310" y="270" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="320" y="270" width="10" height="40" fill="#1E2545" />
|
||||||
|
<rect x="60" y="280" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="90" y="280" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="170" y="280" width="10" height="10" fill="#B8CAD9" />
|
||||||
|
<rect x="180" y="280" width="20" height="40" fill="#DFF2FE" />
|
||||||
|
<rect x="200" y="280" width="10" height="10" fill="#B8CAD9" />
|
||||||
|
<rect x="280" y="280" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="310" y="280" width="10" height="20" fill="#A0BDFC" />
|
||||||
|
<rect x="170" y="290" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="200" y="290" width="10" height="30" fill="#DFF2FE" />
|
||||||
|
<rect x="60" y="300" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="310" y="300" width="10" height="10" fill="#5E76AB" />
|
||||||
|
<rect x="60" y="310" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="90" y="310" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="280" y="310" width="10" height="10" fill="#C0D4FF" />
|
||||||
|
<rect x="310" y="310" width="10" height="10" fill="#1E2545" />
|
||||||
|
<rect x="70" y="320" width="240" height="10" fill="#1E2545" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 15 KiB |
51
static/pgp-key.asc
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
|
||||||
|
mQINBGjoPAMBEADB76TXkUXWbHfE/Nilu0LLmyOxC9hzcsqGp1vSVJq8/8RqFAqM
|
||||||
|
Wy3DxpEhsCnqwTKaI53SzngZnEvM4wn2UNCJ1c3KUNiKzh6w4vt8cPtJo35ywJDe
|
||||||
|
0R8EHR8LPc6QPQ/vBxhMxN6qOMBwdgOEcVNgYGPpjisSsLXgcEeQvGWOeMS3BBwi
|
||||||
|
1JMsSOBBnNQnFkrRu5qNb1gc+g/jA/U9Mi19fACyXe+Qn6QkvLQtNt3ocRoMBokn
|
||||||
|
yTkYOPAcp3uZjPql1gJg8IjmlDuxDTnYu62e3NHMpztq0xvsAQFOO6M2CCt63SaQ
|
||||||
|
Pjeo++R5c6Nu1SvfrShania5LpvQF7Kw3hXsf06SIjzy+8D1L5Gme1Jw0NZcSUoX
|
||||||
|
D+m/Z7PxG6iHHcn8YJKt6wln29UdNHIHyQyeN7J8/im37plHKmeufrEB8Tt5B7Nn
|
||||||
|
yW3Slq0CzlYaT7vILKGKACsDnjPnA8EZYDICI//svffTHzpxAyXYPLAiUhwtA+4M
|
||||||
|
TgQdEEWYJfVYB2yhvj71o6dNwtBvZLfBSgOSUr4hzoogES4V1Zx6wvZMr4hAcHPj
|
||||||
|
BXDqBXgNGGseLH5BIdeoi4ENgdwiFsx2Os50QoCrrcl+cg8ithnUUFGAIQ70HG/Z
|
||||||
|
mtyl3zeCNs4G0jFYlsGTaOJtWEiAr04/4FM6JmKi6WEnwo/IHqDdAqdqZQARAQAB
|
||||||
|
tBVrYmRtdXggPHNzZHRAYWNwaS5hdD6JAk4EEwEKADgWIQSsFJo5UBPFcsp0h5m8
|
||||||
|
0hF8meaYFwUCaOg8AwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRC80hF8
|
||||||
|
meaYF1INEACdVNNZQz4WfoiHyxVh8M4zaYlTBVy9VRjlCMetc7Vziw5SF9Cz/9tl
|
||||||
|
VCJdbYHyakqqzsXezB9M9/3tldoIKUq+/ukjr64eK4pcPK83ov9puufHKK76sG6W
|
||||||
|
j5v7xY7Ni7tOZQQmALeEUfVL7JXU4EnUi6MDR4Uht/qgecFhAaCTlibRF2gf5DPN
|
||||||
|
O6W8yVkWzkTvFiVXpE2jY+mcD19wYrdbFTPcBp7Pjvq1mjTjkTR/OrlJz9LVUTuo
|
||||||
|
Ipf33GjBepwiLWbZ5sr7ATEk9f19H6xf4GikHUm2dstEwvmlrRtH+bTyRmOLuqlO
|
||||||
|
mtIWkY0VO7a7IyYaogYVq1CxpJ/4g8uy0kzYqOrgNySBKmUMY/zm+QpyvIqOSnix
|
||||||
|
kSgkqOyjxXX0rsL9+erC7uCyMwo07FDFmY2aGdG5rTOkeBESgRWm743e8CLV75H5
|
||||||
|
sdQQbXMrV3Nh+HMRZBNrwIR4C93je1vWguKwn3BUJnX6Z+Iv9szT7wPh4GwtLUbu
|
||||||
|
c7E35jtkvaD6r4M0I1fdeIHpS9S0NM87L2S/tSHpCB5MOngAywrPlVi89qyX3X8p
|
||||||
|
4e92anXq6bCsiY4u4yY68jHnqbTKvwu+P+gQYgbzGV+JFmg5hKvkFnyR6yiR+vU3
|
||||||
|
oBKpEYjg/U+isDtHWNNGvpewLmSMHm2XaD/7yIu9OwB58onqLZJYrbkCDQRo6DwD
|
||||||
|
ARAAwKmGKak4tFAVuRSqd+hMPoVZKjJkoaGEqxjmZz70OFl7l+1B3dLYXdmlQHDQ
|
||||||
|
CkOhIkifuk8qo9wurC4w5eHw4hNorlg4ukHjYJFr/xWI/ijnJNaEJL3Z7cdKUPJt
|
||||||
|
oIKJ8+SIG0M635Q4i25HufD/J+qwpJxWwKw9rh+7ejHbwX7f97Vss8SfJWvXZZDc
|
||||||
|
qdK3d3DUoHrj8i5jLndg4kEcOP5lKGsnAsnPnpIHiStsRfrolJv2FEVoVqaRUTPm
|
||||||
|
frabjqa/9Mn9nMYYeP+wUbjWx6Cb2u3HGMTn/eO12IG5kmdNMCz5DOuyHE9Vr/v6
|
||||||
|
upfJkv7K/uqNJ4ZhDtac+rd6Xfu340ldJq6lAz5zHCNNIhFu3A3XMUvotNiEOpG9
|
||||||
|
YanQ+ZLbD37Tbi0o1Hh3quS43X0oSrSUzOlU/bVBIqGnGWqixUOAwq2Oq9yjO/BJ
|
||||||
|
RX/UrrZBF239xLRTD8X47Srkk++ODFQXvtnVWJrlJCuzxfUn96ZNBvGDwxCJnx6u
|
||||||
|
A/RpwArh8F9eF4MbIkSJ8sOqqlFcPIxt+r/CIg8XVh5MYf8KdqTFtI60yvgNYPQ8
|
||||||
|
MR+r4EEy8SieT0y3Aayz0r2zmPrBpAc5b1mTKMeeLbY4rQz3Zn6ELvH+d4pCp9N3
|
||||||
|
R++cJYETJzTY9nIsLgy7MTv2W6fzQRuciFKzr+cuoMz3DpUAEQEAAYkCNgQYAQoA
|
||||||
|
IBYhBKwUmjlQE8VyynSHmbzSEXyZ5pgXBQJo6DwDAhsMAAoJELzSEXyZ5pgX22YP
|
||||||
|
/3+JludvTlMd4E9YDM6Cy2NG44NTPpSJWeovCJB6FktPDIAOPCfDJxpO25veFuD1
|
||||||
|
fgcwuq1mWNx1WVRcIoOtMvmG07Enr26RNZ/fcB2OrWbsS1JCagxNT7iT+rDXocJA
|
||||||
|
mohJo+ntSuNb29YwqHoL7b41B/K8xseLT9hXmf76a+sl/Px7oMwV9+TaH6200SIa
|
||||||
|
q+Fa+geZcpr/lIBYAM1UqrRTOm8kl6qR6zToVuO9PwDY7qbUHCKFODGNdbrBNvE5
|
||||||
|
VGfTWNY2ptAh1D7iA24ucDeBqu3wXy8X73cSDAeHTtExZvSE9KkNWhfpE4p4vgv7
|
||||||
|
2x4OqxgGV3Ws7gBd7Hy88gwrEMMc7kIrC73KeIZc9bSKbDIFfHY97GateNxwmevd
|
||||||
|
Tho5scC8wcjmu8cnSQhREpDyIg0zJzkdoPO6gUIpiDIcArxlI+EAV8zrg0PfTUai
|
||||||
|
0a1eS3kOKOLMsLZGMVit/NHaapw9rx6Xtd/Y4G+80vKZgy9xSyShsmepGn91VlyH
|
||||||
|
f8LHpkBgW96eAMjOS2Tjjia2VYUV/rlrHXBnEYY9InhuS3VPJTenF33LC+L6XjM2
|
||||||
|
6jFZUpkX3kG3mdLfotOe3T6J87YvKBDMoAKKKdN1ozL07j9gw2SfkfMaYzox6ET/
|
||||||
|
3zT5OZ3rt9VScClr1Kq0AeFMynDfnMVL+nswVATpgawK
|
||||||
|
=XHXh
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
14
utils.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { createDefine } from "fresh";
|
||||||
|
|
||||||
|
// This specifies the type of "ctx.state" which is used to share
|
||||||
|
// data among middlewares, layouts and routes.
|
||||||
|
export interface State {
|
||||||
|
shared: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const define = createDefine<State>();
|
||||||
11
vite.config.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2025 xwra
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import { fresh } from "@fresh/plugin-vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [fresh()],
|
||||||
|
});
|
||||||