Compare commits

8 Commits

Author SHA1 Message Date
510897d94b push changes 2025-08-14 15:19:35 +02:00
078634b664 sloppy blog impl from example 2025-08-10 18:28:27 +02:00
6a5176caab 🐛 fix layout
🍱 update 88x31s
🔥 rm landing
2025-08-07 02:27:39 +02:00
i0uring
282c67b1fc update bg.frag 2025-07-30 15:24:38 +02:00
74224bd648 🐛 fix btn sizes 2025-05-08 22:12:02 +02:00
8f217ec82b 🐛 fix scrollbar
🔥 rm disclaimer in footer
2025-05-08 00:19:54 +02:00
0e1055b1a6 📝 change text
 add buttons
🔥 rm socials
2025-05-07 05:31:46 +02:00
94c56d7405 update justfile
🐛 fix theming
📝 change some wording
2025-04-23 21:23:33 +02:00
52 changed files with 1878 additions and 1299 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
.idea/
dist/
pkg/
target/
Cargo.lock
bulma.min.css

BIN
404.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

1
CNAME Normal file
View File

@@ -0,0 +1 @@
lunary.celesteflare.cc

33
Cargo.toml Normal file
View File

@@ -0,0 +1,33 @@
[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" }
[dependencies.web-sys]
version = "0.3"
features = [
'HtmlCanvasElement',
'WebGlBuffer',
'WebGlProgram',
'WebGlRenderingContext',
'WebGl2RenderingContext',
'WebGlShader',
'WebGlUniformLocation',
]

24
LICENSE Normal file
View 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
View File

@@ -0,0 +1,2 @@
[tools]
wasm_opt = "version_122"

6
docker-compose.yml Normal file
View File

@@ -0,0 +1,6 @@
version: '3'
services:
web:
build: ./
ports:
- "8080:8080"

Binary file not shown.

View File

@@ -1,52 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<html lang="en" class="theme-dark">
<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/profile.avif" />
<link data-trunk rel="copy-file" href="public/misc/oneko.gif" />
<link data-trunk rel="copy-file" href="public/misc/monocraft.ttf" />
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
<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" />
<link rel="stylesheet" href="/main-148897a668350f6e.css" integrity="sha384-nGIDBUHCWROKdlElGdixLBXi+MIlWjeP2AyvAlyfif+mxN1AkTIG3r0pc0V5+ujX"/>
<script type="module">
import init, * as bindings from '/web_iouring-5f95d2ecaec15eb1.js';
const wasm = await init({ module_or_path: '/web_iouring-5f95d2ecaec15eb1_bg.wasm' });
window.wasmBindings = bindings;
dispatchEvent(new CustomEvent("TrunkApplicationStarted", {detail: {wasm}}));
</script>
<base href="/" />
<link rel="modulepreload" href="/web_iouring-5f95d2ecaec15eb1.js" crossorigin="anonymous" integrity="sha384-KFz2s0PwvG25+EU7WT9Vuc63SqGBVvHQsZg55YjsnyQhCJeDBYwtOnPXu/onXchI"><link rel="preload" href="/web_iouring-5f95d2ecaec15eb1_bg.wasm" crossorigin="anonymous" integrity="sha384-fLrSAfSJSL3wUOfxHy4NkOhsIskb1fwwEcljekgZNxikCQO+AIGeMrSwXfDa5/F0" as="fetch" type="application/wasm"></head>
<link data-trunk rel="sass" href="main.scss" />
<link data-trunk rel="css" href="bulma.min.css" />
<link data-trunk rel="rust" />
<base data-trunk-public-url />
</head>
</html>

15
justfile Normal file
View File

@@ -0,0 +1,15 @@
#!/usr/bin/env just --justfile
setup:
rustup target add wasm32-unknown-unknown
install:
cargo install --locked wasm-pack trunk --force
debug:
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
trunk serve
build:
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
trunk build --release

View File

@@ -1,77 +0,0 @@
@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: rgba(245, 194, 231, 0.2509803922) !important;
}
::selection {
background: rgba(245, 194, 231, 0.2509803922) !important;
}
a {
color: #f5c2e7 !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;
}

91
main.scss Normal file
View File

