Compare commits

4 Commits

Author SHA1 Message Date
35aa3b076f add iocaine blog post 2025-08-25 21:26:10 +00:00
2e397c5be3 finish blog
 add first blog entry
🎨 rework ui+ux
 add freemono
2025-08-24 03:46:08 +00:00
f1c471839f readd btns
🐛 fixed some blog stuff
2025-08-21 02:19:36 +00:00
bfd8c187c6 🔥 rm bulma
 rework ui+ux
 update shader
 update profile.avif
 update 404
2025-08-20 08:38:03 +00:00
29 changed files with 1270 additions and 1144 deletions

2
.gitignore vendored
View File

@@ -3,4 +3,4 @@ dist/
pkg/ pkg/
target/ target/
Cargo.lock Cargo.lock
bulma.min.css static/

View File

@@ -19,15 +19,19 @@ log = { version = "0.4" }
wasm-logger = { version = "0.2" } wasm-logger = { version = "0.2" }
serde = { version = "1.0" } 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] [dependencies.web-sys]
version = "0.3" version = "0.3"
features = [ features = ["HtmlCanvasElement", "WebGlBuffer", "WebGlProgram", "WebGlRenderingContext", "WebGl2RenderingContext", "WebGlShader", "WebGlUniformLocation", "Navigator", "Clipboard"]
'HtmlCanvasElement',
'WebGlBuffer',
'WebGlProgram',
'WebGlRenderingContext',
'WebGl2RenderingContext',
'WebGlShader',
'WebGlUniformLocation',
]

View File

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

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="theme-dark"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -7,8 +7,11 @@
<title>i0ur.ing ~ welcome</title> <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/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/profile.avif" />
<link data-trunk rel="copy-file" href="public/misc/oneko.gif" /> <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/misc/monocraft.ttf" />
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" /> <link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
@@ -31,7 +34,6 @@
<link data-trunk rel="copy-file" href="public/buttons/aqueer.png" /> <link data-trunk rel="copy-file" href="public/buttons/aqueer.png" />
<link data-trunk rel="sass" href="main.scss" /> <link data-trunk rel="sass" href="main.scss" />
<link data-trunk rel="css" href="bulma.min.css" />
<link data-trunk rel="rust" /> <link data-trunk rel="rust" />
<base data-trunk-public-url /> <base data-trunk-public-url />
</head> </head>

View File

@@ -7,9 +7,7 @@ install:
cargo install --locked wasm-pack trunk --force cargo install --locked wasm-pack trunk --force
debug: debug:
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
trunk serve trunk serve
build: build:
rm bulma.min.css && curl --proto '=https' --tlsv1.3 -LO https://cdn.jsdelivr.net/npm/bulma/css/bulma.min.css
trunk build --release trunk build --release

View File

