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/
target/
Cargo.lock
bulma.min.css
static/

View File

@@ -19,15 +19,19 @@ log = { version = "0.4" }
wasm-logger = { version = "0.2" }
serde = { version = "1.0" }
wasm-bindgen-futures = "0.4.50"
serde_json = "1.0.143"
lsp-types = "0.97"
chrono = "0.4"
yew-markdown = { git = "https://git.celesteflare.cc/i0uring/page_md.git", rev = "1e9840" }
yew-hooks = "0.3"
[patch.crates-io]
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
"csr",
], optional = false }
[dependencies.web-sys]
version = "0.3"
features = [
'HtmlCanvasElement',
'WebGlBuffer',
'WebGlProgram',
'WebGlRenderingContext',
'WebGl2RenderingContext',
'WebGlShader',
'WebGlUniformLocation',
]
features = ["HtmlCanvasElement", "WebGlBuffer", "WebGlProgram", "WebGlRenderingContext", "WebGl2RenderingContext", "WebGlShader", "WebGlUniformLocation", "Navigator", "Clipboard"]

View File

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

View File

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

View File

@@ -7,9 +7,7 @@ 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,12 +1,17 @@
@charset "UTF-8";
@font-face {
font-family: freemono;
src: url("freemono.ttf");
}
@font-face {
font-family: monocraft;
src: url("monocraft.ttf");
}
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-width: thin !important;
scroll-behavior: smooth !important;
@@ -27,49 +32,18 @@ html * {
.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;
background-color: white, 0.213125 !important;
border-color: white !important;
}
a.navbar-item,
@@ -78,14 +52,30 @@ a.navbar-item:hover {
}
::-moz-selection {
background: hsla(232, 97%, 85%, 0.4125) !important;
background: #F5C2E740 !important;
}
::selection {
background: hsla(232, 97%, 85%, 0.4125) !important;
background: #F5C2E740 !important;
}
a {
color: #F5C2E7FF !important;
text-decoration: none !important;
}
a:hover {
text-decoration: underline !important;
filter: url("post.bloom.svg#process") !important;
}
button {
background-color: transparent !important;
border-color: transparent !important;
color: white !important;
}
button:hover {
text-decoration: underline !important;
filter: url("post.bloom.svg#process") !important;
}

BIN
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 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;
@@ -19,48 +17,42 @@ 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));
return fract(sin(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);
return mix(rand(i), rand(i + 1.0), f * f * (3.0 - 2.0 * f));
}
mat4 viewMatrix(vec3 dir, vec3 up) {
vec3 f = normalize(dir);
vec3 s = normalize(cross(f, up));
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) {
vec3 v = cross(z, d);
float c = dot(z, d);
float k = 1.0 / (1.0 + c);
float k_vx = v.x * k;
float k_vy = v.y * k;
float k_vz = v.z * k;
return mat3(
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
k_vx * v.x + c, k_vx * v.y - v.z, k_vx * v.z + v.y,
k_vy * v.x + v.z, k_vy * v.y + c, k_vy * v.z - v.x,
k_vz * v.x - v.y, k_vz * v.y + v.x, k_vz * v.z + c
);
}
@@ -74,48 +66,10 @@ vec3 calcRay(vec2 uv, float fov, float aspect) {
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) {
const float factor = 0.57735027;
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) {
@@ -145,10 +99,6 @@ float artifactDist(vec3 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)));
@@ -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;
}
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));
float w = smoothstep(1.0, 0.0, -uv.y * (scale * 0.01));
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 *= 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) {
vec2 uv = fragCoord * (vec2(1.0) / resolution.xy);
vec2 uv = fragCoord / resolution.xy;
float s = sin(time);
float c = cos(time);
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
@@ -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);
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));
vec2 uvSnow = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
fragColor += vec4(vec3(snow(uvSnow, 4.0)), 0.5) * 0.7;
}
}

View File