@@ -0,0 +1,91 @@
@charset "UTF-8";
@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;
}
.card {
backdrop-filter: blur(4px) !important;
--bulma-card-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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 224 KiB

View File

Before

Width:  |  Height:  |  Size: 4.8 MiB

After

Width:  |  Height:  |  Size: 4.8 MiB

View File

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

View File

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 104 KiB

View File

Before

Width:  |  Height:  |  Size: 315 B

After

Width:  |  Height:  |  Size: 315 B

BIN
public/misc/profile.avif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

285
public/shaders/es3/bg.frag Normal file
View File

@@ -0,0 +1,285 @@
#version 300 es
precision highp float;
const float CAM_FAR = 20.0;
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
const float PI = radians(180.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 smootherstep(float edge0, float edge1, float x) {
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
}
float rand(float n) {
n = fract(n * 43758.5453);
n *= n;
return fract(n * 43758.5453);
}
float hash(float n) {
return fract(abs(fract(n) * 43758.5453));
}
float noise(float x) {
float i = floor(x);
float f = fract(x);
float u = f * f * (3.0 - 2.0 * f);
return mix(hash(i), hash(i + 1.0), u);
}
mat4 viewMatrix(vec3 dir, vec3 up) {
vec3 f = normalize(dir);
vec3 s = normalize(cross(f, up));
return mat4(vec4(s, 0.0), vec4(cross(s, f), 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);
return mat3(
v.x * v.x * k + c,
v.y * v.x * k - v.z,
v.z * v.x * k + v.y,
v.x * v.y * k + v.z,
v.y * v.y * k + c,
v.z * v.y * k - v.x,
v.x * v.z * k - v.y,
v.y * v.z * k + v.x,
v.z * v.z * k + 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));
}
vec2 getWave(vec2 position, vec2 dir, float speed, float frequency, float iTimeshift) {
float x = dot(dir, position) * frequency + iTimeshift * speed;
float wave = exp(sin(x) - 1.0);
float dist = wave * cos(x);
return vec2(wave, -dist);
}
float heightmap(vec2 worldPos) {
const float scale = 0.06;
vec2 p = worldPos * scale;
vec2 p2 = (artifactOffset.xz - vec2(0.0, 1.0)) * scale;
float d = (1.0 - smootherstep(0.0, 1.0, clamp(length(p2 - p) * 1.25, 0.0, 1.0))) * 0.87;
float angle = 0.0;
float freq = 5.0;
float speed = 2.0;
float weight = 1.9;
float wave = 0.0;
float waveScale = 0.0;
vec2 dir;
vec2 res;
for (int i = 0; i < 5; i++) {
dir = vec2(cos(angle), sin(angle));
res = getWave(p, dir, speed, freq, time);
p += dir * res.y * weight * 0.05;
wave += res.x * weight - d;
angle += 12.0;
waveScale += weight;
weight = mix(weight, 0.0, 0.2);
freq *= 1.18;
speed *= 1.06;
}
return wave * (1.0 / waveScale);
}
float octahedron(vec3 p, float s) {
p = abs(p);
return (p.x + p.y + p.z - s) * 0.57735027;
}
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);
}
float objectsDist(vec3 p) {
return artifactDist(p);
}
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 waterColor(vec3 ray, vec3 normal, vec3 p) {
vec3 color = vec3(0.0);
float fogDist = length(p - vec3(0.0, 0.0, -6.0));
float dist = 0.0;
int objId = 0;
vec3 refl = reflect(ray, normal);
vec3 rayPos = p + refl * dist;
if (length(p.xz - artifactOffset.xz) < 8.5 && dot(refl, normalize(artifactOffset - p)) > -0.25) {
for (int i = 0; i < 40; i++) {
dist = objects(rayPos, color, objId);
if (dist < 0.01) {
color = objectsColor(objId, objectsNormal(rayPos, 0.001), rayPos);
break;
}
rayPos += refl * dist;
}
}
float fresnel = 0.04 + 0.9 * pow(1.0 - max(0.0, dot(-normal, ray)), 7.0);
float d = length(artifactOffset - p);
const float r = 14.0;
float atten = clamp(1.0 - (d * d) / (r * r), 0.0, 1.0);
atten *= atten;
vec3 point = vec3(0.75, 0.55, 0.45) * atten * (1.0 + fresnel) * 0.07;
vec3 ambient = dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * max(fresnel, 0.06) * vec3(0.1, 0.5, 1.0) * 0.85;
float fog = smootherstep(25.0, 6.0, fogDist) * (1.0 / (fogDist * 0.1));
return color + (point + ambient) * fog;
}
vec3 waterNormal(vec2 p, float eps) {
vec2 h = vec2(eps, 0.0);
return normalize(vec3(heightmap(p - h.xy) - heightmap(p + h.xy), eps * 2.0, heightmap(p - h.yx) - heightmap(p + h.yx)));
}
void marchWater(vec3 eye, vec3 ray, inout vec4 color) {
const vec3 planeNorm = vec3(0.0, 1.0, 0.0);
const float depth = 3.0;
float ceilDist = intersectPlane(eye, ray, vec3(0.0, 0.0, 0.0), planeNorm);
vec3 normal = vec3(0.0);
if (dot(planeNorm, ray) > -0.05) {
color = vec4(vec3(0.0), CAM_FAR);
return;
}
float height = 0.0;
vec3 rayPos = eye + ray * ceilDist;
for (int i = 0; i < 30; i++) {
height = heightmap(rayPos.xz) * depth - depth;
if (rayPos.y - height < 0.1) {
color.w = distance(rayPos, eye);
vec3 normPos = (eye + ray * color.w);
color.rgb = waterColor(ray, waterNormal(normPos.xz, 0.005), normPos);
return;
}
rayPos += ray * max(rayPos.y - height, 0.1);
}
color = vec4(vec3(0.0), CAM_FAR);
}
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);
vec3 waterColor;
marchWater(camPos, ray, color);
marchObjects(camPos, ray, color.w, color);
return color.rgb;
}
float snow(vec2 uv, float scale) {
float w = smootherstep(1.0, 0.0, -uv.y * (scale * 0.01));
if (w < 0.1) return 0.0;
uv += time / scale;
uv.y += time / scale;
uv.x += sin(uv.y + time * 0.125) / scale;
uv *= scale;
vec2 s = floor(uv), f = fract(uv);
return smootherstep(0.0, min(length(0.5 + 0.5 * sin(11.0 * fract(sin((s + scale) * mat2(7.0, 3.0, 6.0, 5.0)) * 5.0)) - f), 3.0), sin(f.x + f.y) * 0.01) * w;
}
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord * (vec2(1.0) / 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 p = fragCoord.xy / resolution.xy;
vec2 uvSnow = (fragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
fragColor += mix(vec4(vec3(snow(uvSnow, 4.0)), 0.5) + vec4(vec3(snow(uvSnow, 3.0)), 0.5), vec4(0.0), vec4(0.7));
}
}
void main() {
fragColor = vColor;
mainImage(fragColor, gl_FragCoord.xy);
}