@@ -1,12 +1,17 @@
@charset "UTF-8"; @charset "UTF-8";
@font-face {
font-family: freemono;
src: url("freemono.ttf");
}
@font-face { @font-face {
font-family: monocraft; font-family: monocraft;
src: url("monocraft.ttf"); src: url("monocraft.ttf");
} }
html * { html * {
font-family: monocraft, monospace !important; font-family: freemono, monocraft, monospace !important;
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important; scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
scrollbar-width: thin !important; scrollbar-width: thin !important;
scroll-behavior: smooth !important; scroll-behavior: smooth !important;
@@ -27,49 +32,18 @@ html * {
.box { .box {
backdrop-filter: blur(4px) !important; 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 { .card {
backdrop-filter: blur(4px) !important; 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, .input,
.select select, .select select,
.textarea { .textarea {
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
--bulma-input-h: 232 !important; background-color: white, 0.213125 !important;
--bulma-input-s: 97% !important; border-color: white !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,
@@ -78,14 +52,30 @@ a.navbar-item:hover {
} }
::-moz-selection { ::-moz-selection {
background: hsla(232, 97%, 85%, 0.4125) !important; background: #F5C2E740 !important;
} }
::selection { ::selection {
background: hsla(232, 97%, 85%, 0.4125) !important; background: #F5C2E740 !important;
}
a {
color: #F5C2E7FF !important;
text-decoration: none !important;
} }
a:hover { a:hover {
text-decoration: underline !important; text-decoration: underline !important;
filter: url("post.bloom.svg#process") !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
public/misc/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
public/misc/freemono.ttf Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

After

Width:  |  Height:  |  Size: 224 KiB

View File

@@ -4,8 +4,6 @@ precision highp float;
const float CAM_FAR = 20.0; const float CAM_FAR = 20.0;
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0); const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
const float PI = radians(180.0);
uniform float time; uniform float time;
uniform vec2 resolution; uniform vec2 resolution;
uniform bool layer_snow; uniform bool layer_snow;
@@ -19,48 +17,42 @@ vec3 artifactAxis;
vec3 camFwd; vec3 camFwd;
vec3 camUp; 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) { float rand(float n) {
n = fract(n * 43758.5453); return fract(sin(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 noise(float x) {
float i = floor(x); float i = floor(x);
float f = fract(x); float f = fract(x);
float u = f * f * (3.0 - 2.0 * f); return mix(rand(i), rand(i + 1.0), f * f * (3.0 - 2.0 * f));
return mix(hash(i), hash(i + 1.0), u);
} }
mat4 viewMatrix(vec3 dir, vec3 up) { mat4 viewMatrix(vec3 dir, vec3 up) {
vec3 f = normalize(dir); vec3 f = normalize(dir);
vec3 s = normalize(cross(f, up)); 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)); 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) { mat3 rotationAlign(vec3 d, vec3 z) {
vec3 v = cross(z, d); vec3 v = cross(z, d);
float c = dot(z, d); float c = dot(z, d);
float k = 1.0 / (1.0 + c); 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( return mat3(
v.x * v.x * k + c, k_vx * v.x + c, k_vx * v.y - v.z, k_vx * v.z + v.y,
v.y * v.x * k - v.z, k_vy * v.x + v.z, k_vy * v.y + c, k_vy * v.z - v.x,
v.z * v.x * k + v.y, k_vz * v.x - v.y, k_vz * v.y + v.x, k_vz * v.z + c
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
); );
} }
@@ -74,48 +66,10 @@ vec3 calcRay(vec2 uv, float fov, float aspect) {
return normalize(vec3(aspect * uv.x, uv.y, d)); 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) { float octahedron(vec3 p, float s) {
const float factor = 0.57735027;
p = abs(p); p = abs(p);
return (p.x + p.y + p.z - s) * 0.57735027; return (p.x + p.y + p.z - s) * factor;
} }
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) { void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
@@ -145,10 +99,6 @@ float artifactDist(vec3 p) {
return octahedron(p, 1.2); return octahedron(p, 1.2);
} }
float objectsDist(vec3 p) {
return artifactDist(p);
}
vec3 objectsNormal(vec3 p, float eps) { vec3 objectsNormal(vec3 p, float eps) {
vec2 h = vec2(eps, 0); 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))); return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
@@ -174,96 +124,41 @@ void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color; 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) { vec3 march(vec2 uv, vec3 camPos) {
mat4 vm = viewMatrix(camFwd, camUp); mat4 vm = viewMatrix(camFwd, camUp);
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz; vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
vec4 color = vec4(BACKGROUND, CAM_FAR); vec4 color = vec4(BACKGROUND, CAM_FAR);
vec3 waterColor;
marchWater(camPos, ray, color);
marchObjects(camPos, ray, color.w, color); marchObjects(camPos, ray, color.w, color);
return color.rgb; return color.rgb;
} }
float snow(vec2 uv, float scale) { float snow(vec2 uv, float scale) {
float w = smootherstep(1.0, 0.0, -uv.y * (scale * 0.01)); float w = smoothstep(1.0, 0.0, -uv.y * (scale * 0.01));
if (w < 0.1) return 0.0; if (w < 0.1) return 0.0;
uv += time / scale;
uv.y += time / scale; float timeScale = time / scale;
uv += vec2(timeScale, timeScale);
uv.x += sin(uv.y + time * 0.125) / scale; uv.x += sin(uv.y + time * 0.125) / scale;
uv *= 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; 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) { void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord * (vec2(1.0) / resolution.xy); vec2 uv = fragCoord / resolution.xy;
float s = sin(time); float s = sin(time);
float c = cos(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)); 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); artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
@@ -273,9 +168,8 @@ void mainImage(out vec4 fragColor, in vec2 fragCoord) {
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.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) { if (layer_snow) {
vec2 p = fragCoord.xy / resolution.xy; vec2 uvSnow = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
vec2 uvSnow = (fragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y); fragColor += vec4(vec3(snow(uvSnow, 4.0)), 0.5) * 0.7;
fragColor += mix(vec4(vec3(snow(uvSnow, 4.0)), 0.5) + vec4(vec3(snow(uvSnow, 3.0)), 0.5), vec4(0.0), vec4(0.7));
} }
} }

View File

@@ -1,20 +1,24 @@
#![feature(iter_intersperse)]
extern crate core;
use js_sys::Float32Array; use js_sys::Float32Array;
use log::{info, warn}; use log::{info, warn};
use std::cell::RefCell; use std::cell::RefCell;
use std::sync::Arc; use std::sync::Arc;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::{window, HtmlCanvasElement, WebGl2RenderingContext as GL2}; use wasm_bindgen::prelude::*;
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext as GL2, window};
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*; use yew_router::prelude::*;
mod pages; mod pages;
use crate::pages::about::About; 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::entries::Entries;
use crate::pages::blog::entry::Entry; use crate::pages::blog::entry::Entry;
use crate::pages::blog::authors::Authors; use crate::pages::findme::FindMe;
use crate::pages::blog::author::Author; use crate::pages::projects::projects::Projects;
use crate::pages::projects::Projects;
use pages::not_found::PageNotFound; use pages::not_found::PageNotFound;
#[derive(Routable, PartialEq, Eq, Clone, Debug)] #[derive(Routable, PartialEq, Eq, Clone, Debug)]
@@ -34,6 +38,9 @@ pub enum Route {
#[at("/projects")] #[at("/projects")]
Projects, Projects,
#[at("/findme")]
FindMe,
#[not_found] #[not_found]
#[at("/404")] #[at("/404")]
NotFound, NotFound,
@@ -41,209 +48,274 @@ pub enum Route {
pub struct App { pub struct App {
node_ref: NodeRef, node_ref: NodeRef,
navbar_active: bool,
}
pub enum Msg {
ToggleNavbar,
} }
impl Component for App { impl Component for App {
type Message = Msg; type Message = ();
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { node_ref: NodeRef::default(), navbar_active: false } Self {
} node_ref: NodeRef::default(),
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 { fn view(&self, _ctx: &Context<Self>) -> Html {
html! { html! {
<> <>
<canvas style=" <canvas width="100%" height="100%" style="
background-color: black !important; background-color: black !important;
position: fixed !important; position: fixed !important;
left: 0 !important; left: 0 !important;
top: 0 !important; top: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 0 !important; z-index: 0 !important;
" ref={self.node_ref.clone()} /> " ref={self.node_ref.clone()} />
<div style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
height: 100vh !important;
">
<BrowserRouter> <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=" <main style="
z-index: 1 !important; z-index: 1 !important;
display: flex !important; background-color: transparent !important;
flex-direction: row-reverse !important; color: white !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; display: flex !important;
flex-direction: column !important; flex-direction: column !important;
flex-wrap: wrap !important; min-width: fit-content !important;
position: fixed !important; width: calc(100vw - 18 px) !important;
left: 0px !important; flex-wrap: nowrap !important;
bottom: 0px !important; justify-content: space-between !important;
margin-bottom: 0 !important; align-items: center !important;
width: 100% !important; justify-content: center !important;
padding: 0 !important; align-self: center !important;
">{ self.view_footer() }</footer> 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> </div>
</main>
</BrowserRouter>
</> </>
} }
} }
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) { fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
if first_render { self.view_canvas(); } if first_render {
self.view_canvas();
}
} }
} }
fn switch(routes: Route) -> Html { fn switch(routes: Route) -> Html {
match routes { match routes {
Route::About => { html! { <About /> } } Route::About => {
html! { <About /> }
}
Route::Entries => { html! { <Entries /> } } Route::Entries => {
Route::Entry { id } => { html! { <Entry seed={id as u8} /> } } html! { <Entries /> }
Route::Authors => { html! { <Authors /> } } }
Route::Author { id } => { html! { <Author seed={id} /> } } Route::Entry { id } => {
html! { <Entry id={id as u8} /> }
}
Route::Authors => {
html! { <Authors /> }
}
Route::Author { id } => {
html! { <Author id={id as u8} /> }
}
Route::Projects => { html! { <Projects /> } } Route::FindMe => {
Route::NotFound => { html! { <PageNotFound /> } } html! { <FindMe /> }
}
Route::Projects => {
html! { <Projects /> }
}
Route::NotFound => {
html! { <PageNotFound /> }
}
} }
} }
impl App { impl App {
fn view_header(&self, _ctx: &Context<Self>) -> Html { fn view_buttons(&self) -> Html {
let active_class = if self.navbar_active { "is-active" } else { "" };
html! { html! {
<nav class="navbar" style=" <div style="
z-index: 1 !important; z-index: 1 !important;
position: fixed !important; display: flex !important;
top: 0px !important; flex-wrap: wrap !important;
left: 0px !important; flex-direction: row !important;
width: 100% !important; justify-content: center !important;
padding: 0 !important; min-width: 70vw !important;
--bulma-navbar-background-color: #1E1E2E80 !important; width: 70vh !important;
backdrop-filter: blur(4px) !important; max-width: calc(100vw - 18px) !important;
height: fit-content !important;
overflow: hidden !important;
margin-right: auto !important;
margin-left: auto !important;
"> ">
<div class="navbar-brand"> <a style="
<button class={classes!("navbar-burger", "burger", active_class)} aria-label="menu" aria-expanded="false" onclick={_ctx.link().callback(|_| Msg::ToggleNavbar)}> width: 88px !important;
<span aria-hidden="true"></span> height: 31px !important;
<span aria-hidden="true"></span> margin-right: 0.2rem !important;
<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; image-rendering: pixelated !important;
"> image-resolution: from-image !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;"> image-orientation: from-image !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" " href="https://i0ur.ing/"><img loading="eager" alt="iouring" src="iouring.png" /></a>
<a style="
width: 88px !important; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://i0ur.ing/"><img loading="eager" alt="servfail" class="image" src="iouring.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://webassembly.org/"><img loading="eager" alt="wasm" class="image" src="wasm.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" class="image" src="servfail.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" class="image" src="dataforest.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" class="image" src="fedora.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://artixlinux.org/"><img loading="eager" alt="artix" class="image" src="artix.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://nixos.org/"><img loading="eager" alt="nixos" class="image" src="nixos.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://chimera-linux.org/"><img loading="eager" alt="void" class="image" src="chimera.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://voidlinux.org/"><img loading="eager" alt="void" class="image" src="void.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" class="image" src="rust.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" class="image" src="csharp.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" class="image" src="kotlin.png" /></a> margin-right: 0.2rem !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" 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; width: 88px !important;
height: 31px !important height: 31px !important;
" href="https://www.java.com/"><img loading="eager" alt="java" class="image" src="java.png" /></a> image-rendering: pixelated !important;
<a class="mt-1 mb-1 mr-1 ml-0" style=" image-resolution: from-image !important;
width: 88px !important; image-orientation: from-image !important;
height: 31px !important "><img loading="eager" alt="aqueer" src="aqueer.png" /></a>
"><img loading="eager" alt="aqueer" class="image" src="aqueer.png" /></a>
</div> </div>
</div>
</>
} }
} }
@@ -253,25 +325,35 @@ impl App {
let gl = canvas let gl = canvas
.get_context("webgl2") .get_context("webgl2")
.unwrap_or(canvas.get_context("webgl").unwrap()) .unwrap_or(canvas.get_context("webgl").unwrap())
.unwrap().dyn_into::<GL2>().unwrap(); .unwrap()
.dyn_into::<GL2>()
.unwrap();
let vertex_buffer = gl.create_buffer().unwrap(); let vertex_buffer = gl.create_buffer().unwrap();
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer)); gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![ gl.buffer_data_with_array_buffer_view(
-1.0, 1.0, 0.0, GL2::ARRAY_BUFFER,
-1.0, -1.0, 0.0, &Float32Array::from(
1.0, -1.0, 0.0, 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, 1.0, 1.0, 0.0,
].as_slice()), GL2::STATIC_DRAW); ]
.as_slice(),
),
GL2::STATIC_DRAW,
);
let color_buffer = gl.create_buffer().unwrap(); let color_buffer = gl.create_buffer().unwrap();
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer)); gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
gl.buffer_data_with_array_buffer_view(GL2::ARRAY_BUFFER, &Float32Array::from(vec![ gl.buffer_data_with_array_buffer_view(
1.0, 0.0, 0.0, 1.0, GL2::ARRAY_BUFFER,
0.0, 1.0, 0.0, 1.0, &Float32Array::from(
0.0, 0.0, 1.0, 1.0, vec![
1.0, 1.0, 0.0, 1.0 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); ]
.as_slice(),
),
GL2::STATIC_DRAW,
);
let element_buffer = gl.create_buffer().unwrap(); let element_buffer = gl.create_buffer().unwrap();
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer)); gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
@@ -307,7 +389,11 @@ impl App {
gl.attach_shader(&shader_program, &fragment_shader); gl.attach_shader(&shader_program, &fragment_shader);
gl.link_program(&shader_program); gl.link_program(&shader_program);
if !gl.get_program_parameter(&shader_program, GL2::LINK_STATUS).as_bool().unwrap() { if !gl
.get_program_parameter(&shader_program, GL2::LINK_STATUS)
.as_bool()
.unwrap()
{
warn!("shader couldn't be linked!"); warn!("shader couldn't be linked!");
} }
gl.use_program(Some(&shader_program)); gl.use_program(Some(&shader_program));
@@ -328,7 +414,9 @@ impl App {
gl.clear_color(0.0, 0.0, 0.0, 1.0); gl.clear_color(0.0, 0.0, 0.0, 1.0);
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT); 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 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_matrix = gl.get_uniform_location(&shader_program, "model");
let unim_time = gl.get_uniform_location(&shader_program, "time"); let unim_time = gl.get_uniform_location(&shader_program, "time");
let unim_res = gl.get_uniform_location(&shader_program, "resolution"); let unim_res = gl.get_uniform_location(&shader_program, "resolution");
@@ -336,10 +424,17 @@ impl App {
let mut timestamp = 0.0; let mut timestamp = 0.0;
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.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); gl.draw_elements_with_i32(
GL2::TRIANGLES,
element_indices.len() as i32,
GL2::UNSIGNED_SHORT,
0,
);
fn request_animation_frame(f: &Closure<dyn FnMut()>) { fn request_animation_frame(f: &Closure<dyn FnMut()>) {
window().unwrap().request_animation_frame(f.as_ref().unchecked_ref()) window()
.unwrap()
.request_animation_frame(f.as_ref().unchecked_ref())
.expect("should register `requestAnimationFrame` OK"); .expect("should register `requestAnimationFrame` OK");
} }
@@ -355,13 +450,21 @@ impl App {
gl.uniform_matrix4fv_with_f32_array(unim_matrix.as_ref(), false, default_matrix.as_slice()); 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.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.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.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.viewport(0, 0, canvas.width() as i32, canvas.height() as i32);
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT); 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); gl.draw_elements_with_i32(
GL2::TRIANGLES,
element_indices.len() as i32,
GL2::UNSIGNED_SHORT,
0,
);
request_animation_frame(cb.borrow().as_ref().unwrap()); request_animation_frame(cb.borrow().as_ref().unwrap());
} }