@@ -1,20 +1,24 @@
#![feature(iter_intersperse)]
extern crate core;
use js_sys::Float32Array;
use log::{info, warn};
use std::cell::RefCell;
use std::sync::Arc;
use wasm_bindgen::prelude::*;
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_router::prelude::*;
mod pages;
use crate::pages::about::About;
use crate::pages::blog::author::Author;
use crate::pages::blog::authors::Authors;
use crate::pages::blog::entries::Entries;
use crate::pages::blog::entry::Entry;
use crate::pages::blog::authors::Authors;
use crate::pages::blog::author::Author;
use crate::pages::projects::Projects;
use crate::pages::findme::FindMe;
use crate::pages::projects::projects::Projects;
use pages::not_found::PageNotFound;
#[derive(Routable, PartialEq, Eq, Clone, Debug)]
@@ -34,6 +38,9 @@ pub enum Route {
#[at("/projects")]
Projects,
#[at("/findme")]
FindMe,
#[not_found]
#[at("/404")]
NotFound,
@@ -41,209 +48,274 @@ pub enum Route {
pub struct App {
node_ref: NodeRef,
navbar_active: bool,
}
pub enum Msg {
ToggleNavbar,
}
impl Component for App {
type Message = Msg;
type Message = ();
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
}
Self {
node_ref: NodeRef::default(),
}
}
fn view(&self, _ctx: &Context<Self>) -> Html {
html! {
<>
<canvas style="
<canvas width="100%" height="100%" style="
background-color: black !important;
position: fixed !important;
left: 0 !important;
top: 0 !important;
width: 100vw !important;
height: 100vh !important;
z-index: 0 !important;
" ref={self.node_ref.clone()}/>
<div style="
" ref={self.node_ref.clone()} />
<BrowserRouter>
<header style="
z-index: 2 !important;
display: flex !important;
color: white !important;
flex-direction: row !important;
align-items: center !important;
justify-content: center !important;
max-width: 100vw !important;
min-height: 20px !important;
height: 20px !important;
max-height: 20px !important;
filter: url(post.bloom.svg) !important;
">
<h3>{r"»[ "}</h3>
<h3><Link<Route> to={Route::About}>{r"about"}</Link<Route>></h3>
<h3>{r"|"}</h3>
<h3><Link<Route> to={Route::Entries}>{r"blog"}</Link<Route>></h3>
<h3>{r"|"}</h3>
<h3><Link<Route> to={Route::FindMe}>{r"findme"}</Link<Route>></h3>
<h3>{r"|"}</h3>
<h3><Link<Route> to={Route::Projects}>{r"projects"}</Link<Route>></h3>
<h3>{r" ]«"}</h3>
</header>
<main style="
z-index: 1 !important;
background-color: transparent !important;
color: white !important;
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
height: 100vh !important;
min-width: fit-content !important;
width: calc(100vw - 18 px) !important;
flex-wrap: nowrap !important;
justify-content: space-between !important;
align-items: center !important;
justify-content: center !important;
align-self: center !important;
margin-left: auto !important;
margin-right: auto !important;
overflow-x: hidden !important;
overflow-y: auto !important;
height: fit-content !important;
max-height: calc(100vh - 42px) !important;
">
<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;
/*
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;
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>
overflow-x: hidden !important;
overflow-y: auto !important;
min-width: 70vw !important;
width: 70vh !important;
max-width: calc(100vw - 18px) !important;
height: 100% !important;
margin-top: auto !important;
margin-bottom: auto !important;
scrollbar-width: thin !important;
scrollbar-color: transparent transparent !important;
"><Switch<Route> render={switch} />
{self.view_buttons()}
</div>
</main>
</BrowserRouter>
</>
}
}
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
if first_render { self.view_canvas(); }
if first_render {
self.view_canvas();
}
}
}
fn switch(routes: Route) -> Html {
match routes {
Route::About => { html! { <About /> } }
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::Entries => {
html! { <Entries /> }
}
Route::Entry { id } => {
html! { <Entry id={id as u8} /> }
}
Route::Authors => {
html! { <Authors /> }
}
Route::Author { id } => {
html! { <Author id={id as u8} /> }
}
Route::Projects => { html! { <Projects /> } }
Route::NotFound => { html! { <PageNotFound /> } }
Route::FindMe => {
html! { <FindMe /> }
}
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 { "" };
fn view_buttons(&self) -> Html {
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 style="
z-index: 1 !important;
display: flex !important;
flex-wrap: wrap !important;
flex-direction: row !important;
justify-content: center !important;
min-width: 70vw !important;
width: 70vh !important;
max-width: calc(100vw - 18px) !important;
height: fit-content !important;
overflow: hidden !important;
margin-right: auto !important;
margin-left: auto !important;
">
<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>
</>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://i0ur.ing/"><img loading="eager" alt="iouring" src="iouring.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://webassembly.org/"><img loading="eager" alt="wasm" src="wasm.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" src="servfail.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" src="dataforest.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" src="fedora.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://artixlinux.org/"><img loading="eager" alt="artix" src="artix.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://nixos.org/"><img loading="eager" alt="nixos" src="nixos.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://chimera-linux.org/"><img loading="eager" alt="void" src="chimera.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://voidlinux.org/"><img loading="eager" alt="void" src="void.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" src="rust.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" src="csharp.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" src="kotlin.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
margin-right: 0.2rem !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
" href="https://www.java.com/"><img loading="eager" alt="java" src="java.png" /></a>
<a style="
width: 88px !important;
height: 31px !important;
image-rendering: pixelated !important;
image-resolution: from-image !important;
image-orientation: from-image !important;
"><img loading="eager" alt="aqueer" src="aqueer.png" /></a>
</div>
}
}
@@ -251,27 +323,37 @@ impl App {
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();
.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);
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);
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));
@@ -307,7 +389,11 @@ impl App {
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() {
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));
@@ -328,7 +414,9 @@ impl App {
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 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");
@@ -336,11 +424,18 @@ impl App {
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);
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");
window()
.unwrap()
.request_animation_frame(f.as_ref().unchecked_ref())
.expect("should register `requestAnimationFrame` OK");
}
let cb = Arc::new(RefCell::new(None));
@@ -355,13 +450,21 @@ impl App {
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.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);
gl.draw_elements_with_i32(
GL2::TRIANGLES,
element_indices.len() as i32,
GL2::UNSIGNED_SHORT,
0,
);
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 {
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>
<div>
<img alt="insert pfp here" style="
float: left !important;
margin-right: 10px !important;
border-radius: 0.5rem !important;
" loading="eager" width="96px" height="96px" src="profile.avif" />
<div style="
display: flex !important;
flex-direction: column !important;
max-height: 96px !important;
">
<p style="
display: flex !important;
color: transparent !important;
flex-direction: row !important;
align-items: center !important;
width: fit-content !important;
background-clip: text !important;
background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;
-webkit-background-clip: text !important;
margin: unset !important;
filter: url(post.bloom.svg#process) !important;
">
<img loading="eager" alt="ket" src="oneko.gif" />{r" lia · they/she · transfemby"}</p>
<p style="margin: unset !important;">
<p style="margin: unset !important;">{r"certified catgirl™"}</p>
<p style="margin: unset !important;">{r"software engineer"}</p>
<p style="margin: unset !important;">{r"professional yapper"}</p>
<p style="margin: unset !important;">{r"neurospicy and disabled"}</p>
</p>
</div>
<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>
<p style="
font-weight: bold !important;
font-style: italic !important;
font-size: xx-large !important;
">{r">intro_"}<br /><p style="
font-weight: normal !important;
font-style: normal !important;
font-size: medium !important;
">{r#"
name's lia or lucy. we are creating.
trying to provide a safe space in the internet.
our goal's to make this world a better ~ less trashy ~ place.
may sound naive as fuck but fuck it we ball.
audhd (certified) and probably bpd or ptsd. i'm not a psychologist tho.
interact with caution, may bite.
political view's prolly very obvious. should be enough to mention i'm social.
figure it yourself. use your brain.
"#}</p>{r">hobbies_"}<br /><p style="
font-weight: normal !important;
font-style: normal !important;
font-size: medium !important;
">{r#"
watching anime, designing something kewl, playing games, starting a new project or just yapping with friends, nearly everything's included.
i especially enjoy cooking or even baking if enough spoons are to avail.
which anime? has to be very specific, i'm really picky and judgy regarding them.
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
has to be something i can somewhat imagine in my fucky-wucky head.
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
projects? you can find public ones in my projects tab.
"#}/*</p>{r"hobbies"}<br /><p style="
font-weight: normal !important;
font-style: normal !important;
font-size: medium !important;
">{r#"
watching anime, designing something kewl, playing games, starting a new project or just yapping/meeting up with friends, nearly everything's included.
i especially enjoy cooking or even baking if enough spoons are to avail.
which anime? has to be very specific, i'm really picky and judgy regarding them.
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
has to be something i can somewhat imagine in my fucky-wucky head.
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
projects? you can find public ones in my projects tab.
"#}*/</p>
</p>
</div>
</>
}

View File

@@ -1,10 +1,12 @@
use yew::prelude::*;
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)]
pub struct Props {
pub seed: u8,
pub id: u8,
}
pub struct Author {
@@ -15,68 +17,55 @@ impl Component for Author {
type Properties = Props;
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 {
self.author = content::Author::from_seed(ctx.props().seed);
self.author = content::Author::from_id(ctx.props().id);
true
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
let id = author.id;
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
.filter(|&id_offset| {
Post::from_id(id_offset as u8)
.authors
.iter()
.any(|id1| *id1 == id)
})
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
.collect();
html! {
<div 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 style="
display: flex !important;
width: 100% !important;
flex-direction: column !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-bottom: 2rem !important;
">
<h2 style="text-align: center !important;">{ "about me" }</h2>
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-bottom: 1rem !important;
"><AuthorCard {id} /></div>
<h2 style="text-align: center !important;">{ "my posts" }</h2>
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
">{ for cards }</div>
</div>
}
}
}
/*
<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_router::components::Link;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Author;
use crate::pages::blog::content::BlogEntry;
use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsAuthorCard {
pub seed: u8,
pub id: u8,
}
pub struct AuthorCard {
@@ -20,42 +20,53 @@ impl Component for AuthorCard {
type Properties = PropsAuthorCard;
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 {
self.author = Author::from_seed(ctx.props().seed);
self.author = Author::from_id(ctx.props().id);
true
}
fn view(&self, _ctx: &Context<Self>) -> Html {
let Self { author } = self;
let keywords = author.keywords.iter().map(|keyword| html! {
<p style="margin: unset !important;">{*keyword}</p>
}).collect::<Html>();
html! {
<>
<div 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 style="
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
background-color: #ECBEE130 !important;
border-radius: 0.5rem !important;
width: fit-content !important;
height: fit-content !important;
text-align: left !important;
margin: 1rem 1rem 0 0 !important;
">
<img style="
float: left !important;
margin-right: 0.3rem !important;
border-radius: 0.5rem !important;
" alt="insert pfp here" width="96" height="96" src={author.image_url} />
<div style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
margin-top: 0.3rem !important;
margin-right: 0.3rem !important;
">
<h3 style="margin: unset !important;">
<Link<Route> to={Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
</h3>
{ keywords }
</div>
<footer class="card-footer">
<Link<Route> classes={classes!("card-footer-item")} to={Route::Author { id: author.seed }}>
{ "Profile" }
</Link<Route>>
</footer>
</div>
</>
}
}
}

View File

@@ -1,31 +1,31 @@
use yew::prelude::*;
use crate::pages::blog::authorcard::AuthorCard;
use crate::pages::blog::content::{Author, BlogEntry};
/*pub enum Msg {
NextAuthors,
}*/
pub struct Authors {
seeds: Vec<u8>,
}
pub struct Authors;
impl Component for Authors {
type Message = (); //Msg;
type Message = ();
type Properties = ();
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 {
html! {
{ for self.seeds.iter().map(|&seed| { html! {
<AuthorCard {seed} />
} }) }
<div style="
display: flex !important;
width: 100% !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin-top: 2rem !important;
margin-bottom: 2rem !important;
">{ for Author::AUTHORS.map(|author| {
let id = author.id;
html! { <AuthorCard {id} /> }
}) }</div>
}
}
}

