Compare commits
12 Commits
349102301c
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 35aa3b076f | |||
| 2e397c5be3 | |||
| f1c471839f | |||
| bfd8c187c6 | |||
| 510897d94b | |||
| 078634b664 | |||
| 6a5176caab | |||
|
|
282c67b1fc | ||
| 74224bd648 | |||
| 8f217ec82b | |||
| 0e1055b1a6 | |||
| 94c56d7405 |
6
.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
.idea/
|
||||
dist/
|
||||
pkg/
|
||||
target/
|
||||
Cargo.lock
|
||||
static/
|
||||
37
Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "web_iouring"
|
||||
version = "0.0.0-develop"
|
||||
authors = ["iouring"]
|
||||
license = "Apache-2.0"
|
||||
edition = "2024"
|
||||
rust-version = "1.91"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
js-sys = "0.3"
|
||||
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
|
||||
"csr",
|
||||
] }
|
||||
yew-router = { git = "https://github.com/yewstack/yew.git", rev = "21f373b" }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
|
||||
log = { version = "0.4" }
|
||||
wasm-logger = { version = "0.2" }
|
||||
|
||||
serde = { version = "1.0" }
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
serde_json = "1.0.143"
|
||||
|
||||
lsp-types = "0.97"
|
||||
chrono = "0.4"
|
||||
|
||||
yew-markdown = { git = "https://git.celesteflare.cc/i0uring/page_md.git", rev = "1e9840" }
|
||||
yew-hooks = "0.3"
|
||||
[patch.crates-io]
|
||||
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
|
||||
"csr",
|
||||
], optional = false }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3"
|
||||
features = ["HtmlCanvasElement", "WebGlBuffer", "WebGlProgram", "WebGlRenderingContext", "WebGl2RenderingContext", "WebGlShader", "WebGlUniformLocation", "Navigator", "Clipboard"]
|
||||
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
||||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
||||
2
Trunk.toml
Normal file
@@ -0,0 +1,2 @@
|
||||
[tools]
|
||||
wasm_opt = "version_122"
|
||||
62
index.html
@@ -1,49 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="theme-dark">
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>i0ur.ing ~ welcome</title>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/misc/favicon.ico" />
|
||||
<link data-trunk rel="copy-file" href="public/misc/404.png" />
|
||||
<link data-trunk rel="copy-file" href="public/misc/profile.avif" />
|
||||
<link data-trunk rel="copy-file" href="public/misc/oneko.gif" />
|
||||
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/misc/freemono.ttf" />
|
||||
<link data-trunk rel="copy-file" href="public/misc/monocraft.ttf" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="/main-5f0876a079f6ac58.css" integrity="sha384-Sa1ZWpdYJVb1uRRURVr4pvhoJEjjsLIMS2kyltK9FZdLGYa55YVhC47tdfBRErb+"/>
|
||||
|
||||
<script type="module">
|
||||
import init, * as bindings from '/web_iouring-8ac0645919329a76.js';
|
||||
const wasm = await init({ module_or_path: '/web_iouring-8ac0645919329a76_bg.wasm' });
|
||||
<link data-trunk rel="copy-file" href="public/shaders/post.bloom.svg" />
|
||||
|
||||
<link data-trunk rel="copy-file" href="public/buttons/iouring.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/wasm.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/dataforest.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/servfail.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/rust.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/csharp.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/kotlin.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/java.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/fedora.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/artix.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/nixos.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/chimera.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/void.png" />
|
||||
<link data-trunk rel="copy-file" href="public/buttons/aqueer.png" />
|
||||
|
||||
window.wasmBindings = bindings;
|
||||
|
||||
|
||||
dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}}));
|
||||
|
||||
</script>
|
||||
<base href="/" />
|
||||
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css" />
|
||||
<link rel="modulepreload" href="/web_iouring-8ac0645919329a76.js" crossorigin="anonymous" integrity="sha384-KPFxJ5+Aa+ZaSfSE63zjwFcXMH3E+vHNFH4J/VsMxr1NE1pixe0oIzXatlghxyv6"><link rel="preload" href="/web_iouring-8ac0645919329a76_bg.wasm" crossorigin="anonymous" integrity="sha384-NOiLI21zp8RPEUB76WCpoBPABF5V2fkwSiFS7VhnzEbvHeALjfZEWKWN59UWzI71" as="fetch" type="application/wasm"></head>
|
||||
<link data-trunk rel="sass" href="main.scss" />
|
||||
<link data-trunk rel="rust" />
|
||||
<base data-trunk-public-url />
|
||||
</head>
|
||||
|
||||
</html>
|
||||
13
justfile
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env just --justfile
|
||||
|
||||
setup:
|
||||
rustup target add wasm32-unknown-unknown
|
||||
|
||||
install:
|
||||
cargo install --locked wasm-pack trunk --force
|
||||
|
||||
debug:
|
||||
trunk serve
|
||||
|
||||
build:
|
||||
trunk build --release
|
||||
@@ -1,70 +0,0 @@
|
||||
@font-face {
|
||||
font-family: monocraft;
|
||||
src: url("monocraft.ttf");
|
||||
}
|
||||
html * {
|
||||
font-family: monocraft, monospace !important;
|
||||
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
|
||||
scrollbar-width: thin !important;
|
||||
scroll-behavior: smooth !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
-ms-overflow-style: none !important;
|
||||
scrollbar-width: none !important;
|
||||
}
|
||||
|
||||
.textarea::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
backdrop-filter: blur(4px) !important;
|
||||
--bulma-box-background-color: #1E1E2E80 !important;
|
||||
--bulma-shadow-h: 232 !important;
|
||||
--bulma-shadow-s: 97% !important;
|
||||
--bulma-shadow-l: 85% !important;
|
||||
--bulma-shadow: 0 0.5em 1em -0.125em hsla(var(--bulma-shadow-h),
|
||||
var(--bulma-shadow-s),
|
||||
var(--bulma-shadow-l),
|
||||
0.3), 0 0px 0 1px hsla(var(--bulma-shadow-h),
|
||||
var(--bulma-shadow-s),
|
||||
var(--bulma-shadow-l),
|
||||
0.02) !important;
|
||||
}
|
||||
|
||||
.input,
|
||||
.select select,
|
||||
.textarea {
|
||||
backdrop-filter: blur(4px);
|
||||
--bulma-input-h: 232 !important;
|
||||
--bulma-input-s: 97% !important;
|
||||
--bulma-input-l: 85% !important;
|
||||
--bulma-input-background-h: var(--bulma-input-h) !important;
|
||||
--bulma-input-background-s: var(--bulma-input-s) !important;
|
||||
--bulma-input-background-l: var(--bulma-input-l) !important;
|
||||
background-color: hsla(var(--bulma-input-h), var(--bulma-input-s), var(--bulma-input-background-l), 0.213125) !important;
|
||||
border-color: hsl(var(--bulma-input-h), var(--bulma-input-s), var(--bulma-input-l)) !important;
|
||||
}
|
||||
|
||||
a.navbar-item,
|
||||
a.navbar-item:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: hsla(232, 97%, 85%, 0.4125) !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: hsla(232, 97%, 85%, 0.4125) !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline !important;
|
||||
filter: url("post.bloom.svg#process") !important;
|
||||
}
|
||||
81
main.scss
Normal file
@@ -0,0 +1,81 @@
|
||||
@charset "UTF-8";
|
||||
|
||||
@font-face {
|
||||
font-family: freemono;
|
||||
src: url("freemono.ttf");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: monocraft;
|
||||
src: url("monocraft.ttf");
|
||||
}
|
||||
|
||||
html * {
|
||||
font-family: freemono, monocraft, monospace !important;
|
||||
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
|
||||
scrollbar-width: thin !important;
|
||||
scroll-behavior: smooth !important;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
-ms-overflow-style: none !important;
|
||||
scrollbar-width: none !important;
|
||||
}
|
||||
|
||||
.textarea::-webkit-scrollbar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.box {
|
||||
backdrop-filter: blur(4px) !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
backdrop-filter: blur(4px) !important;
|
||||
}
|
||||
|
||||
.input,
|
||||
.select select,
|
||||
.textarea {
|
||||
backdrop-filter: blur(4px);
|
||||
background-color: white, 0.213125 !important;
|
||||
border-color: white !important;
|
||||
}
|
||||
|
||||
a.navbar-item,
|
||||
a.navbar-item:hover {
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
::-moz-selection {
|
||||
background: #F5C2E740 !important;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: #F5C2E740 !important;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #F5C2E7FF !important;
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline !important;
|
||||
filter: url("post.bloom.svg#process") !important;
|
||||
}
|
||||
|
||||
button {
|
||||
background-color: transparent !important;
|
||||
border-color: transparent !important;
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
text-decoration: underline !important;
|
||||
filter: url("post.bloom.svg#process") !important;
|
||||
}
|
||||
BIN
profile.avif
|
Before Width: | Height: | Size: 139 KiB |
BIN
public/blog/testimage.jpg
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
BIN
public/misc/404.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
BIN
public/misc/freemono.ttf
Normal file
|
Before Width: | Height: | Size: 315 B After Width: | Height: | Size: 315 B |
BIN
public/misc/profile.avif
Normal file
|
After Width: | Height: | Size: 224 KiB |
179
public/shaders/es3/bg.frag
Normal file
@@ -0,0 +1,179 @@
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
const float CAM_FAR = 20.0;
|
||||
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
|
||||
|
||||
uniform float time;
|
||||
uniform vec2 resolution;
|
||||
uniform bool layer_snow;
|
||||
|
||||
in vec4 vColor;
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
|
||||
vec3 artifactOffset;
|
||||
mat3 artifactRotation;
|
||||
vec3 artifactAxis;
|
||||
vec3 camFwd;
|
||||
vec3 camUp;
|
||||
|
||||
float rand(float n) {
|
||||
return fract(sin(n) * 43758.5453);
|
||||
}
|
||||
|
||||
float noise(float x) {
|
||||
float i = floor(x);
|
||||
float f = fract(x);
|
||||
return mix(rand(i), rand(i + 1.0), f * f * (3.0 - 2.0 * f));
|
||||
}
|
||||
|
||||
mat4 viewMatrix(vec3 dir, vec3 up) {
|
||||
vec3 f = normalize(dir);
|
||||
vec3 s = normalize(cross(f, up));
|
||||
vec3 u = cross(s, f);
|
||||
|
||||
return mat4(
|
||||
vec4(s, 0.0),
|
||||
vec4(u, 0.0),
|
||||
vec4(-f, 0.0),
|
||||
vec4(0.0, 0.0, 0.0, 1.0)
|
||||
);
|
||||
}
|
||||
|
||||
mat3 rotationAlign(vec3 d, vec3 z) {
|
||||
vec3 v = cross(z, d);
|
||||
float c = dot(z, d);
|
||||
float k = 1.0 / (1.0 + c);
|
||||
|
||||
float k_vx = v.x * k;
|
||||
float k_vy = v.y * k;
|
||||
float k_vz = v.z * k;
|
||||
|
||||
return mat3(
|
||||
k_vx * v.x + c, k_vx * v.y - v.z, k_vx * v.z + v.y,
|
||||
k_vy * v.x + v.z, k_vy * v.y + c, k_vy * v.z - v.x,
|
||||
k_vz * v.x - v.y, k_vz * v.y + v.x, k_vz * v.z + c
|
||||
);
|
||||
}
|
||||
|
||||
float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
|
||||
return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
||||
}
|
||||
|
||||
vec3 calcRay(vec2 uv, float fov, float aspect) {
|
||||
uv = uv * 2.0 - 1.0;
|
||||
float d = 1.0 / tan(radians(fov) * 0.5);
|
||||
return normalize(vec3(aspect * uv.x, uv.y, d));
|
||||
}
|
||||
|
||||
float octahedron(vec3 p, float s) {
|
||||
const float factor = 0.57735027;
|
||||
p = abs(p);
|
||||
return (p.x + p.y + p.z - s) * factor;
|
||||
}
|
||||
|
||||
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
|
||||
p -= artifactOffset;
|
||||
p = artifactRotation * p;
|
||||
float dist = octahedron(p, 0.8);
|
||||
const float glowDist = 4.8;
|
||||
if (dist < glowDist) {
|
||||
float d = dist + rand(dist) * 1.7;
|
||||
glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
||||
}
|
||||
if (dist < currDist) {
|
||||
currDist = dist;
|
||||
id = 1;
|
||||
}
|
||||
}
|
||||
|
||||
float objects(vec3 p, inout vec3 glowColor, inout int objId) {
|
||||
float dist = CAM_FAR;
|
||||
artifact(p, dist, glowColor, objId);
|
||||
return dist;
|
||||
}
|
||||
|
||||
float artifactDist(vec3 p) {
|
||||
p -= artifactOffset;
|
||||
p = artifactRotation * p;
|
||||
return octahedron(p, 1.2);
|
||||
}
|
||||
|
||||
vec3 objectsNormal(vec3 p, float eps) {
|
||||
vec2 h = vec2(eps, 0);
|
||||
return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
|
||||
}
|
||||
|
||||
vec3 objectsColor(int id, vec3 normal, vec3 ray) {
|
||||
return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
|
||||
id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
|
||||
vec3(1.0, 1.0, 0.0);
|
||||
}
|
||||
|
||||
void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
|
||||
float dist = 0.0;
|
||||
int id;
|
||||
vec3 rayPos = eye;
|
||||
float depth = CAM_FAR;
|
||||
for (int i = 0; i < 30; i++) {
|
||||
dist = objects(rayPos, color.rgb, id);
|
||||
depth = distance(rayPos, eye);
|
||||
if (depth > wDepth || dist < 0.01) break;
|
||||
rayPos += ray * dist;
|
||||
}
|
||||
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
|
||||
}
|
||||
|
||||
|
||||
vec3 march(vec2 uv, vec3 camPos) {
|
||||
mat4 vm = viewMatrix(camFwd, camUp);
|
||||
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
||||
vec4 color = vec4(BACKGROUND, CAM_FAR);
|
||||
marchObjects(camPos, ray, color.w, color);
|
||||
return color.rgb;
|
||||
}
|
||||
|
||||
float snow(vec2 uv, float scale) {
|
||||
float w = smoothstep(1.0, 0.0, -uv.y * (scale * 0.01));
|
||||
if (w < 0.1) return 0.0;
|
||||
|
||||
float timeScale = time / scale;
|
||||
uv += vec2(timeScale, timeScale);
|
||||
uv.x += sin(uv.y + time * 0.125) / scale;
|
||||
uv *= scale;
|
||||
|
||||
vec2 s = floor(uv);
|
||||
vec2 f = fract(uv);
|
||||
|
||||
float sinValueX = sin((s.x + scale) * 7.0) * 5.0;
|
||||
float sinValueY = sin((s.y + scale) * 5.0) * 5.0;
|
||||
float combinedSinValue = sinValueX + sinValueY;
|
||||
|
||||
float lengthValue = length(0.5 + 0.5 * sin(11.0 * fract(combinedSinValue)) - f);
|
||||
|
||||
return smoothstep(0.0, min(lengthValue, 3.0), sin(f.x + f.y) * 0.01) * w;
|
||||
}
|
||||
|
||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||
vec2 uv = fragCoord / resolution.xy;
|
||||
float s = sin(time);
|
||||
float c = cos(time);
|
||||
|
||||
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
|
||||
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
|
||||
|
||||
camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
||||
camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
||||
|
||||
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);
|
||||
|
||||
if (layer_snow) {
|
||||
vec2 uvSnow = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
|
||||
fragColor += vec4(vec3(snow(uvSnow, 4.0)), 0.5) * 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
fragColor = vColor;
|
||||
mainImage(fragColor, gl_FragCoord.xy);
|
||||
}
|
||||
11
public/shaders/es3/bg.vert
Normal file
@@ -0,0 +1,11 @@
|
||||
#version 300 es
|
||||
layout(location = 0) in vec3 position;
|
||||
layout(location = 1) in vec4 color;
|
||||
|
||||
uniform mat4 model;
|
||||
out vec4 vColor;
|
||||
|
||||
void main(void) {
|
||||
vColor = color;
|
||||
gl_Position = model * vec4(position, 1.0);
|
||||
}
|
||||
|
Before Width: | Height: | Size: 691 B After Width: | Height: | Size: 691 B |
0
src/lib.rs
Normal file
480
src/main.rs
Normal file
@@ -0,0 +1,480 @@
|
||||
#![feature(iter_intersperse)]
|
||||
extern crate core;
|
||||
|
||||
use js_sys::Float32Array;
|
||||
use log::{info, warn};
|
||||
use std::cell::RefCell;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::JsCast;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext as GL2, window};
|
||||
use yew::prelude::*;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
mod pages;
|
||||
use crate::pages::about::About;
|
||||
use crate::pages::blog::author::Author;
|
||||
use crate::pages::blog::authors::Authors;
|
||||
use crate::pages::blog::entries::Entries;
|
||||
use crate::pages::blog::entry::Entry;
|
||||
use crate::pages::findme::FindMe;
|
||||
use crate::pages::projects::projects::Projects;
|
||||
use pages::not_found::PageNotFound;
|
||||
|
||||
#[derive(Routable, PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Route {
|
||||
#[at("/")]
|
||||
About,
|
||||
|
||||
#[at("/blog/entries/:id")]
|
||||
Entry { id: u8 },
|
||||
#[at("/blog/entries")]
|
||||
Entries,
|
||||
#[at("/blog/authors/:id")]
|
||||
Author { id: u8 },
|
||||
#[at("/blog/authors")]
|
||||
Authors,
|
||||
|
||||
#[at("/projects")]
|
||||
Projects,
|
||||
|
||||
#[at("/findme")]
|
||||
FindMe,
|
||||
|
||||
#[not_found]
|
||||
#[at("/404")]
|
||||
NotFound,
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
node_ref: NodeRef,
|
||||
}
|
||||
|
||||
impl Component for App {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
node_ref: NodeRef::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<canvas width="100%" height="100%" style="
|
||||
background-color: black !important;
|
||||
position: fixed !important;
|
||||
left: 0 !important;
|
||||
top: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
z-index: 0 !important;
|
||||
" ref={self.node_ref.clone()} />
|
||||
<BrowserRouter>
|
||||
<header style="
|
||||
z-index: 2 !important;
|
||||
display: flex !important;
|
||||
color: white !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
max-width: 100vw !important;
|
||||
min-height: 20px !important;
|
||||
height: 20px !important;
|
||||
max-height: 20px !important;
|
||||
filter: url(post.bloom.svg) !important;
|
||||
">
|
||||
<h3>{r"»[ "}</h3>
|
||||
<h3><Link<Route> to={Route::About}>{r"about"}</Link<Route>></h3>
|
||||
<h3>{r"|"}</h3>
|
||||
<h3><Link<Route> to={Route::Entries}>{r"blog"}</Link<Route>></h3>
|
||||
<h3>{r"|"}</h3>
|
||||
<h3><Link<Route> to={Route::FindMe}>{r"findme"}</Link<Route>></h3>
|
||||
<h3>{r"|"}</h3>
|
||||
<h3><Link<Route> to={Route::Projects}>{r"projects"}</Link<Route>></h3>
|
||||
<h3>{r" ]«"}</h3>
|
||||
</header>
|
||||
<main style="
|
||||
z-index: 1 !important;
|
||||
background-color: transparent !important;
|
||||
color: white !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
min-width: fit-content !important;
|
||||
width: calc(100vw - 18 px) !important;
|
||||
flex-wrap: nowrap !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
align-self: center !important;
|
||||
margin-left: auto !important;
|
||||
margin-right: auto !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
height: fit-content !important;
|
||||
max-height: calc(100vh - 42px) !important;
|
||||
">
|
||||
/*
|
||||
min-height: 70vh !important;
|
||||
height: 80vw !important;
|
||||
*/
|
||||
<div style="
|
||||
background-color: rgba(0, 0, 0, 0.3125) !important;
|
||||
backdrop-filter: blur(6px) !important;
|
||||
padding: 4px 0px 0px 4px !important;
|
||||
border-radius: 1.0rem !important;
|
||||
border-style: solid !important;
|
||||
border-color: black !important;
|
||||
border-width: 1px !important;
|
||||
z-index: 1 !important;
|
||||
overflow-x: hidden !important;
|
||||
overflow-y: auto !important;
|
||||
min-width: 70vw !important;
|
||||
width: 70vh !important;
|
||||
max-width: calc(100vw - 18px) !important;
|
||||
height: 100% !important;
|
||||
margin-top: auto !important;
|
||||
margin-bottom: auto !important;
|
||||
scrollbar-width: thin !important;
|
||||
scrollbar-color: transparent transparent !important;
|
||||
"><Switch<Route> render={switch} />
|
||||
{self.view_buttons()}
|
||||
</div>
|
||||
</main>
|
||||
</BrowserRouter>
|
||||
</>
|
||||
}
|
||||
}
|
||||
|
||||
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
||||
if first_render {
|
||||
self.view_canvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn switch(routes: Route) -> Html {
|
||||
match routes {
|
||||
Route::About => {
|
||||
html! { <About /> }
|
||||
}
|
||||
|
||||
Route::Entries => {
|
||||
html! { <Entries /> }
|
||||
}
|
||||
Route::Entry { id } => {
|
||||
html! { <Entry id={id as u8} /> }
|
||||
}
|
||||
Route::Authors => {
|
||||
html! { <Authors /> }
|
||||
}
|
||||
Route::Author { id } => {
|
||||
html! { <Author id={id as u8} /> }
|
||||
}
|
||||
|
||||
Route::FindMe => {
|
||||
html! { <FindMe /> }
|
||||
}
|
||||
|
||||
Route::Projects => {
|
||||
html! { <Projects /> }
|
||||
}
|
||||
Route::NotFound => {
|
||||
html! { <PageNotFound /> }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn view_buttons(&self) -> Html {
|
||||
html! {
|
||||
<div style="
|
||||
z-index: 1 !important;
|
||||
display: flex !important;
|
||||
flex-wrap: wrap !important;
|
||||
flex-direction: row !important;
|
||||
justify-content: center !important;
|
||||
min-width: 70vw !important;
|
||||
width: 70vh !important;
|
||||
max-width: calc(100vw - 18px) !important;
|
||||
height: fit-content !important;
|
||||
overflow: hidden !important;
|
||||
margin-right: auto !important;
|
||||
margin-left: auto !important;
|
||||
">
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://i0ur.ing/"><img loading="eager" alt="iouring" src="iouring.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://webassembly.org/"><img loading="eager" alt="wasm" src="wasm.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" src="servfail.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" src="dataforest.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" src="fedora.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://artixlinux.org/"><img loading="eager" alt="artix" src="artix.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://nixos.org/"><img loading="eager" alt="nixos" src="nixos.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://chimera-linux.org/"><img loading="eager" alt="void" src="chimera.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://voidlinux.org/"><img loading="eager" alt="void" src="void.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" src="rust.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" src="csharp.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" src="kotlin.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
margin-right: 0.2rem !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
" href="https://www.java.com/"><img loading="eager" alt="java" src="java.png" /></a>
|
||||
<a style="
|
||||
width: 88px !important;
|
||||
height: 31px !important;
|
||||
image-rendering: pixelated !important;
|
||||
image-resolution: from-image !important;
|
||||
image-orientation: from-image !important;
|
||||
"><img loading="eager" alt="aqueer" src="aqueer.png" /></a>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
fn view_canvas(&self) {
|
||||
let canvas = self.node_ref.cast::<HtmlCanvasElement>().unwrap();
|
||||
|
||||
let gl = canvas
|
||||
.get_context("webgl2")
|
||||
.unwrap_or(canvas.get_context("webgl").unwrap())
|
||||
.unwrap()
|
||||
.dyn_into::<GL2>()
|
||||
.unwrap();
|
||||
|
||||
let vertex_buffer = gl.create_buffer().unwrap();
|
||||
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
GL2::ARRAY_BUFFER,
|
||||
&Float32Array::from(
|
||||
vec![
|
||||
-1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0,
|
||||
]
|
||||
.as_slice(),
|
||||
),
|
||||
GL2::STATIC_DRAW,
|
||||
);
|
||||
|
||||
let color_buffer = gl.create_buffer().unwrap();
|
||||
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
GL2::ARRAY_BUFFER,
|
||||
&Float32Array::from(
|
||||
vec![
|
||||
1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0,
|
||||
]
|
||||
.as_slice(),
|
||||
),
|
||||
GL2::STATIC_DRAW,
|
||||
);
|
||||
|
||||
let element_buffer = gl.create_buffer().unwrap();
|
||||
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||
let element_indices = vec![3, 2, 1, 3, 1, 0];
|
||||
gl.buffer_data_with_array_buffer_view(
|
||||
GL2::ELEMENT_ARRAY_BUFFER,
|
||||
&js_sys::Uint16Array::from(element_indices.as_slice()),
|
||||
GL2::STATIC_DRAW,
|
||||
);
|
||||
|
||||
let vertex_shader_code = include_str!("../public/shaders/es3/bg.vert");
|
||||
// info!("{}", vertex_shader_code);
|
||||
let vertex_shader = gl.create_shader(GL2::VERTEX_SHADER).unwrap();
|
||||
gl.shader_source(&vertex_shader, vertex_shader_code);
|
||||
gl.compile_shader(&vertex_shader);
|
||||
let vertex_shader_log = gl.get_shader_info_log(&vertex_shader);
|
||||
if !vertex_shader_log.as_ref().unwrap().is_empty() {
|
||||
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||
}
|
||||
|
||||
let fragment_shader_code = include_str!("../public/shaders/es3/bg.frag");
|
||||
let fragment_shader = gl.create_shader(GL2::FRAGMENT_SHADER).unwrap();
|
||||
gl.shader_source(&fragment_shader, fragment_shader_code);
|
||||
gl.compile_shader(&fragment_shader);
|
||||
let fragment_shader_log = gl.get_shader_info_log(&fragment_shader);
|
||||
if !fragment_shader_log.as_ref().unwrap().is_empty() {
|
||||
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||
}
|
||||
|
||||
let shader_program = gl.create_program().unwrap();
|
||||
|
||||
gl.attach_shader(&shader_program, &vertex_shader);
|
||||
gl.attach_shader(&shader_program, &fragment_shader);
|
||||
|
||||
gl.link_program(&shader_program);
|
||||
if !gl
|
||||
.get_program_parameter(&shader_program, GL2::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap()
|
||||
{
|
||||
warn!("shader couldn't be linked!");
|
||||
}
|
||||
gl.use_program(Some(&shader_program));
|
||||
|
||||
let position = gl.get_attrib_location(&shader_program, "position") as u32;
|
||||
gl.enable_vertex_attrib_array(position);
|
||||
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||
gl.vertex_attrib_pointer_with_i32(position, 3, GL2::FLOAT, false, 0, 0);
|
||||
|
||||
let color = gl.get_attrib_location(&shader_program, "color") as u32;
|
||||
gl.enable_vertex_attrib_array(color);
|
||||
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||
gl.vertex_attrib_pointer_with_i32(color, 4, GL2::FLOAT, false, 0, 0);
|
||||
|
||||
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||
|
||||
gl.enable(GL2::DEPTH_TEST);
|
||||
gl.clear_color(0.0, 0.0, 0.0, 1.0);
|
||||
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||
|
||||
let default_matrix = vec![
|
||||
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||
];
|
||||
let unim_matrix = gl.get_uniform_location(&shader_program, "model");
|
||||
let unim_time = gl.get_uniform_location(&shader_program, "time");
|
||||
let unim_res = gl.get_uniform_location(&shader_program, "resolution");
|
||||
let unim_overlay_time = gl.get_uniform_location(&shader_program, "layer_snow");
|
||||
|
||||
let mut timestamp = 0.0;
|
||||
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||
gl.draw_elements_with_i32(
|
||||
GL2::TRIANGLES,
|
||||
element_indices.len() as i32,
|
||||
GL2::UNSIGNED_SHORT,
|
||||
0,
|
||||
);
|
||||
|
||||
fn request_animation_frame(f: &Closure<dyn FnMut()>) {
|
||||
window()
|
||||
.unwrap()
|
||||
.request_animation_frame(f.as_ref().unchecked_ref())
|
||||
.expect("should register `requestAnimationFrame` OK");
|
||||
}
|
||||
|
||||
let cb = Arc::new(RefCell::new(None));
|
||||
*cb.borrow_mut() = Some(Closure::wrap(Box::new({
|
||||
let cb = cb.clone();
|
||||
move || {
|
||||
timestamp += 20.0;
|
||||
|
||||
canvas.set_width(window().unwrap().inner_width().unwrap().as_f64().unwrap() as u32);
|
||||
canvas.set_height(window().unwrap().inner_height().unwrap().as_f64().unwrap() as u32);
|
||||
|
||||
gl.uniform_matrix4fv_with_f32_array(unim_matrix.as_ref(), false, default_matrix.as_slice());
|
||||
|
||||
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||
gl.uniform2fv_with_f32_array(
|
||||
unim_res.as_ref(),
|
||||
vec![canvas.width() as f32, canvas.height() as f32].as_slice(),
|
||||
);
|
||||
|
||||
gl.uniform1i(unim_overlay_time.as_ref(), 0); // TODO: ... | add is xmas check
|
||||
|
||||
gl.viewport(0, 0, canvas.width() as i32, canvas.height() as i32);
|
||||
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||
gl.draw_elements_with_i32(
|
||||
GL2::TRIANGLES,
|
||||
element_indices.len() as i32,
|
||||
GL2::UNSIGNED_SHORT,
|
||||
0,
|
||||
);
|
||||
|
||||
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||
}
|
||||
}) as Box<dyn FnMut()>));
|
||||
|
||||
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||
yew::Renderer::<App>::new().render();
|
||||
}
|
||||
95
src/pages/about.rs
Normal file
@@ -0,0 +1,95 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct About;
|
||||
impl Component for About {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<>
|
||||
<div>
|
||||
<img alt="insert pfp here" style="
|
||||
float: left !important;
|
||||
margin-right: 10px !important;
|
||||
border-radius: 0.5rem !important;
|
||||
" loading="eager" width="96px" height="96px" src="profile.avif" />
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
max-height: 96px !important;
|
||||
">
|
||||
<p style="
|
||||
display: flex !important;
|
||||
color: transparent !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
width: fit-content !important;
|
||||
background-clip: text !important;
|
||||
background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;
|
||||
-webkit-background-clip: text !important;
|
||||
margin: unset !important;
|
||||
filter: url(post.bloom.svg#process) !important;
|
||||
">
|
||||
<img loading="eager" alt="ket" src="oneko.gif" />{r" lia · they/she · transfemby"}</p>
|
||||
<p style="margin: unset !important;">
|
||||
<p style="margin: unset !important;">{r"certified catgirl™"}</p>
|
||||
<p style="margin: unset !important;">{r"software engineer"}</p>
|
||||
<p style="margin: unset !important;">{r"professional yapper"}</p>
|
||||
<p style="margin: unset !important;">{r"neurospicy and disabled"}</p>
|
||||
</p>
|
||||
</div>
|
||||
<p style="
|
||||
font-weight: bold !important;
|
||||
font-style: italic !important;
|
||||
font-size: xx-large !important;
|
||||
">{r">intro_"}<br /><p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: medium !important;
|
||||
">{r#"
|
||||
name's lia or lucy. we are creating.
|
||||
trying to provide a safe space in the internet.
|
||||
our goal's to make this world a better ~ less trashy ~ place.
|
||||
may sound naive as fuck but fuck it we ball.
|
||||
audhd (certified) and probably bpd or ptsd. i'm not a psychologist tho.
|
||||
interact with caution, may bite.
|
||||
political view's prolly very obvious. should be enough to mention i'm social.
|
||||
figure it yourself. use your brain.
|
||||
"#}</p>{r">hobbies_"}<br /><p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: medium !important;
|
||||
">{r#"
|
||||
watching anime, designing something kewl, playing games, starting a new project or just yapping with friends, nearly everything's included.
|
||||
i especially enjoy cooking or even baking if enough spoons are to avail.
|
||||
which anime? has to be very specific, i'm really picky and judgy regarding them.
|
||||
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
|
||||
has to be something i can somewhat imagine in my fucky-wucky head.
|
||||
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
|
||||
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
|
||||
projects? you can find public ones in my projects tab.
|
||||
"#}/*</p>{r"hobbies"}<br /><p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: medium !important;
|
||||
">{r#"
|
||||
watching anime, designing something kewl, playing games, starting a new project or just yapping/meeting up with friends, nearly everything's included.
|
||||
i especially enjoy cooking or even baking if enough spoons are to avail.
|
||||
which anime? has to be very specific, i'm really picky and judgy regarding them.
|
||||
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
|
||||
has to be something i can somewhat imagine in my fucky-wucky head.
|
||||
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
|
||||
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
|
||||
projects? you can find public ones in my projects tab.
|
||||
"#}*/</p>
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
}
|
||||
}
|
||||
71
src/pages/blog/author.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use crate::pages::blog::content;
|
||||
use crate::pages::blog::content::{BlogEntry, Post};
|
||||
use yew::prelude::*;
|
||||
use crate::pages::blog::authorcard::AuthorCard;
|
||||
use crate::pages::blog::entrycard::EntryCard;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
pub struct Author {
|
||||
author: content::Author,
|
||||
}
|
||||
impl Component for Author {
|
||||
type Message = ();
|
||||
type Properties = Props;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
author: content::Author::from_id(ctx.props().id),
|
||||
}
|
||||
}
|
||||
|
||||
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||
self.author = content::Author::from_id(ctx.props().id);
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { author } = self;
|
||||
let id = author.id;
|
||||
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
|
||||
.filter(|&id_offset| {
|
||||
Post::from_id(id_offset as u8)
|
||||
.authors
|
||||
.iter()
|
||||
.any(|id1| *id1 == id)
|
||||
})
|
||||
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
|
||||
.collect();
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
margin-bottom: 2rem !important;
|
||||
">
|
||||
<h2 style="text-align: center !important;">{ "about me" }</h2>
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
margin-bottom: 1rem !important;
|
||||
"><AuthorCard {id} /></div>
|
||||
<h2 style="text-align: center !important;">{ "my posts" }</h2>
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
">{ for cards }</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
72
src/pages/blog/authorcard.rs
Normal file
@@ -0,0 +1,72 @@
|
||||
use yew::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::pages::blog::content::Author;
|
||||
use crate::pages::blog::content::BlogEntry;
|
||||
|
||||
use crate::Route;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsAuthorCard {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
pub struct AuthorCard {
|
||||
author: Author,
|
||||
}
|
||||
|
||||
impl Component for AuthorCard {
|
||||
type Message = ();
|
||||
type Properties = PropsAuthorCard;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
author: Author::from_id(ctx.props().id),
|
||||
}
|
||||
}
|
||||
|
||||
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||
self.author = Author::from_id(ctx.props().id);
|
||||
true
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { author } = self;
|
||||
let keywords = author.keywords.iter().map(|keyword| html! {
|
||||
<p style="margin: unset !important;">{*keyword}</p>
|
||||
}).collect::<Html>();
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
background-color: #ECBEE130 !important;
|
||||
border-radius: 0.5rem !important;
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
text-align: left !important;
|
||||
margin: 1rem 1rem 0 0 !important;
|
||||
">
|
||||
<img style="
|
||||
float: left !important;
|
||||
margin-right: 0.3rem !important;
|
||||
border-radius: 0.5rem !important;
|
||||
" alt="insert pfp here" width="96" height="96" src={author.image_url} />
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: wrap !important;
|
||||
margin-top: 0.3rem !important;
|
||||
margin-right: 0.3rem !important;
|
||||
">
|
||||
<h3 style="margin: unset !important;">
|
||||
<Link<Route> to={Route::Author { id: author.id }}>
|
||||
{ &*author.name }
|
||||
</Link<Route>>
|
||||
</h3>
|
||||
{ keywords }
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
31
src/pages/blog/authors.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::pages::blog::authorcard::AuthorCard;
|
||||
use crate::pages::blog::content::{Author, BlogEntry};
|
||||
|
||||
pub struct Authors;
|
||||
impl Component for Authors {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
margin-top: 2rem !important;
|
||||
margin-bottom: 2rem !important;
|
||||
">{ for Author::AUTHORS.map(|author| {
|
||||
let id = author.id;
|
||||
html! { <AuthorCard {id} /> }
|
||||
}) }</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
299
src/pages/blog/content.rs
Normal file
@@ -0,0 +1,299 @@
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Author {
|
||||
pub id: u8,
|
||||
pub image_url: &'static str,
|
||||
pub name: &'static str,
|
||||
pub keywords: &'static [&'static str],
|
||||
pub about: &'static str,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Post {
|
||||
pub id: u8,
|
||||
pub authors: &'static [u8],
|
||||
pub title: &'static str,
|
||||
pub utcdate: &'static str,
|
||||
pub content: &'static str,
|
||||
}
|
||||
|
||||
impl BlogEntry for Author {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
*Self::AUTHORS
|
||||
.get(entry.id as usize)
|
||||
.cloned()
|
||||
.unwrap_or(&Self {
|
||||
id: u8::MAX,
|
||||
image_url: "",
|
||||
name: "not found",
|
||||
keywords: [].as_slice(),
|
||||
about: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl BlogEntry for Post {
|
||||
fn from_entry(entry: &mut Entry) -> Self {
|
||||
*Self::POSTS
|
||||
.get(entry.id as usize)
|
||||
.cloned()
|
||||
.unwrap_or(&Self {
|
||||
id: u8::MAX,
|
||||
authors: &[u8::MAX],
|
||||
title: "not found",
|
||||
utcdate: "1970-01-01",
|
||||
content: "",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Entry {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn from_id(seed: u8) -> Self {
|
||||
Self { id: seed }
|
||||
}
|
||||
}
|
||||
pub trait BlogEntry: Sized {
|
||||
fn from_entry(entry: &mut Entry) -> Self;
|
||||
fn from_id(id: u8) -> Self {
|
||||
Self::from_entry(&mut Entry::from_id(id))
|
||||
}
|
||||
|
||||
const AUTHORS: [&'static Author; 2] = [
|
||||
&Author {
|
||||
id: 0,
|
||||
image_url: "profile.avif",
|
||||
name: "lia",
|
||||
keywords: &[
|
||||
"certified catgirl™",
|
||||
"software engineer",
|
||||
"professional yapper",
|
||||
"neurospicy and disabled",
|
||||
],
|
||||
about: "",
|
||||
},
|
||||
&Author {
|
||||
id: 1,
|
||||
image_url: "",
|
||||
name: "mreowww",
|
||||
keywords: &["meow", "mrrp", "mew"],
|
||||
about: "",
|
||||
},
|
||||
];
|
||||
|
||||
const POSTS: [&'static Post; 2] = [
|
||||
&Post {
|
||||
id: 0,
|
||||
authors: &[0],
|
||||
title: "haj, world!",
|
||||
utcdate: "2025-08-24",
|
||||
content: r#"
|
||||
### my first blogpost
|
||||
haj! and welcome to my first blogpost.
|
||||
this blogpost won't have that much content.
|
||||
fun fact: this post becomes html from markdown :3
|
||||
pretty cool, ain't it? we think so too.
|
||||
it's for including images, formatted text, code and content much easier.
|
||||
i also added a too overcomplicated blog backend that allows authors and posts by u8::MAX, cause why not.
|
||||
won't post that much either way, so why bother with higher values.
|
||||
nonetheless... have a great day, evening, whatever daytime it's for you rn and stay safe. ♡
|
||||
you may take a look at my other blogposts. this one was basically practially just a test.
|
||||
"#,
|
||||
},
|
||||
&Post {
|
||||
id: 1,
|
||||
authors: &[0],
|
||||
title: "sweet little poison",
|
||||
utcdate: "2025-08-25",
|
||||
content: r#"
|
||||
### > setting up iocaine with docker and caddy. ~
|
||||
sup, today we're going to give ya a crash course on how to homebrew poison for ai. ^.^
|
||||
we'll be utilizing docker for containerization and caddy as a reverse-proxy.
|
||||
|
||||
#### > preparing docker and files ~
|
||||
first, we're going to create a new external network and recreate our file struct.
|
||||
in this example we're going to name it "caddy". very creative, i know.
|
||||
we achieve this by running following pile of commands:
|
||||
```sh
|
||||
docker network create caddy && \
|
||||
mkdir -p ./caddy/pages/example.com ./iocaine ./socks && \
|
||||
touch ./docker-compose.yml ./caddy/proxy ./iocaine/config.toml && \
|
||||
touch ./caddy/pages/example.com/index.html && \
|
||||
printf '<html><body><h1>hellow body consisting of blood and flesh</h1></body></html>' \
|
||||
> ./caddy/pages/example.com/index.html
|
||||
```
|
||||
|
||||
after running that we should be set and ready for the next few steps.
|
||||
if you're curious, this is how it should look like right now:
|
||||
```sh
|
||||
Documents/example » tree .
|
||||
.
|
||||
├── caddy
|
||||
│ ├── pages
|
||||
│ │ └── example.com
|
||||
│ │ └── index.html
|
||||
│ └── proxy
|
||||
├── docker-compose.yml
|
||||
├── iocaine
|
||||
│ └── config.toml
|
||||
└── socks
|
||||
```
|
||||
|
||||
#### > downloading data and configuring iocaine ~
|
||||
|
||||
next things next: we're going to download a robots.json, some markov-chain-stuffies and a words.txt.
|
||||
for now, we will be using the default stuff from the official docs cause why not:
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.3 \
|
||||
-L https://archive.org/download/GeorgeOrwells1984/1984_djvu.txt \
|
||||
-o ./iocaine/1984.txt && \
|
||||
curl --proto '=https' --tlsv1.3 \
|
||||
-L https://archive.org/download/ost-english-brave_new_world_aldous_huxley/Brave_New_World_Aldous_Huxley_djvu.txt \
|
||||
-o ./iocaine/brave-new-world.txt && \
|
||||
curl --proto '=https' --tlsv1.2 \
|
||||
-L https://git.savannah.gnu.org/cgit/miscfiles.git/plain/web2 \
|
||||
-o ./iocaine/words.txt && \
|
||||
curl --proto '=https' --tlsv1.3 \
|
||||
-L https://github.com/ai-robots-txt/ai.robots.txt/raw/refs/heads/main/robots.json \
|
||||
-o ./iocaine/robots.json
|
||||
```
|
||||
|
||||
most importantly, we have to scrape nam-shub-of-enki from gergely's git.
|
||||
here's a way to curl and extract it automatically in the correct folder:
|
||||
```sh
|
||||
curl --proto '=https' --tlsv1.3 \
|
||||
-L https://git.madhouse-project.org/api/packages/iocaine/generic/nam-shub-of-enki/20250711.0/nam-shub-of-enki-20250711.0.tar.zst \
|
||||
-o ./iocaine/nam-shub-of-enki.tar.zst && \
|
||||
sudo tar -xvf ./iocaine/nam-shub-of-enki.tar.zst -C ./iocaine
|
||||
```
|
||||
|
||||
now, we're going to prepare our iocaine/config.toml like this:
|
||||
```sh
|
||||
cat > ./iocaine/config.toml <<'YAML'
|
||||
[server]
|
||||
bind = "/run/iocaine/waow.socket"
|
||||
unix_listen_access = "everybody"
|
||||
|
||||
[server.control]
|
||||
bind = "/run/iocaine/listen.socket"
|
||||
unix_listen_access = "owner"
|
||||
|
||||
[server.request-handler]
|
||||
path = "/data"
|
||||
language = "roto"
|
||||
|
||||
[sources]
|
||||
words = "/data/words.txt"
|
||||
markov = [ "/data/1984.txt", "/data/brave-new-world.txt" ]
|
||||
|
||||
[metrics]
|
||||
enable = false
|
||||
YAML
|
||||
```
|
||||
|
||||
#### > cooking up our docker compose file for deployment ~
|
||||
|
||||
i have fucked around with docker a little while to make this somehow work.
|
||||
here's a command which will insert needed content into our docker-compose.yml:
|
||||
```sh
|
||||
cat > ./docker-compose.yml <<'YAML'
|
||||
services:
|
||||
caddy:
|
||||
image: caddy:alpine
|
||||
container_name: proxy.caddy
|
||||
hostname: proxy.caddy
|
||||
restart: unless-stopped
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "443:443/udp"
|
||||
networks:
|
||||
- caddy
|
||||
volumes:
|
||||
- ./socks:/run/iocaine:ro
|
||||
- ./caddy/pages:/var/www/html
|
||||
- ./caddy/data:/data
|
||||
- ./caddy/config:/config
|
||||
- ./caddy/proxy:/etc/caddy/Caddyfile:ro
|
||||
depends_on:
|
||||
- iocaine
|
||||
iocaine:
|
||||
image: git.madhouse-project.org/iocaine/iocaine:2
|
||||
container_name: proxy.iocaine
|
||||
hostname: proxy.iocaine
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "127.0.0.1:42069:42069"
|
||||
networks:
|
||||
- caddy
|
||||
volumes:
|
||||
- ./socks:/run/iocaine:rw
|
||||
- ./iocaine:/data
|
||||
environment:
|
||||
- IOCAINE__SERVER__BIND="/run/iocaine/waow.socket"
|
||||
- IOCAINE__SERVER__UNIX_LISTEN_ACCESS="everybody"
|
||||
- IOCAINE__SERVER__REQUEST_HANDLER__PATH="/data"
|
||||
- IOCAINE__SERVER__REQUEST_HANDLER__LANGUAGE="roto"
|
||||
- IOCAINE__SERVER__CONTROL__BIND="/run/iocaine/listen.socket"
|
||||
- IOCAINE__SERVER__CONTROL__UNIX_LISTEN_ACCESS="owner"
|
||||
- IOCAINE__SOURCES__WORDS="/data/words.txt"
|
||||
- IOCAINE__SOURCES__MARKOV=["/data/1984.txt", "/data/brave-new-world.txt"]
|
||||
- IOCAINE__METRICS__ENABLE=false
|
||||
- NSOE__AI_ROBOTS_TXT_PATH=/data/robots.json
|
||||
command: --config-file /data/config.toml start
|
||||
networks:
|
||||
caddy:
|
||||
external: true
|
||||
YAML
|
||||
```
|
||||
|
||||
#### > finishing up with caddy and deploying ~
|
||||
our caddyfile needs some love to work.
|
||||
here's what i ripped from the official docs:
|
||||
```sh
|
||||
cat > ./iocaine/proxy <<'TXT'
|
||||
(iocaine) {
|
||||
@read method GET HEAD
|
||||
@not-read not {
|
||||
method GET HEAD
|
||||
}
|
||||
reverse_proxy @read unix//run/iocaine/waow.socket {
|
||||
#reverse_proxy @read proxy.iocaine:42069 {
|
||||
@fallback status 421
|
||||
handle_response @fallback {
|
||||
{blocks.handler}
|
||||
}
|
||||
}
|
||||
handle @not-read {
|
||||
{blocks.default}
|
||||
}
|
||||
}
|
||||
|
||||
example.com {
|
||||
import iocaine {
|
||||
handler {
|
||||
reverse_proxy http://example:8080
|
||||
}
|
||||
default {
|
||||
# this is the behaviour if neither a GET nor HEAD request comes in
|
||||
reverse_proxy http://example:8080
|
||||
}
|
||||
}
|
||||
}
|
||||
TXT
|
||||
```
|
||||
now the last thing that's left is a plain and simple:
|
||||
`docker compose up -d`
|
||||
and we should be good to go!
|
||||
|
||||
i hope this post made it easier/helped you with setting up iocaine on your own server. ♡
|
||||
it's good to have this set up just to fuck around with fucking parisitic generative ai stealing your things.
|
||||
gatekeep your stuff from big corpos that are trying to make their business from your experience and skills.
|
||||
"#,
|
||||
},
|
||||
];
|
||||
}
|
||||
55
src/pages/blog/entries.rs
Normal file
@@ -0,0 +1,55 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::pages::blog::content::BlogEntry;
|
||||
use crate::pages::blog::content::Post;
|
||||
use crate::pages::blog::entrycard::EntryCard;
|
||||
|
||||
pub struct Entries {}
|
||||
|
||||
impl Component for Entries {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self {}
|
||||
}
|
||||
|
||||
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div>
|
||||
<h1 style="margin: unset !important;">{ "entries" }</h1>
|
||||
<h2 style="
|
||||
margin: unset !important;
|
||||
margin-bottom: 1rem !important;
|
||||
">{ "here are some things i yapped about" }</h2>
|
||||
{ self.view_posts(ctx) }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
impl Entries {
|
||||
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
|
||||
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
|
||||
.filter(|&id_offset| {
|
||||
Post::from_id(id_offset as u8)
|
||||
.authors
|
||||
.iter()
|
||||
.all(|id| *id != u8::MAX)
|
||||
})
|
||||
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
|
||||
.collect();
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
margin: unset !important;
|
||||
margin-bottom: 1rem !important;
|
||||
width: 100% !important;
|
||||
">
|
||||
{ for cards }
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
77
src/pages/blog/entry.rs
Normal file
@@ -0,0 +1,77 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use yew::prelude::*;
|
||||
use yew_markdown::Markdown;
|
||||
use yew_router::prelude::*;
|
||||
|
||||
use crate::pages::blog::content;
|
||||
use crate::pages::blog::content::{Author, BlogEntry};
|
||||
use crate::Route;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||
pub struct Props {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct PostState {
|
||||
pub inner: content::Post,
|
||||
}
|
||||
|
||||
impl Reducible for PostState {
|
||||
type Action = u8;
|
||||
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||
Self {
|
||||
inner: content::Post::from_id(action.into()),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn Entry(props: &Props) -> Html {
|
||||
let id = props.id;
|
||||
|
||||
let post = use_reducer(|| PostState {
|
||||
inner: content::Post::from_id(id.into()),
|
||||
});
|
||||
|
||||
{
|
||||
let post_dispatcher = post.dispatcher();
|
||||
use_effect_with(id, move |id| {
|
||||
post_dispatcher.dispatch(*id);
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
let post = &post.inner;
|
||||
|
||||
html! {
|
||||
<>
|
||||
<h1 style="
|
||||
color: #F5C2E7FF !important;
|
||||
margin: unset !important;
|
||||
filter: url(post.bloom.svg#process) !important;
|
||||
">{ &*post.title }</h1>
|
||||
<div style="flex-direction: column !important;">
|
||||
<h2 style="margin: unset !important;">
|
||||
{ "written by " } {
|
||||
post.authors.iter().map(|id| {
|
||||
let author = Author::from_id(*id);
|
||||
html! {
|
||||
<Link<Route> to={crate::Route::Author { id: author.id }}>
|
||||
{ &*author.name }
|
||||
</Link<Route>>
|
||||
}
|
||||
}).intersperse(html! { ", " }).collect::<Html>()
|
||||
} { format!(" on {}", &post.utcdate) }
|
||||
</h2>
|
||||
</div>
|
||||
<Markdown
|
||||
src={post.content}
|
||||
hard_line_breaks={true}
|
||||
send_debug_info={Callback::noop()}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
}
|
||||
98
src/pages/blog/entrycard.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use std::rc::Rc;
|
||||
use yew::prelude::*;
|
||||
use yew_router::components::Link;
|
||||
|
||||
use crate::pages::blog::content::{Author, BlogEntry, Post};
|
||||
|
||||
use crate::Route;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsEntryCard {
|
||||
pub id: u8,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Debug)]
|
||||
pub struct PostState {
|
||||
inner: Post,
|
||||
}
|
||||
|
||||
impl Reducible for PostState {
|
||||
type Action = u8;
|
||||
|
||||
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||
Self {
|
||||
inner: Post::from_id(action.into()),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
#[function_component]
|
||||
pub fn EntryCard(props: &PropsEntryCard) -> Html {
|
||||
let id = props.id;
|
||||
|
||||
let post = use_reducer_eq(|| PostState {
|
||||
inner: Post::from_id(id.into()),
|
||||
});
|
||||
|
||||
{
|
||||
let post_dispatcher = post.dispatcher();
|
||||
use_effect_with(id, move |id| {
|
||||
post_dispatcher.dispatch(*id);
|
||||
|
||||
|| {}
|
||||
});
|
||||
}
|
||||
|
||||
let post = &post.inner;
|
||||
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: wrap !important;
|
||||
background-color: #ECBEE130 !important;
|
||||
border-radius: 0.5rem !important;
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
text-align: left !important;
|
||||
margin: 1rem 1rem 0 0 !important;
|
||||
">
|
||||
<Link<Route> to={Route::Entry { id: post.id }}>
|
||||
<h2 style="
|
||||
margin: unset !important;
|
||||
margin-top: 0.3rem !important;
|
||||
margin-left: 0.3rem !important;
|
||||
margin-right: 0.3rem !important;
|
||||
">{ &*post.title }</h2>
|
||||
</Link<Route>>
|
||||
<h3 style="
|
||||
margin: unset !important;
|
||||
margin-left: 0.3rem !important;
|
||||
margin-right: 0.3rem !important;
|
||||
">
|
||||
{ format!("published {}", post.utcdate) }
|
||||
</h3>
|
||||
<div style="flex-direction: column !important;">
|
||||
<h3 style="
|
||||
margin: unset !important;
|
||||
margin-left: 0.3rem !important;
|
||||
margin-right: 0.3rem !important;
|
||||
margin-bottom: 0.3rem !important;
|
||||
">
|
||||
{ "written by " }
|
||||
{
|
||||
post.authors.iter().map(|id| {
|
||||
let author = Author::from_id(*id);
|
||||
html! {
|
||||
<Link<Route> to={crate::Route::Author { id: author.id }}>
|
||||
{ &*author.name }
|
||||
</Link<Route>>
|
||||
}
|
||||
}).intersperse(html! { ", " }).collect::<Html>()
|
||||
}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
7
src/pages/blog/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
||||
pub mod author;
|
||||
pub mod authorcard;
|
||||
pub mod authors;
|
||||
pub mod content;
|
||||
pub mod entries;
|
||||
pub mod entry;
|
||||
pub mod entrycard;
|
||||
138
src/pages/findme.rs
Normal file
@@ -0,0 +1,138 @@
|
||||
use web_sys::window;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct FindMe;
|
||||
impl Component for FindMe {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let clipboard = window().expect("meow").navigator().clipboard();
|
||||
|
||||
let handle_copy_matrix = {
|
||||
let clipboard = clipboard.clone();
|
||||
Callback::from(move |_| {
|
||||
let _ = clipboard.write_text(&*"@iouring:hi.stellaris.fyi".to_string());
|
||||
})
|
||||
};
|
||||
|
||||
let handle_copy_telegram = {
|
||||
let clipboard = clipboard.clone();
|
||||
Callback::from(move |_| {
|
||||
let _ = clipboard.write_text(&*"@luc1ell3".to_string());
|
||||
})
|
||||
};
|
||||
|
||||
let handle_copy_discord = {
|
||||
let clipboard = clipboard.clone();
|
||||
Callback::from(move |_| {
|
||||
let _ = clipboard.write_text(&*"@donotusedisc0rdkthxbye".to_string());
|
||||
})
|
||||
};
|
||||
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: row !mportant;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
width: 100% !important;
|
||||
">
|
||||
<div style="
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
margin: 1rem 1rem 1rem 1rem !important;
|
||||
">
|
||||
<p style="
|
||||
font-weight: bold !important;
|
||||
font-style: normal !important;
|
||||
font-size: xx-large !important;
|
||||
text-align: center !important;
|
||||
margin: unset !important;
|
||||
">{r"follow us"}</p>
|
||||
<p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: large !important;
|
||||
">{" × "}<a style="
|
||||
color: #F5C2E7FF !important;
|
||||
" href="https://ice.stellaris.fyi/@iouring">{r"fediverse"}</a><br />
|
||||
{" × "}<a style="
|
||||
color: #B4BEFEFF !important;
|
||||
" href="https://bsky.app/profile/i0ur.ing">{r"bluesky"}</a><br />
|
||||
</p>
|
||||
</div>
|
||||
<div style="
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
margin: 1rem 1rem 1rem 1rem !important;
|
||||
">
|
||||
<p style="
|
||||
font-weight: bold !important;
|
||||
font-style: normal !important;
|
||||
font-size: xx-large !important;
|
||||
text-align: center !important;
|
||||
margin: unset !important;
|
||||
">{r"write us"}</p>
|
||||
<p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: large !important;
|
||||
">
|
||||
{"× "}
|
||||
<button style="
|
||||
color: #94E2D5FF !important;
|
||||
font-size: large !important;
|
||||
" onclick={handle_copy_matrix}>{r"matrix"}</button>
|
||||
<br />
|
||||
{"× "}
|
||||
<button style="
|
||||
color: #B4BEFEFF !important;
|
||||
font-size: large !important;
|
||||
" onclick={handle_copy_telegram}>{r"telegr"}</button>
|
||||
<br />
|
||||
{"× "}
|
||||
<button style="
|
||||
color: #F38BA8FF !important;
|
||||
font-size: large !important;
|
||||
" onclick={handle_copy_discord}>{r"ewcord"}</button>
|
||||
</p>
|
||||
</div>
|
||||
<div style="
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
margin: 1rem 1rem 1rem 1rem !important;
|
||||
">
|
||||
<p style="
|
||||
font-weight: bold !important;
|
||||
font-style: normal !important;
|
||||
font-size: xx-large !important;
|
||||
text-align: center !important;
|
||||
margin: unset !important;
|
||||
">{r"git gud"}</p>
|
||||
<p style="
|
||||
font-weight: normal !important;
|
||||
font-style: normal !important;
|
||||
font-size: large !important;
|
||||
">{" × "}<a style="
|
||||
color: #F5C2E7FF !important;
|
||||
" href="https://git.celesteflare.cc/i0uring">{r"my own!!!"}</a><br />
|
||||
{" × "}<a style="
|
||||
color: #CBA6F7FF !important;
|
||||
" href="https://git.gay/luciel">{r"the gay one"}</a><br />
|
||||
{" × "}<a style="
|
||||
color: #B4BEFEFF !important;
|
||||
" href="https://git.rimuru.club/i0uring">{r"git of fren"}</a><br />
|
||||
{" × "}<a style="
|
||||
color: #89B4FAFF !important;
|
||||
" href="https://codeberg.org/i0uring">{r"a mountain what"}</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
6
src/pages/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
||||
pub mod about;
|
||||
pub mod blog;
|
||||
pub mod findme;
|
||||
pub mod projects;
|
||||
|
||||
pub mod not_found;
|
||||
31
src/pages/not_found.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct PageNotFound;
|
||||
|
||||
impl Component for PageNotFound {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
text-align: center !important;
|
||||
flex-direction: row !important;
|
||||
align-items: center !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
">
|
||||
<img loading="eager" alt="huh" style="
|
||||
width: 96px !important;
|
||||
height: 96px !important;
|
||||
" width="96" height="96" src="404.png" />
|
||||
<h1>{ "what" }</h1>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
51
src/pages/projects/content.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub struct Project {
|
||||
pub url: &'static str,
|
||||
pub name: &'static str,
|
||||
pub desc: &'static str,
|
||||
}
|
||||
|
||||
impl ProjectEntry for Project {}
|
||||
|
||||
pub trait ProjectEntry: Sized {
|
||||
const PROJECTS_ONGOING: [&'static Project; 3] = [
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/stellaris/mod_headsup",
|
||||
name: "headsup mod",
|
||||
desc: "extensible hud mc mod",
|
||||
},
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/app_catnip",
|
||||
name: "catnip",
|
||||
desc: "all-rounder ide in the making",
|
||||
},
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/app_nekochat",
|
||||
name: "neko chat",
|
||||
desc: "my planned matrix client",
|
||||
},
|
||||
];
|
||||
|
||||
const PROJECTS_FINISHED: [&'static Project; 4] = [
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/lib_tinyevents",
|
||||
name: "tiny events",
|
||||
desc: "a java 21+ event-sys",
|
||||
},
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/dotfiles",
|
||||
name: "dotfiles",
|
||||
desc: "personal set of configurations",
|
||||
},
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/lib_swingify",
|
||||
name: "tiny events",
|
||||
desc: "simplifies java swing window creation",
|
||||
},
|
||||
&Project {
|
||||
url: "https://git.celesteflare.cc/i0uring/dotfiles",
|
||||
name: "modern netty",
|
||||
desc: "adds experimental netty handlers to mc",
|
||||
},
|
||||
];
|
||||
}
|
||||
3
src/pages/projects/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
pub mod content;
|
||||
pub mod projectcard;
|
||||
pub mod projects;
|
||||
54
src/pages/projects/projectcard.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use yew::prelude::*;
|
||||
|
||||
use crate::pages::projects::content::Project;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||
pub struct PropsAuthorCard {
|
||||
pub project: Project,
|
||||
}
|
||||
|
||||
pub struct ProjectCard {
|
||||
project: Project,
|
||||
}
|
||||
|
||||
impl Component for ProjectCard {
|
||||
type Message = ();
|
||||
type Properties = PropsAuthorCard;
|
||||
|
||||
fn create(ctx: &Context<Self>) -> Self {
|
||||
Self {
|
||||
project: ctx.props().project,
|
||||
}
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let Self { project } = self;
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
background-color: #ECBEE130 !important;
|
||||
border-radius: 0.5rem !important;
|
||||
width: fit-content !important;
|
||||
height: fit-content !important;
|
||||
text-align: left !important;
|
||||
margin: 1rem 1rem 0 0 !important;
|
||||
">
|
||||
<div style="
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: wrap !important;
|
||||
margin: 0.3rem 0.3rem 0.3rem 0.3rem !important;
|
||||
">
|
||||
<h2 style="margin: unset !important;">
|
||||
<a href={project.url}>{ &*project.name }</a>
|
||||
</h2>
|
||||
<h3 style="margin: unset !important;">
|
||||
{ &*project.desc }
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/pages/projects/projects.rs
Normal file
@@ -0,0 +1,54 @@
|
||||
use crate::pages::projects::content::{Project, ProjectEntry};
|
||||
use crate::pages::projects::projectcard::ProjectCard;
|
||||
use yew::prelude::*;
|
||||
|
||||
pub struct Projects;
|
||||
impl Component for Projects {
|
||||
type Message = ();
|
||||
type Properties = ();
|
||||
|
||||
fn create(_ctx: &Context<Self>) -> Self {
|
||||
Self
|
||||
}
|
||||
|
||||
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||
let projects_ongoing = Project::PROJECTS_ONGOING.map(|project| {
|
||||
let project = *project;
|
||||
html! { <ProjectCard {project} /> }
|
||||
});
|
||||
|
||||
let projects_finished = Project::PROJECTS_FINISHED.map(|project| {
|
||||
let project = *project;
|
||||
html! { <ProjectCard {project} /> }
|
||||
});
|
||||
|
||||
html! {
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: column !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: center !important;
|
||||
margin-bottom: 2rem !important;
|
||||
">
|
||||
<h2 style="text-align: center !important;">{ "ongoing projects" }</h2>
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: flex-start !important;
|
||||
margin-bottom: 1rem !important;
|
||||
">{ for projects_ongoing }</div>
|
||||
<h2 style="text-align: center !important;">{ "finished projects" }</h2>
|
||||
<div style="
|
||||
display: flex !important;
|
||||
width: 100% !important;
|
||||
flex-direction: row !important;
|
||||
flex-wrap: wrap !important;
|
||||
justify-content: flex-start !important;
|
||||
">{ for projects_finished }</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
}
|
||||