View File

@@ -12,28 +12,82 @@ impl Component for About {
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {
html! { html! {
<> <>
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5"> <div>
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src="profile.avif" /> <img alt="insert pfp here" style="
<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;"> float: left !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> margin-right: 10px !important;
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;"> border-radius: 0.5rem !important;
<p>{r"they/she · transfem"}</p> " loading="eager" width="96px" height="96px" src="profile.avif" />
<p>{r"certified catgirl™"}</p> <div style="
<p>{r"software engineer"}</p> 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> </p>
</div> </div>
<hr style="background-color: #B4BEFE60 !important;" /> <p style="
<p class="subtitle">{r#"about me"#}</p> font-weight: bold !important;
<div class="content is-size-7">{r#" font-style: italic !important;
haj! i'm luciel, but you can also just call me lucie. font-size: xx-large !important;
i have lots of interests including music, cooking, development and learning new things. ">{r">intro_"}<br /><p style="
in my free-time i mostly work on my private projects or spent time with my partners or online friends. font-weight: normal !important;
my knowledge is based on autodidactics and experiences with (former) friends. font-style: normal !important;
looking for an apprenticeship or job atm. because i wasn't able to finish my previous one due to some financial circumstances. font-size: medium !important;
you can find public projects i work on somewhere in the header. ">{r#"
i may or may not update information stated here. name's lia or lucy. we are creating.
"#} trying to provide a safe space in the internet.
</div> 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> </div>
</> </>
} }

View File

@@ -1,10 +1,12 @@
use yew::prelude::*;
use crate::pages::blog::content; use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry; 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)] #[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub seed: u8, pub id: u8,
} }
pub struct Author { pub struct Author {
@@ -15,68 +17,55 @@ impl Component for Author {
type Properties = Props; type Properties = Props;
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Self { author: content::Author::from_seed(ctx.props().seed), } Self {
author: content::Author::from_id(ctx.props().id),
}
} }
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.author = content::Author::from_seed(ctx.props().seed); self.author = content::Author::from_id(ctx.props().id);
true true
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self; 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! { html! {
<div class="tile is-parent container box is-small is-flex-shrink-5 mb-5 mt-5"> <div style="
<img alt="insert pfp here" loading="eager" class="image is-128x128 is-square is-inline-block mg-small" src={author.image_url.clone()} /> display: flex !important;
<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;"> width: 100% !important;
<p class="column is-narrow" style="filter: url(post.bloom.svg#process) !important; color: #B4BEFE !important;">{author.name.clone()}</p> flex-direction: column !important;
<p class="column is-narrow" style="background-clip: text !important; background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;"> flex-wrap: wrap !important;
{ for author.keywords.iter().map(|tag| html! { <p>{ tag }</p> }) } justify-content: center !important;
</p> margin-bottom: 2rem !important;
</div> ">
<hr style="background-color: #B4BEFE60 !important;" /> <h2 style="text-align: center !important;">{ "about me" }</h2>
<p class="subtitle">{r#"about me"#}</p> <div style="
<div class="content is-size-7"> display: flex !important;
{ author.about.clone() } width: 100% !important;
</div> 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> </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

@@ -1,14 +1,14 @@
use yew::prelude::*; use yew::prelude::*;
use yew_router::components::Link; use yew_router::components::Link;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Author; use crate::pages::blog::content::Author;
use crate::pages::blog::content::BlogEntry;
use crate::Route; use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)] #[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsAuthorCard { pub struct PropsAuthorCard {
pub seed: u8, pub id: u8,
} }
pub struct AuthorCard { pub struct AuthorCard {
@@ -20,42 +20,53 @@ impl Component for AuthorCard {
type Properties = PropsAuthorCard; type Properties = PropsAuthorCard;
fn create(ctx: &Context<Self>) -> Self { fn create(ctx: &Context<Self>) -> Self {
Self { author: Author::from_seed(ctx.props().seed), } Self {
author: Author::from_id(ctx.props().id),
}
} }
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool { fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
self.author = Author::from_seed(ctx.props().seed); self.author = Author::from_id(ctx.props().id);
true true
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self; let Self { author } = self;
let keywords = author.keywords.iter().map(|keyword| html! {
<p style="margin: unset !important;">{*keyword}</p>
}).collect::<Html>();
html! { html! {
<> <div style="
<div class="card"> display: flex !important;
<div class="card-content"> flex-direction: row !important;
<div class="media"> flex-wrap: wrap !important;
<div class="media-left"> background-color: #ECBEE130 !important;
<figure class="image is-128x128"> border-radius: 0.5rem !important;
<img alt="this should normally show an image mew,," src={author.image_url.clone()} /> width: fit-content !important;
</figure> height: fit-content !important;
</div> text-align: left !important;
<div class="media-content"> margin: 1rem 1rem 0 0 !important;
<p class="title is-3">{ &author.name }</p> ">
<p> <img style="
{ "I like " } float: left !important;
<b>{ author.keywords.join(", ") }</b> margin-right: 0.3rem !important;
</p> border-radius: 0.5rem !important;
</div> " alt="insert pfp here" width="96" height="96" src={author.image_url} />
</div> <div style="
</div> display: flex !important;
<footer class="card-footer"> flex-direction: column !important;
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}> flex-wrap: wrap !important;
{ "Profile" } 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>> </Link<Route>>
</footer> </h3>
{ keywords }
</div>
</div> </div>
</>
} }
} }
} }