View 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);
}

View File

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 691 B

0
src/lib.rs Normal file
View File

377
src/main.rs Normal file
View File

@@ -0,0 +1,377 @@
use js_sys::Float32Array;
use log::{info, warn};
use std::cell::RefCell;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext as GL2};
use yew::prelude::*;
use yew_router::prelude::*;
mod pages;
use crate::pages::about::About;
use crate::pages::blog::entries::Entries;
use crate::pages::blog::entry::Entry;
use crate::pages::blog::authors::Authors;
use crate::pages::blog::author::Author;
use crate::pages::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,
#[not_found]
#[at("/404")]
NotFound,
}
pub struct App {
node_ref: NodeRef,
navbar_active: bool,
}
pub enum Msg {
ToggleNavbar,
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self { node_ref: NodeRef::default(), navbar_active: false }
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::ToggleNavbar => {
self.navbar_active = !self.navbar_active;
true
}
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
<>
<canvas style="
background-color: black !important;
position: fixed !important;
left: 0 !important;
top: 0 !important;
z-index: 0 !important;
" ref={self.node_ref.clone()}/>
<div style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
height: 100vh !important;
">
<BrowserRouter>
<main style="
z-index: 1 !important;
display: flex !important;
flex-direction: row-reverse !important;
flex-wrap: wrap !important;
overflow: visible !important;
left: 0 !important;
margin-top: calc(var(--bulma-navbar-height)) !important;
margin-bottom: calc(var(--bulma-navbar-height) * 2) !important;
height: calc(100vh - (var(--bulma-navbar-height) * 2)) !important;
width: 100% !important;
align-items: center !important;
"><Switch<Route> render={switch} />
</main>
<header style="
z-index: 1 !important;
display: flex;
flex-direction: column;
flex-wrap: wrap;
">{ self.view_header(_ctx) }</header>
</BrowserRouter>
<footer class="footer" style="
--bulma-footer-background-color: #1E1E2E80 !important;
z-index: 1 !important;
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
position: fixed !important;
left: 0px !important;
bottom: 0px !important;
margin-bottom: 0 !important;
width: 100% !important;
padding: 0 !important;
">{ self.view_footer() }</footer>
</div>
</>
}
}
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 seed={id as u8} /> } }
Route::Authors => { html! { <Authors /> } }
Route::Author { id } => { html! { <Author seed={id} /> } }
Route::Projects => { html! { <Projects /> } }
Route::NotFound => { html! { <PageNotFound /> } }
}
}
impl App {
fn view_header(&self, _ctx: &Context<Self>) -> Html {
let active_class = if self.navbar_active { "is-active" } else { "" };
html! {
<nav class="navbar" style="
z-index: 1 !important;
position: fixed !important;
top: 0px !important;
left: 0px !important;
width: 100% !important;
padding: 0 !important;
--bulma-navbar-background-color: #1E1E2E80 !important;
backdrop-filter: blur(4px) !important;
">
<div class="navbar-brand">
<button class={classes!("navbar-burger", "burger", active_class)} aria-label="menu" aria-expanded="false" onclick={_ctx.link().callback(|_| Msg::ToggleNavbar)}>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</button>
</div>
<div class={classes!("navbar-menu", active_class)}>
<div class="navbar-start">
<Link<Route> classes={classes!("navbar-item")} to={Route::About}>{r"about"}</Link<Route>>
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>{r"blog"}</Link<Route>>
<Link<Route> classes={classes!("navbar-item")} to={Route::Projects}>{r"projects"}</Link<Route>>
</div>
</div>
</nav>
}
}
fn view_footer(&self) -> Html {
html! {
<>
<div class="is-flex is-flex-direction-column is-flex-wrap-wrap" style="
backdrop-filter: blur(4px);
width: 100vw !important;
text-align: left !important;
vertical-align: middle !important;
height: 40px !important; max-height: 40px !important;
image-rendering: pixelated !important;
">
<div class="is-align-content-flex-start is-flex is-flex-shrink-0 is-flex-direction-column is-align-content-center is-flex-wrap-wrap ml-0 mt-0 mr-0 mb-0" style="width: 100% !important; min-height: 40px !important; height: 40px !important; max-height: 40px !important; overflow-y: hidden !important; overflow-x: auto !important;">
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://i0ur.ing/"><img loading="eager" alt="servfail" class="image" src="iouring.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://webassembly.org/"><img loading="eager" alt="wasm" class="image" src="wasm.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" class="image" src="servfail.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" class="image" src="dataforest.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" class="image" src="fedora.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://artixlinux.org/"><img loading="eager" alt="artix" class="image" src="artix.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://nixos.org/"><img loading="eager" alt="nixos" class="image" src="nixos.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://chimera-linux.org/"><img loading="eager" alt="void" class="image" src="chimera.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://voidlinux.org/"><img loading="eager" alt="void" class="image" src="void.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" class="image" src="rust.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" class="image" src="csharp.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" class="image" src="kotlin.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
" href="https://www.java.com/"><img loading="eager" alt="java" class="image" src="java.png" /></a>
<a class="mt-1 mb-1 mr-1 ml-0" style="
width: 88px !important;
height: 31px !important
"><img loading="eager" alt="aqueer" class="image" src="aqueer.png" /></a>
</div>
</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();
}

41
src/pages/about.rs Normal file
View File

@@ -0,0 +1,41 @@
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 class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src="profile.avif" />
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;"><img loading="eager" alt="ket" class="image is-24x24 is-square is-inline-block mg-small" src="oneko.gif" />{r" Lucielle R. Hoerner"}</p>
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
<p>{r"they/she · transfem"}</p>
<p>{r"certified catgirl™"}</p>
<p>{r"software engineer"}</p>
</p>
</div>
<hr style="background-color: #B4BEFE60 !important;" />
<p class="subtitle">{r#"about me"#}</p>
<div class="content is-size-7">{r#"
haj! i'm luciel, but you can also just call me lucie.
i have lots of interests including music, cooking, development and learning new things.
in my free-time i mostly work on my private projects or spent time with my partners or online friends.
my knowledge is based on autodidactics and experiences with (former) friends.
looking for an apprenticeship or job atm. because i wasn't able to finish my previous one due to some financial circumstances.
you can find public projects i work on somewhere in the header.
i may or may not update information stated here.
"#}
</div>
</div>
</>
}
}
}

82
src/pages/blog/author.rs Normal file
View File

@@ -0,0 +1,82 @@
use yew::prelude::*;
use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry;
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct Props {
pub seed: 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_seed(ctx.props().seed), }
}
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.author = content::Author::from_seed(ctx.props().seed);
true
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
html! {
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5">
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src={author.image_url.clone()} />
<div class="columns is-narrow ml-3 is-inline-block is-vcentered is-centered is-gapless is-multiline is-0 mt-4" style="vertical-align: top !important;">
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;">{author.name.clone()}</p>
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;">
{ for author.keywords.iter().map(|tag| html! { <p>{ tag }</p> }) }
</p>
</div>
<hr style="background-color: #B4BEFE60 !important;" />
<p class="subtitle">{r#"about me"#}</p>
<div class="content is-size-7">
{ author.about.clone() }
</div>
</div>
}
}
}
/*
<div class="section container">
<div class="tile is-ancestor is-vertical">
<div class="tile is-parent">
<article class="tile is-child notification is-light">
<p class="title">{ &author.name }</p>
</article>
</div>
<div class="tile">
<div class="tile is-parent is-3">
<article class="tile is-child notification">
<p class="title">{ "Interests" }</p>
<div class="tags">
{ for author.keywords.iter().map(|tag| html! { <span class="tag is-info">{ tag }</span> }) }
</div>
</article>
</div>
<div class="tile is-parent">
<figure class="tile is-child image is-square">
<img alt="The author's profile picture." src={author.image_url.clone()} />
</figure>
</div>
<div class="tile is-parent">
<article class="tile is-child notification is-info">
<div class="content">
<p class="title">{ "About me" }</p>
<div class="content">
{ author.about.clone() }
</div>
</div>
</article>
</div>
</div>
</div>
</div>
*/