View File

@@ -1,144 +1,299 @@
#[derive(Clone, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Author {
pub seed: u8,
pub name: String,
pub keywords: Vec<String>,
pub image_url: String,
pub about: String
pub id: u8,
pub image_url: &'static str,
pub name: &'static str,
pub keywords: &'static [&'static str],
pub about: &'static str,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Post {
pub id: u8,
pub authors: &'static [u8],
pub title: &'static str,
pub utcdate: &'static str,
pub content: &'static str,
}
impl BlogEntry for Author {
fn from_entry(entry: &mut Entry) -> Self {
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![]
}
}
*Self::AUTHORS
.get(entry.id as usize)
.cloned()
.unwrap_or(&Self {
id: u8::MAX,
image_url: "",
name: "not found",
keywords: [].as_slice(),
about: "",
})
}
}
#[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() }
*Self::POSTS
.get(entry.id as usize)
.cloned()
.unwrap_or(&Self {
id: u8::MAX,
authors: &[u8::MAX],
title: "not found",
utcdate: "1970-01-01",
content: "",
})
}
}
pub struct Entry {
pub seed: u8
pub id: u8,
}
impl Entry {
pub fn from_seed(seed: u8) -> Self {
Self { seed }
pub fn from_id(seed: u8) -> Self {
Self { id: seed }
}
}
pub trait BlogEntry: Sized {
fn from_entry(entry: &mut Entry) -> Self;
fn from_id(id: u8) -> Self {
Self::from_entry(&mut Entry::from_id(id))
}
const AUTHORS: [&'static Author; 2] = [
&Author {
id: 0,
image_url: "profile.avif",
name: "lia",
keywords: &[
"certified catgirl™",
"software engineer",
"professional yapper",
"neurospicy and disabled",
],
about: "",
},
&Author {
id: 1,
image_url: "",
name: "mreowww",
keywords: &["meow", "mrrp", "mew"],
about: "",
},
];
const POSTS: [&'static Post; 2] = [
&Post {
id: 0,
authors: &[0],
title: "haj, world!",
utcdate: "2025-08-24",
content: r#"
### my first blogpost
haj! and welcome to my first blogpost.
this blogpost won't have that much content.
fun fact: this post becomes html from markdown :3
pretty cool, ain't it? we think so too.
it's for including images, formatted text, code and content much easier.
i also added a too overcomplicated blog backend that allows authors and posts by u8::MAX, cause why not.
won't post that much either way, so why bother with higher values.
nonetheless... have a great day, evening, whatever daytime it's for you rn and stay safe. ♡
you may take a look at my other blogposts. this one was basically practially just a test.
"#,
},
&Post {
id: 1,
authors: &[0],
title: "sweet little poison",
utcdate: "2025-08-25",
content: r#"
### > setting up iocaine with docker and caddy. ~
sup, today we're going to give ya a crash course on how to homebrew poison for ai. ^.^
we'll be utilizing docker for containerization and caddy as a reverse-proxy.
#### > preparing docker and files ~
first, we're going to create a new external network and recreate our file struct.
in this example we're going to name it "caddy". very creative, i know.
we achieve this by running following pile of commands:
```sh
docker network create caddy && \
mkdir -p ./caddy/pages/example.com ./iocaine ./socks && \
touch ./docker-compose.yml ./caddy/proxy ./iocaine/config.toml && \
touch ./caddy/pages/example.com/index.html && \
printf '<html><body><h1>hellow body consisting of blood and flesh</h1></body></html>' \
> ./caddy/pages/example.com/index.html
```
after running that we should be set and ready for the next few steps.
if you're curious, this is how it should look like right now:
```sh
Documents/example » tree .
.
├── caddy
│   ├── pages
│   │   └── example.com
│   │   └── index.html
│   └── proxy
├── docker-compose.yml
├── iocaine
│   └── config.toml
└── socks
```
#### > downloading data and configuring iocaine ~
next things next: we're going to download a robots.json, some markov-chain-stuffies and a words.txt.
for now, we will be using the default stuff from the official docs cause why not:
```sh
curl --proto '=https' --tlsv1.3 \
-L https://archive.org/download/GeorgeOrwells1984/1984_djvu.txt \
-o ./iocaine/1984.txt && \
curl --proto '=https' --tlsv1.3 \
-L https://archive.org/download/ost-english-brave_new_world_aldous_huxley/Brave_New_World_Aldous_Huxley_djvu.txt \
-o ./iocaine/brave-new-world.txt && \
curl --proto '=https' --tlsv1.2 \
-L https://git.savannah.gnu.org/cgit/miscfiles.git/plain/web2 \
-o ./iocaine/words.txt && \
curl --proto '=https' --tlsv1.3 \
-L https://github.com/ai-robots-txt/ai.robots.txt/raw/refs/heads/main/robots.json \
-o ./iocaine/robots.json
```
most importantly, we have to scrape nam-shub-of-enki from gergely's git.
here's a way to curl and extract it automatically in the correct folder:
```sh
curl --proto '=https' --tlsv1.3 \
-L https://git.madhouse-project.org/api/packages/iocaine/generic/nam-shub-of-enki/20250711.0/nam-shub-of-enki-20250711.0.tar.zst \
-o ./iocaine/nam-shub-of-enki.tar.zst && \
sudo tar -xvf ./iocaine/nam-shub-of-enki.tar.zst -C ./iocaine
```
now, we're going to prepare our iocaine/config.toml like this:
```sh
cat > ./iocaine/config.toml <<'YAML'
[server]
bind = "/run/iocaine/waow.socket"
unix_listen_access = "everybody"
[server.control]
bind = "/run/iocaine/listen.socket"
unix_listen_access = "owner"
[server.request-handler]
path = "/data"
language = "roto"
[sources]
words = "/data/words.txt"
markov = [ "/data/1984.txt", "/data/brave-new-world.txt" ]
[metrics]
enable = false
YAML
```
#### > cooking up our docker compose file for deployment ~
i have fucked around with docker a little while to make this somehow work.
here's a command which will insert needed content into our docker-compose.yml:
```sh
cat > ./docker-compose.yml <<'YAML'
services:
caddy:
image: caddy:alpine
container_name: proxy.caddy
hostname: proxy.caddy
restart: unless-stopped
cap_add:
- NET_ADMIN
ports:
- "80:80"
- "443:443"
- "443:443/udp"
networks:
- caddy
volumes:
- ./socks:/run/iocaine:ro
- ./caddy/pages:/var/www/html
- ./caddy/data:/data
- ./caddy/config:/config
- ./caddy/proxy:/etc/caddy/Caddyfile:ro
depends_on:
- iocaine
iocaine:
image: git.madhouse-project.org/iocaine/iocaine:2
container_name: proxy.iocaine
hostname: proxy.iocaine
restart: unless-stopped
ports:
- "127.0.0.1:42069:42069"
networks:
- caddy
volumes:
- ./socks:/run/iocaine:rw
- ./iocaine:/data
environment:
- IOCAINE__SERVER__BIND="/run/iocaine/waow.socket"
- IOCAINE__SERVER__UNIX_LISTEN_ACCESS="everybody"
- IOCAINE__SERVER__REQUEST_HANDLER__PATH="/data"
- IOCAINE__SERVER__REQUEST_HANDLER__LANGUAGE="roto"
- IOCAINE__SERVER__CONTROL__BIND="/run/iocaine/listen.socket"
- IOCAINE__SERVER__CONTROL__UNIX_LISTEN_ACCESS="owner"
- IOCAINE__SOURCES__WORDS="/data/words.txt"
- IOCAINE__SOURCES__MARKOV=["/data/1984.txt", "/data/brave-new-world.txt"]
- IOCAINE__METRICS__ENABLE=false
- NSOE__AI_ROBOTS_TXT_PATH=/data/robots.json
command: --config-file /data/config.toml start
networks:
caddy:
external: true
YAML
```
#### > finishing up with caddy and deploying ~
our caddyfile needs some love to work.
here's what i ripped from the official docs:
```sh
cat > ./iocaine/proxy <<'TXT'
(iocaine) {
@read method GET HEAD
@not-read not {
method GET HEAD
}
reverse_proxy @read unix//run/iocaine/waow.socket {
#reverse_proxy @read proxy.iocaine:42069 {
@fallback status 421
handle_response @fallback {
{blocks.handler}
}
}
handle @not-read {
{blocks.default}
}
}
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))
example.com {
import iocaine {
handler {
reverse_proxy http://example:8080
}
default {
# this is the behaviour if neither a GET nor HEAD request comes in
reverse_proxy http://example:8080
}
}
}
TXT
```
now the last thing that's left is a plain and simple:
`docker compose up -d`
and we should be good to go!
i hope this post made it easier/helped you with setting up iocaine on your own server. ♡
it's good to have this set up just to fuck around with fucking parisitic generative ai stealing your things.
gatekeep your stuff from big corpos that are trying to make their business from your experience and skills.
"#,
},
];
}

View File

@@ -1,91 +1,55 @@
use yew::prelude::*;
use yew_router::prelude::*;
use crate::pages::blog::content::Author;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::Post;
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)
}
pub struct Entries {}
impl Component for Entries {
type Message = Msg;
type Message = ();
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 create(_ctx: &Context<Self>) -> Self {
Self {}
}
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>
<div>
<h1 style="margin: unset !important;">{ "entries" }</h1>
<h2 style="
margin: unset !important;
margin-bottom: 1rem !important;
">{ "here are some things i yapped about" }</h2>
{ self.view_posts(ctx) }
<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();
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
.filter(|&id_offset| {
Post::from_id(id_offset as u8)
.authors
.iter()
.all(|id| *id != u8::MAX)
})
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
.collect();
html! {
<div class="columns">
<div class="column">
<ul class="list">
{ for cards }
</ul>
</div>
<div style="
display: flex !important;
flex-direction: row !important;
flex-wrap: wrap !important;
justify-content: center !important;
margin: unset !important;
margin-bottom: 1rem !important;
width: 100% !important;
">
{ for cards }
</div>
}
}
}
}