View File

@@ -1,31 +1,31 @@
use yew::prelude::*; use yew::prelude::*;
use crate::pages::blog::authorcard::AuthorCard; use crate::pages::blog::authorcard::AuthorCard;
use crate::pages::blog::content::{Author, BlogEntry};
/*pub enum Msg { pub struct Authors;
NextAuthors,
}*/
pub struct Authors {
seeds: Vec<u8>,
}
impl Component for Authors { impl Component for Authors {
type Message = (); //Msg; type Message = ();
type Properties = (); type Properties = ();
fn create(_ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
Self { seeds: vec![0], } Self
} }
/*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 { fn view(&self, _: &Context<Self>) -> Html {
html! { html! {
{ for self.seeds.iter().map(|&seed| { html! { <div style="
<AuthorCard {seed} /> 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>
} }
} }
} }

View File

@@ -1,144 +1,299 @@
#[derive(Clone, Debug, Eq, PartialEq)] #[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Author { pub struct Author {
pub seed: u8, pub id: u8,
pub name: String, pub image_url: &'static str,
pub keywords: Vec<String>, pub name: &'static str,
pub image_url: String, pub keywords: &'static [&'static str],
pub about: String 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 { impl BlogEntry for Author {
fn from_entry(entry: &mut Entry) -> Self { fn from_entry(entry: &mut Entry) -> Self {
return match entry.seed { *Self::AUTHORS
0 => Self { .get(entry.id as usize)
seed: entry.seed, .cloned()
name: "iouring".to_string(), .unwrap_or(&Self {
image_url: "profile.avif".to_string(), id: u8::MAX,
about: "sup".to_string(), image_url: "",
keywords: vec![ name: "not found",
"meow".to_string(), keywords: [].as_slice(),
"mrrp".to_string(), about: "",
"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 { impl BlogEntry for Post {
fn from_entry(entry: &mut Entry) -> Self { fn from_entry(entry: &mut Entry) -> Self {
return Self { *Self::POSTS
meta: PostMeta::from_entry(entry), .get(entry.id as usize)
content: vec![PostPart::from_entry(entry)] .cloned()
} .unwrap_or(&Self {
} id: u8::MAX,
} authors: &[u8::MAX],
title: "not found",
#[derive(Clone, Debug, Eq, PartialEq)] utcdate: "1970-01-01",
pub enum PostPart { content: "",
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 struct Entry {
pub seed: u8 pub id: u8,
} }
impl Entry { impl Entry {
pub fn from_seed(seed: u8) -> Self { pub fn from_id(seed: u8) -> Self {
Self { seed } 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}
} }
} }
pub trait BlogEntry: Sized { example.com {
fn from_entry(entry: &mut Entry) -> Self; import iocaine {
fn from_seed(seed: u8) -> Self { handler {
Self::from_entry(&mut Entry::from_seed(seed)) 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.
"#,
},
];
}

View File

@@ -1,90 +1,54 @@
use yew::prelude::*; use yew::prelude::*;
use yew_router::prelude::*;
use crate::pages::blog::content::Author;
use crate::pages::blog::content::BlogEntry; use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Post;
use crate::pages::blog::entrycard::EntryCard; use crate::pages::blog::entrycard::EntryCard;
use crate::pages::blog::navigation::{PageQuery, Pagination};
use crate::Route;
const ITEMS_PER_PAGE: u8 = 10; pub struct Entries {}
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 { impl Component for Entries {
type Message = Msg; type Message = ();
type Properties = (); type Properties = ();
fn create(ctx: &Context<Self>) -> Self { fn create(_ctx: &Context<Self>) -> Self {
let link = ctx.link().clone(); Self {}
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 { fn view(&self, ctx: &Context<Self>) -> Html {
let page = self.page;
html! { html! {
<div class="section container"> <div>
<h1 class="title">{ "entries" }</h1> <h1 style="margin: unset !important;">{ "entries" }</h1>
<h2 class="subtitle">{ "here are some things i yapped about" }</h2> <h2 style="
margin: unset !important;
margin-bottom: 1rem !important;
">{ "here are some things i yapped about" }</h2>
{ self.view_posts(ctx) } { self.view_posts(ctx) }
<Pagination
{page}
total_pages={TOTAL_PAGES}
route_to_page={Route::Entries}
/>
</div> </div>
} }
} }
} }
impl Entries { impl Entries {
fn view_posts(&self, _ctx: &Context<Self>) -> Html { fn view_posts(&self, _ctx: &Context<Self>) -> Html {
let start_seed = (self.page - 1) * ITEMS_PER_PAGE; let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
let cards: Vec<_> = (0..ITEMS_PER_PAGE) .filter(|&id_offset| {
.filter(|&seed_offset| Author::from_seed(seed_offset).seed != u8::MAX) Post::from_id(id_offset as u8)
.map(|seed_offset| { .authors
.iter()
.all(|id| *id != u8::MAX)
})
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
.collect();
html! { html! {
<li class="list-item mb-5"> <div style="
<EntryCard seed={start_seed + seed_offset} /> display: flex !important;
</li> flex-direction: row !important;
} flex-wrap: wrap !important;
}).collect(); justify-content: center !important;
html! { margin: unset !important;
<div class="columns"> margin-bottom: 1rem !important;
<div class="column"> width: 100% !important;
<ul class="list"> ">
{ for cards } { for cards }
</ul>
</div>
</div> </div>
} }
} }

View File

@@ -1,15 +1,16 @@
use std::rc::Rc; use std::rc::Rc;
use yew::prelude::*; use yew::prelude::*;
use yew_markdown::Markdown;
use yew_router::prelude::*; use yew_router::prelude::*;
use crate::Route;
use crate::pages::blog::content; use crate::pages::blog::content;
use crate::pages::blog::content::BlogEntry; use crate::pages::blog::content::{Author, BlogEntry};
use crate::Route;
#[derive(Clone, Debug, Eq, PartialEq, Properties)] #[derive(Clone, Debug, Eq, PartialEq, Properties)]
pub struct Props { pub struct Props {
pub seed: u8, pub id: u8,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
@@ -20,133 +21,57 @@ pub struct PostState {
impl Reducible for PostState { impl Reducible for PostState {
type Action = u8; type Action = u8;
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> { fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
Self { inner: content::Post::from_seed(action.into()), }.into() Self {
inner: content::Post::from_id(action.into()),
}
.into()
} }
} }
#[function_component] #[function_component]
pub fn Entry(props: &Props) -> Html { pub fn Entry(props: &Props) -> Html {
let seed = props.seed; let id = props.id;
let post = use_reducer(|| PostState { let post = use_reducer(|| PostState {
inner: content::Post::from_seed(seed.into()), inner: content::Post::from_id(id.into()),
}); });
{ {
let post_dispatcher = post.dispatcher(); let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| { use_effect_with(id, move |id| {
post_dispatcher.dispatch(*seed); post_dispatcher.dispatch(*id);
|| {} || {}
}); });
} }
let post = &post.inner; 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! { html! {
<> <>
<section class="hero is-medium is-light has-background"> <h1 style="
<img alt="The hero's background" class="hero-background is-transparent" src={post.meta.image_url.clone()} /> color: #F5C2E7FF !important;
<div class="hero-body"> margin: unset !important;
<div class="container"> filter: url(post.bloom.svg#process) !important;
<h1 class="title"> ">{ &*post.title }</h1>
{ &post.meta.title } <div style="flex-direction: column !important;">
</h1> <h2 style="margin: unset !important;">
<h2 class="subtitle"> { "written by " } {
{ "by " } post.authors.iter().map(|id| {
<Link<Route> classes={classes!("has-text-weight-semibold")} to={Route::Author { id: post.meta.author.seed }}> let author = Author::from_id(*id);
{ &post.meta.author.name } html! {
<Link<Route> to={crate::Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>> </Link<Route>>
}
}).intersperse(html! { ", " }).collect::<Html>()
} { format!(" on {}", &post.utcdate) }
</h2> </h2>
<div class="tags">
{ for keywords }
</div>
</div>
</div>
</section>
<div class="section container">
{ view_content }
</div> </div>
<Markdown
src={post.content}
hard_line_breaks={true}
send_debug_info={Callback::noop()}
/>
</> </>
} }
} }

View File

@@ -1,43 +1,44 @@
use std::rc::Rc; use std::rc::Rc;
use yew::prelude::*; use yew::prelude::*;
use yew_router::components::Link; use yew_router::components::Link;
use crate::pages::blog::content::BlogEntry; use crate::pages::blog::content::{Author, BlogEntry, Post};
use crate::pages::blog::content::PostMeta;
use crate::Route; use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)] #[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsEntryCard { pub struct PropsEntryCard {
pub seed: u8, pub id: u8,
} }
#[derive(PartialEq, Eq, Debug)] #[derive(PartialEq, Eq, Debug)]
pub struct PostMetaState { pub struct PostState {
inner: PostMeta, inner: Post,
} }
impl Reducible for PostMetaState { impl Reducible for PostState {
type Action = u8; type Action = u8;
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> { fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
Self { inner: PostMeta::from_seed(action.into()), }.into() Self {
inner: Post::from_id(action.into()),
}
.into()
} }
} }
#[function_component] #[function_component]
pub fn EntryCard(props: &PropsEntryCard) -> Html { pub fn EntryCard(props: &PropsEntryCard) -> Html {
let seed = props.seed; let id = props.id;
let post = use_reducer_eq(|| PostMetaState { let post = use_reducer_eq(|| PostState {
inner: PostMeta::from_seed(seed.into()), inner: Post::from_id(id.into()),
}); });
{ {
let post_dispatcher = post.dispatcher(); let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| { use_effect_with(id, move |id| {
post_dispatcher.dispatch(*seed); post_dispatcher.dispatch(*id);
|| {} || {}
}); });
@@ -46,22 +47,51 @@ pub fn EntryCard(props: &PropsEntryCard) -> Html {
let post = &post.inner; let post = &post.inner;
html! { html! {
<div class="card" style="height: 300px !important; max-height: 300px !important;"> <div style="
<img loading="eager" class="card-image image is-2by1" src={post.image_url.clone()} alt="blog image" style=" display: flex !important;
object-fit: cover !important; flex-direction: column !important;
width: 100% !important; flex-wrap: wrap !important;
max-width: 100% !important; background-color: #ECBEE130 !important;
height: 200px !important; border-radius: 0.5rem !important;
max-height: 200px !important; width: fit-content !important;
filter: blur(6px) !important; height: fit-content !important;
" /> text-align: left !important;
<div class="card-content" style="height: fit-content !important; width: fit-content !important;"> margin: 1rem 1rem 0 0 !important;
<Link<Route> classes={classes!("title", "is-block")} to={Route::Entry { id: post.seed }}> ">
{ &post.title } <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>> </Link<Route>>
<Link<Route> classes={classes!("subtitle", "is-block")} to={Route::Author { id: post.author.seed }}> <h3 style="
{ &post.author.name } 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>> </Link<Route>>
}
}).intersperse(html! { ", " }).collect::<Html>()
}
</h3>
</div> </div>
</div> </div>
} }

View File

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

View File

@@ -1,213 +0,0 @@
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>
}
}

138
src/pages/findme.rs Normal file
View 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>
}
}
}

View File

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

View File

@@ -11,16 +11,21 @@ impl Component for PageNotFound {
} }
fn view(&self, _ctx: &Context<Self>) -> Html { fn view(&self, _ctx: &Context<Self>) -> Html {
// TODO: ... | personalize
html! { html! {
<section class="hero is-danger is-bold is-large"> <div style="
<div class="hero-body"> display: flex !important;
<div class="container"> text-align: center !important;
<h1 class="title">{ "Page not found" }</h1> flex-direction: row !important;
<h2 class="subtitle">{ "Page page does not seem to exist" }</h2> 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> </div>
</div>
</section>
} }
} }
} }

View File

@@ -1,79 +0,0 @@
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>
</>
}
}
}

View 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",
},
];
}

View File

@@ -0,0 +1,3 @@
pub mod content;
pub mod projectcard;
pub mod projects;

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

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