View File

@@ -0,0 +1,61 @@
use yew::prelude::*;
use yew_router::components::Link;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Author;
use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsAuthorCard {
pub seed: 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_seed(ctx.props().seed), }
}
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.author = Author::from_seed(ctx.props().seed);
true
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
html! {
<>
<div class="card">
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-128x128">
<img alt="this should normally show an image mew,," src={author.image_url.clone()} />
</figure>
</div>
<div class="media-content">
<p class="title is-3">{ &author.name }</p>
<p>
{ "I like " }
<b>{ author.keywords.join(", ") }</b>
</p>
</div>
</div>
</div>
<footer class="card-footer">
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}>
{ "Profile" }
</Link<Route>>
</footer>
</div>
</>
}
}
}

31
src/pages/blog/authors.rs Normal file
View File

@@ -0,0 +1,31 @@
use yew::prelude::*;
use crate::pages::blog::authorcard::AuthorCard;
/*pub enum Msg {
NextAuthors,
}*/
pub struct Authors {
seeds: Vec<u8>,
}
impl Component for Authors {
type Message = (); //Msg;
type Properties = ();
fn create(_ctx: &Context<Self>) -> Self {
Self { seeds: vec![0], }
}
/*fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg { Msg::NextAuthors => { self.seeds = vec![0]; true } }
}*/
fn view(&self, _: &Context<Self>) -> Html {
html! {
{ for self.seeds.iter().map(|&seed| { html! {
<AuthorCard {seed} />
} }) }
}
}
}