View File

@@ -1,15 +1,16 @@
use std::rc::Rc;
use yew::prelude::*;
use yew_markdown::Markdown;
use yew_router::prelude::*;
use crate::Route;
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)]
pub struct Props {
pub seed: u8,
pub id: u8,
}
#[derive(PartialEq, Eq, Debug)]
@@ -20,133 +21,57 @@ pub struct PostState {
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()
Self {
inner: content::Post::from_id(action.into()),
}
.into()
}
}
#[function_component]
pub fn Entry(props: &Props) -> Html {
let seed = props.seed;
let id = props.id;
let post = use_reducer(|| PostState {
inner: content::Post::from_seed(seed.into()),
inner: content::Post::from_id(id.into()),
});
{
let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
use_effect_with(id, move |id| {
post_dispatcher.dispatch(*id);
|| {}
});
}
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 }
<h1 style="
color: #F5C2E7FF !important;
margin: unset !important;
filter: url(post.bloom.svg#process) !important;
">{ &*post.title }</h1>
<div style="flex-direction: column !important;">
<h2 style="margin: unset !important;">
{ "written by " } {
post.authors.iter().map(|id| {
let author = Author::from_id(*id);
html! {
<Link<Route> to={crate::Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
</h2>
<div class="tags">
{ for keywords }
</div>
</div>
</div>
</section>
<div class="section container">
{ view_content }
}
}).intersperse(html! { ", " }).collect::<Html>()
} { format!(" on {}", &post.utcdate) }
</h2>
</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 yew::prelude::*;
use yew_router::components::Link;
use crate::pages::blog::content::BlogEntry;
use crate::pages::blog::content::PostMeta;
use crate::pages::blog::content::{Author, BlogEntry, Post};
use crate::Route;
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
pub struct PropsEntryCard {
pub seed: u8,
pub id: u8,
}
#[derive(PartialEq, Eq, Debug)]
pub struct PostMetaState {
inner: PostMeta,
pub struct PostState {
inner: Post,
}
impl Reducible for PostMetaState {
impl Reducible for PostState {
type Action = u8;
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]
pub fn EntryCard(props: &PropsEntryCard) -> Html {
let seed = props.seed;
let id = props.id;
let post = use_reducer_eq(|| PostMetaState {
inner: PostMeta::from_seed(seed.into()),
let post = use_reducer_eq(|| PostState {
inner: Post::from_id(id.into()),
});
{
let post_dispatcher = post.dispatcher();
use_effect_with(seed, move |seed| {
post_dispatcher.dispatch(*seed);
use_effect_with(id, move |id| {
post_dispatcher.dispatch(*id);
|| {}
});
@@ -46,22 +47,51 @@ pub fn EntryCard(props: &PropsEntryCard) -> Html {
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 style="
display: flex !important;
flex-direction: column !important;
flex-wrap: wrap !important;
background-color: #ECBEE130 !important;
border-radius: 0.5rem !important;
width: fit-content !important;
height: fit-content !important;
text-align: left !important;
margin: 1rem 1rem 0 0 !important;
">
<Link<Route> to={Route::Entry { id: post.id }}>
<h2 style="
margin: unset !important;
margin-top: 0.3rem !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
">{ &*post.title }</h2>
</Link<Route>>
<h3 style="
margin: unset !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
">
{ format!("published {}", post.utcdate) }
</h3>
<div style="flex-direction: column !important;">
<h3 style="
margin: unset !important;
margin-left: 0.3rem !important;
margin-right: 0.3rem !important;
margin-bottom: 0.3rem !important;
">
{ "written by " }
{
post.authors.iter().map(|id| {
let author = Author::from_id(*id);
html! {
<Link<Route> to={crate::Route::Author { id: author.id }}>
{ &*author.name }
</Link<Route>>
}
}).intersperse(html! { ", " }).collect::<Html>()
}
</h3>
</div>
</div>
}

View File

@@ -1,8 +1,7 @@
pub mod author;
pub mod authorcard;
pub mod authors;
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

@@ -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 blog;
pub mod findme;
pub mod projects;
pub mod not_found;

View File

@@ -11,16 +11,21 @@ impl Component for PageNotFound {
}
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>
<div style="
display: flex !important;
text-align: center !important;
flex-direction: row !important;
align-items: center !important;
flex-wrap: wrap !important;
justify-content: center !important;
">
<img loading="eager" alt="huh" style="
width: 96px !important;
height: 96px !important;
" width="96" height="96" src="404.png" />
<h1>{ "what" }</h1>
</div>
}
}
}

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