144
src/pages/blog/content.rs Normal file
View File

@@ -0,0 +1,144 @@
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Author {
pub seed: u8,
pub name: String,
pub keywords: Vec<String>,
pub image_url: String,
pub about: String
}
impl BlogEntry for Author {
fn from_entry(entry: &mut Entry) -> Self {
return match entry.seed {
0 => Self {
seed: entry.seed,
name: "iouring".to_string(),
image_url: "profile.avif".to_string(),
about: "sup".to_string(),
keywords: vec![
"meow".to_string(),
"mrrp".to_string(),
"mew".to_string()
]
},
_ => Self {
seed: u8::MAX,
name: "not found".to_string(),
image_url: "".to_string(),
about: "".to_string(),
keywords: vec![]
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct PostMeta {
pub seed: u8,
pub title: String,
pub author: Author,
pub keywords: Vec<String>,
pub image_url: String,
}
impl BlogEntry for PostMeta {
fn from_entry(entry: &mut Entry) -> Self {
return match entry.seed {
0 => Self {
seed: entry.seed,
title: "awawa title".to_string(),
author: Author::from_entry(entry),
keywords: vec!["meow".to_string(), "mrrp".to_string(), "mew".to_string()],
image_url: "testimage.jpg".to_string()
},
_ => Self {
seed: entry.seed,
title: "not found".to_string(),
author: Author::from_entry(entry),
keywords: vec![],
image_url: "".to_string()
}
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Post {
pub meta: PostMeta,
pub content: Vec<PostPart>,
}
impl BlogEntry for Post {
fn from_entry(entry: &mut Entry) -> Self {
return Self {
meta: PostMeta::from_entry(entry),
content: vec![PostPart::from_entry(entry)]
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum PostPart {
Section(Section),
Quote(Quote),
}
impl BlogEntry for PostPart {
fn from_entry(entry: &mut Entry) -> Self {
// TODO: ... | add proper logic
return Self::Section(Section::from_entry(entry))
// return Self::Quote(Quote::from_entry(entry))
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Section {
pub title: String,
pub paragraphs: Vec<String>,
pub image_url: String,
}
impl BlogEntry for Section {
fn from_entry(entry: &mut Entry) -> Self {
return match entry.seed {
0 => Self {
title: "awawa title".to_string(),
image_url: "".to_string(),
paragraphs: vec![
"meow 1".to_string(),
"mrrp 2".to_string(),
"mew 3".to_string()
]
},
_ => Self {
title: "not found".to_string(),
image_url: "".to_string(),
paragraphs: vec![]
},
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Quote {
pub author: Author,
pub content: String,
}
impl BlogEntry for Quote {
fn from_entry(entry: &mut Entry) -> Self {
return Self { author: Author::from_seed(entry.seed), content: "awawa content".to_string() }
}
}
pub struct Entry {
pub seed: u8
}
impl Entry {
pub fn from_seed(seed: u8) -> Self {
Self { seed }
}
}
pub trait BlogEntry: Sized {
fn from_entry(entry: &mut Entry) -> Self;
fn from_seed(seed: u8) -> Self {
Self::from_entry(&mut Entry::from_seed(seed))
}
}

91
src/pages/blog/entries.rs Normal file
View File

@@ -0,0 +1,91 @@
use yew::prelude::*;
use yew_router::prelude::*;
use crate::pages::blog::content::Author;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::entrycard::EntryCard;
use crate::pages::blog::navigation::{PageQuery, Pagination};
use crate::Route;
const ITEMS_PER_PAGE: u8 = 10;
const TOTAL_PAGES: u8 = 1;
pub enum Msg {
PageUpdated,
}
pub struct Entries {
page: u8,
_listener: LocationHandle,
}
fn current_page(ctx: &Context<Entries>) -> u8 {
let location = ctx.link().location().unwrap();
location.query::<PageQuery>().map(|it| it.page as u8).unwrap_or(1)
}
impl Component for Entries {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
let link = ctx.link().clone();
let listener = ctx
.link()
.add_location_listener(link.callback(move |_| Msg::PageUpdated))
.unwrap();
Self {
page: current_page(ctx),
_listener: listener,
}
}
fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
match msg {
Msg::PageUpdated => self.page = current_page(ctx),
}
true
}
fn view(&self, ctx: &Context<Self>) -> Html {
let page = self.page;
html! {
<div class="section container">
<h1 class="title">{ "entries" }</h1>
<h2 class="subtitle">{ "here are some things i yapped about" }</h2>
{ self.view_posts(ctx) }
<Pagination
{page}
total_pages={TOTAL_PAGES}
route_to_page={Route::Entries}
/>
</div>
}
}
}
impl Entries {
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
let start_seed = (self.page - 1) * ITEMS_PER_PAGE;
let cards: Vec<_> = (0..ITEMS_PER_PAGE)
.filter(|&seed_offset| Author::from_seed(seed_offset).seed != u8::MAX)
.map(|seed_offset| {
html! {
<li class="list-item mb-5">
<EntryCard seed={start_seed + seed_offset} />
</li>
}
}).collect();
html! {
<div class="columns">
<div class="column">
<ul class="list">
{ for cards }
</ul>
</div>
</div>
}
}
}

152
src/pages/blog/entry.rs Normal file
View File

@@ -0,0 +1,152 @@
use std::rc::Rc;
use yew::prelude::*;
use yew_router::prelude::*;
use crate::Route;
use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry;
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct Props {
pub seed: 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_seed(action.into()), }.into()
}
}
#[function_component]
pub fn Entry(props: &Props) -> Html {
let seed = props.seed;
let post = use_reducer(|| PostState {
inner: content::Post::from_seed(seed.into()),
});
{
let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
|| {}
});
}
let post = &post.inner;
let render_quote = |quote: &content::Quote| {
html! {
<article class="media block box my-6">
<figure class="media-left">
<p class="image is-64x64">
<img alt="The author's profile" src={quote.author.image_url.clone()} loading="lazy" />
</p>
</figure>
<div class="media-content">
<div class="content">
<Link<Route> classes={classes!("is-size-5")} to={Route::Author { id: quote.author.seed }}>
<strong>{ &quote.author.name }</strong>
</Link<Route>>
<p class="is-family-secondary">
{ &quote.content }
</p>
</div>
</div>
</article>
}
};
let render_section_hero = |section: &content::Section| {
html! {
<section class="hero is-dark has-background mt-6 mb-3">
<img alt="This section's image" class="hero-background is-transparent" src={section.image_url.clone()} loading="lazy" />
<div class="hero-body">
<div class="container">
<h2 class="subtitle">{ &section.title }</h2>
</div>
</div>
</section>
}
};
let render_section = |section, show_hero| {
let hero = if show_hero {
render_section_hero(section)
} else {
html! {}
};
let paragraphs = section.paragraphs.iter().map(|paragraph| {
html! {
<p>{ paragraph }</p>
}
});
html! {
<section>
{ hero }
<div>{ for paragraphs }</div>
</section>
}
};
let view_content = {
// don't show hero for the first section
let mut show_hero = false;
let parts = post.content.iter().map(|part| match part {
content::PostPart::Section(section) => {
let html = render_section(section, show_hero);
// show hero between sections
show_hero = true;
html
}
content::PostPart::Quote(quote) => {
// don't show hero after a quote
show_hero = false;
render_quote(&quote)
}
});
html! {{for parts}}
};
let keywords = post
.meta
.keywords
.iter()
.map(|keyword| html! { <span class="tag is-info">{ keyword }</span> });
html! {
<>
<section class="hero is-medium is-light has-background">
<img alt="The hero's background" class="hero-background is-transparent" src={post.meta.image_url.clone()} />
<div class="hero-body">
<div class="container">
<h1 class="title">
{ &post.meta.title }
</h1>
<h2 class="subtitle">
{ "by " }
<Link<Route> classes={classes!("has-text-weight-semibold")} to={Route::Author { id: post.meta.author.seed }}>
{ &post.meta.author.name }
</Link<Route>>
</h2>
<div class="tags">
{ for keywords }
</div>
</div>
</div>
</section>
<div class="section container">
{ view_content }
</div>
</>
}
}

View File

@@ -0,0 +1,68 @@
use std::rc::Rc;
use yew::prelude::*;
use yew_router::components::Link;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::PostMeta;
use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsEntryCard {
pub seed: u8,
}
#[derive(PartialEq, Eq, Debug)]
pub struct PostMetaState {
inner: PostMeta,
}
impl Reducible for PostMetaState {
type Action = u8;
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
Self { inner: PostMeta::from_seed(action.into()), }.into()
}
}
#[function_component]
pub fn EntryCard(props: &PropsEntryCard) -> Html {
let seed = props.seed;
let post = use_reducer_eq(|| PostMetaState {
inner: PostMeta::from_seed(seed.into()),
});
{
let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
|| {}
});
}
let post = &post.inner;
html! {
<div class="card" style="height: 300px !important; max-height: 300px !important;">
<img loading="eager" class="card-image image is-2by1" src={post.image_url.clone()} alt="blog image" style="
object-fit: cover !important;
width: 100% !important;
max-width: 100% !important;
height: 200px !important;
max-height: 200px !important;
filter: blur(6px) !important;
" />
<div class="card-content" style="height: fit-content !important; width: fit-content !important;">
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.seed }}>
{ &post.title }
</Link<Route>>
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.seed }}>
{ &post.author.name }
</Link<Route>>
</div>
</div>
}
}

8
src/pages/blog/mod.rs Normal file
View File

@@ -0,0 +1,8 @@
pub mod content;
pub mod entries;
pub mod entry;
pub mod entrycard;
pub mod authors;
pub mod author;
pub mod authorcard;
pub mod navigation;

View File

@@ -0,0 +1,213 @@
use std::ops::Range;
use serde::{Deserialize, Serialize};
use yew::prelude::*;
use yew_router::components::Link;
use crate::Route;
const ELLIPSIS: &str = "\u{02026}";
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)]
pub struct PageQuery {
pub page: u8,
}
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsNavigation {
pub page: u8,
pub total_pages: u8,
pub route_to_page: Route,
}
#[function_component]
pub fn RelNavButtons(props: &PropsNavigation) -> Html {
let PropsNavigation {
page,
total_pages,
route_to_page: to,
} = props.clone();
html! {
<>
<Link<Route, PageQuery>
classes={classes!("pagination-previous")}
disabled={page==1}
query={Some(PageQuery{page: page - 1})}
to={to.clone()}
>
{ "Previous" }
</Link<Route, PageQuery>>
<Link<Route, PageQuery>
classes={classes!("pagination-next")}
disabled={page==total_pages}
query={Some(PageQuery{page: page + 1})}
{to}
>
{ "Next page" }
</Link<Route, PageQuery>>
</>
}
}
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
pub struct RenderLinksProps {
range: Range<u8>,
len: usize,
max_links: usize,
props: PropsNavigation,
}
#[function_component]
pub fn RenderLinks(props: &RenderLinksProps) -> Html {
let RenderLinksProps {
range,
len,
max_links,
props,
} = props.clone();
let mut range = range;
if len > max_links {
let last_link =
html! {<RenderLink to_page={range.next_back().unwrap()} props={props.clone()} />};
let links = range
.take(max_links - 2)
.map(|page| html! {<RenderLink to_page={page} props={props.clone()} />});
html! {
<>
{ for links }
<li><span class="pagination-ellipsis">{ ELLIPSIS }</span></li>
{ last_link }
</>
}
} else {
html! {
for page in range {
<RenderLink to_page={page} props={props.clone()} />
}
}
}
}
#[derive(Properties, Clone, Debug, PartialEq, Eq)]
pub struct RenderLinkProps {
to_page: u8,
props: PropsNavigation,
}
#[function_component]
pub fn RenderLink(props: &RenderLinkProps) -> Html {
let RenderLinkProps { to_page, props } = props.clone();
let PropsNavigation {
page,
route_to_page,
..
} = props;
let is_current_class = if to_page == page { "is-current" } else { "" };
html! {
<li>
<Link<Route, PageQuery>
classes={classes!("pagination-link", is_current_class)}
to={route_to_page}
query={Some(PageQuery{page: to_page})}
>
{ to_page }
</Link<Route, PageQuery>>
</li>
}
}
#[function_component]
pub fn Links(props: &PropsNavigation) -> Html {
const LINKS_PER_SIDE: usize = 3;
let PropsNavigation {
page, total_pages, ..
} = *props;
let pages_prev = page.checked_sub(1).unwrap_or_default() as usize;
let pages_next = (total_pages - page) as usize;
let links_left = LINKS_PER_SIDE.min(pages_prev)
// if there are less than `LINKS_PER_SIDE` to the right, we add some more on the left.
+ LINKS_PER_SIDE.checked_sub(pages_next).unwrap_or_default();
let links_right = 2 * LINKS_PER_SIDE - links_left;
html! {
<>
<RenderLinks range={ 1..page } len={pages_prev} max_links={links_left} props={props.clone()} />
<RenderLink to_page={page} props={props.clone()} />
<RenderLinks range={ page + 1..total_pages + 1 } len={pages_next} max_links={links_right} props={props.clone()} />
</>
}
}
#[function_component]
pub fn Pagination(props: &PropsNavigation) -> Html {
html! {
<nav class="pagination is-right" role="navigation" aria-label="pagination">
<RelNavButtons ..{props.clone()} />
<ul class="pagination-list">
<Links ..{props.clone()} />
</ul>
</nav>
}
}
#[function_component]
pub fn Nav() -> Html {
let navbar_active = use_state_eq(|| false);
let toggle_navbar = {
let navbar_active = navbar_active.clone();
Callback::from(move |_| {
navbar_active.set(!*navbar_active);
})
};
let active_class = if !*navbar_active { "is-active" } else { "" };
html! {
<nav class="navbar is-primary" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<h1 class="navbar-item is-size-3">{ "Yew Blog" }</h1>
<button class={classes!("navbar-burger", "burger", active_class)}
aria-label="menu" aria-expanded="false"
onclick={toggle_navbar}
>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</button>
</div>
<div class={classes!("navbar-menu", active_class)}>
<div class="navbar-start">
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}> // TODO: ...
{ "Home" }
</Link<Route>>
<Link<Route> classes={classes!("navbar-item")} to={Route::Entries}>
{ "Posts" }
</Link<Route>>
<div class="navbar-item has-dropdown is-hoverable">
<div class="navbar-link">
{ "More" }
</div>
<div class="navbar-dropdown">
<Link<Route> classes={classes!("navbar-item")} to={Route::Authors}>
{ "Meet the authors" }
</Link<Route>>
</div>
</div>
</div>
</div>
</nav>
}
}

5
src/pages/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod about;
pub mod blog;
pub mod projects;
pub mod not_found;

26
src/pages/not_found.rs Normal file
View File

@@ -0,0 +1,26 @@
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 {
// TODO: ... | personalize
html! {
<section class="hero is-danger is-bold is-large">
<div class="hero-body">
<div class="container">
<h1 class="title">{ "Page not found" }</h1>
<h2 class="subtitle">{ "Page page does not seem to exist" }</h2>
</div>
</div>
</section>
}
}
}

79
src/pages/projects.rs Normal file
View File

@@ -0,0 +1,79 @@
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 {
html! {
<>
<div class="tile is-parent container mb-5 mt-5" style="height: auto !important;">
{ self.view_projects_ongoing() }
{ self.view_projects_finished() }
</div>
</>
}
}
}
impl Projects {
fn view_projects_ongoing(&self) -> Html {
html! {
<>
<p class="title ml-2 is-size-4">{r#"ongoing projects"#}</p>
<div class="columns is-size-7 ml-3 mr-3" style="margin-top: var(--bulma-block-spacing) !important;">
<div class="column box mb-5 mr-1">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/app_catnip">
{r#"catnip"#}
</a></p>
<p class="content">{r#"all-rounder ide in the making"#}</p>
</div>
<div class="column box mb-5 ml-1">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/app_nekochat">
{r#"neko chat"#}
</a></p>
<p class="content">{r#"my planned matrix client"#}</p>
</div>
</div>
</>
}
}
fn view_projects_finished(&self) -> Html {
html! {
<>
<p class="title ml-2 is-size-4">{r#"finished projects"#}</p>
<div class="columns is-size-7 ml-3 mr-3">
<div class="column box mb-5 mr-1">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/lib_tinyevents">
{r#"tiny events"#}
</a></p>
<p class="content">{r#"a java seventeen (and up) event-sys that is able to be scaled in large systems."#}</p>
</div>
<div class="column box mb-5 ml-1 mr-2">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/dotfiles">
{r#"dotfiles"#}
</a></p>
<p class="content">{r#"my personal set of configurations for different things"#}</p>
</div>
<div class="column box mb-5 mr-1">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/i0uring/lib_swingify">
{r#"swingify"#}
</a></p>
<p class="content">{r#"my java swing wrapper to simplify window creation for bogus-brains."#}</p>
</div>
<div class="column box mb-5 ml-1">
<p class="subtitle is-inline-block mb-0"><a href="https://git.celesteflare.cc/stellaris/mod_mnet">
{r#"modern netty"#}
</a></p>
<p class="content">{r#"fabric mod that adds experimental iouring and kqueue support."#}</p>
</div>
</div>
</>
}
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.