Compare commits
29 Commits
e435b62c52
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| 35aa3b076f | |||
| 2e397c5be3 | |||
| f1c471839f | |||
| bfd8c187c6 | |||
| 510897d94b | |||
| 078634b664 | |||
| 6a5176caab | |||
|
|
282c67b1fc | ||
| 74224bd648 | |||
| 8f217ec82b | |||
| 0e1055b1a6 | |||
| 94c56d7405 | |||
|
|
0a203fdb65 | ||
| 3788bf81b5 | |||
| b8325accb2 | |||
| f8241695c9 | |||
| 2e8ecfddd8 | |||
| fd6367eb2d | |||
| fbb76210ef | |||
| 5797baa64e | |||
| ebcc54cc1e | |||
| 9d6f05cace | |||
| 3e4ca7fcf7 | |||
| d68b9915c0 | |||
|
|
c7b262cd49 | ||
|
|
9a9be89650 | ||
|
|
af25035bec | ||
|
|
822725267e | ||
|
|
6428bcb799 |
8
.gitignore
vendored
@@ -1,2 +1,6 @@
|
|||||||
.idea
|
.idea/
|
||||||
simplewebserver-*
|
dist/
|
||||||
|
pkg/
|
||||||
|
target/
|
||||||
|
Cargo.lock
|
||||||
|
static/
|
||||||
|
|||||||
37
Cargo.toml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
[package]
|
||||||
|
name = "web_iouring"
|
||||||
|
version = "0.0.0-develop"
|
||||||
|
authors = ["iouring"]
|
||||||
|
license = "Apache-2.0"
|
||||||
|
edition = "2024"
|
||||||
|
rust-version = "1.91"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
js-sys = "0.3"
|
||||||
|
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
|
||||||
|
"csr",
|
||||||
|
] }
|
||||||
|
yew-router = { git = "https://github.com/yewstack/yew.git", rev = "21f373b" }
|
||||||
|
wasm-bindgen = { version = "0.2" }
|
||||||
|
|
||||||
|
log = { version = "0.4" }
|
||||||
|
wasm-logger = { version = "0.2" }
|
||||||
|
|
||||||
|
serde = { version = "1.0" }
|
||||||
|
wasm-bindgen-futures = "0.4.50"
|
||||||
|
serde_json = "1.0.143"
|
||||||
|
|
||||||
|
lsp-types = "0.97"
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
|
yew-markdown = { git = "https://git.celesteflare.cc/i0uring/page_md.git", rev = "1e9840" }
|
||||||
|
yew-hooks = "0.3"
|
||||||
|
[patch.crates-io]
|
||||||
|
yew = { git = "https://github.com/yewstack/yew.git", rev = "21f373b", features = [
|
||||||
|
"csr",
|
||||||
|
], optional = false }
|
||||||
|
|
||||||
|
[dependencies.web-sys]
|
||||||
|
version = "0.3"
|
||||||
|
features = ["HtmlCanvasElement", "WebGlBuffer", "WebGlProgram", "WebGlRenderingContext", "WebGl2RenderingContext", "WebGlShader", "WebGlUniformLocation", "Navigator", "Clipboard"]
|
||||||
2
Trunk.toml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
[tools]
|
||||||
|
wasm_opt = "version_122"
|
||||||
|
Before Width: | Height: | Size: 91 KiB |
|
Before Width: | Height: | Size: 570 KiB |
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Application ~ Chatty",
|
|
||||||
"link": "https://github.com/lunarydess/Application-Chatty",
|
|
||||||
"image": "1.png",
|
|
||||||
"desc": [
|
|
||||||
"A simple chat-server written in Java 21.",
|
|
||||||
"I am using Netty 4 for Networking.",
|
|
||||||
"It has a modern UI built with Swing and FlatLaf. ♡",
|
|
||||||
"The app is free-to-use but still w.i.p."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.8 KiB |
@@ -1,10 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Discord ~ Ashuramaru",
|
|
||||||
"link": "https://github.com/lunarydess/Discord-Ashuramaru",
|
|
||||||
"image": "2.png",
|
|
||||||
"desc": [
|
|
||||||
"This is a discord bot I wrote in Java.",
|
|
||||||
"It has basic functions and an advanced captcha system.",
|
|
||||||
"Built with JavaCord, picocli and Jackson."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.6 KiB |
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Library ~ TinyEvents",
|
|
||||||
"link": "https://github.com/lunarydess/Library-TinyEvents",
|
|
||||||
"image": "3.png",
|
|
||||||
"desc": [
|
|
||||||
"A event-system written in Java 17.",
|
|
||||||
"It's easy-to-use and blazingly fast.",
|
|
||||||
"Well-performing benchmarks and reliable error-handling.",
|
|
||||||
"Usable in any imaginable infra."
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 29 KiB |
@@ -1,284 +0,0 @@
|
|||||||
#version 100
|
|
||||||
precision lowp 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;
|
|
||||||
|
|
||||||
vec4 vColor;
|
|
||||||
|
|
||||||
vec3 artifactOffset;
|
|
||||||
mat3 artifactRotation;
|
|
||||||
vec3 artifactAxis;
|
|
||||||
vec3 camFwd;
|
|
||||||
vec3 camUp;
|
|
||||||
|
|
||||||
float smootherstep(float edge0, float edge1, float x) {
|
|
||||||
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
||||||
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float rand(float n) {
|
|
||||||
n = fract(n * 43758.5453);
|
|
||||||
n *= n;
|
|
||||||
return fract(n * 43758.5453);
|
|
||||||
}
|
|
||||||
|
|
||||||
float hash(float n) {
|
|
||||||
return fract(abs(fract(n) * 43758.5453));
|
|
||||||
}
|
|
||||||
|
|
||||||
float noise(float x) {
|
|
||||||
float i = floor(x);
|
|
||||||
float f = fract(x);
|
|
||||||
float u = f * f * (3.0 - 2.0 * f);
|
|
||||||
return mix(hash(i), hash(i + 1.0), u);
|
|
||||||
}
|
|
||||||
|
|
||||||
mat4 viewMatrix(vec3 dir, vec3 up) {
|
|
||||||
vec3 f = normalize(dir);
|
|
||||||
vec3 s = normalize(cross(f, up));
|
|
||||||
return mat4(vec4(s, 0.0), vec4(cross(s, f), 0.0), vec4(-f, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
mat3 rotationAlign(vec3 d, vec3 z) {
|
|
||||||
vec3 v = cross(z, d);
|
|
||||||
float c = dot(z, d);
|
|
||||||
float k = 1.0 / (1.0 + c);
|
|
||||||
return mat3(
|
|
||||||
v.x * v.x * k + c,
|
|
||||||
v.y * v.x * k - v.z,
|
|
||||||
v.z * v.x * k + v.y,
|
|
||||||
v.x * v.y * k + v.z,
|
|
||||||
v.y * v.y * k + c,
|
|
||||||
v.z * v.y * k - v.x,
|
|
||||||
v.x * v.z * k - v.y,
|
|
||||||
v.y * v.z * k + v.x,
|
|
||||||
v.z * v.z * k + c
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
|
|
||||||
return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 calcRay(vec2 uv, float fov, float aspect) {
|
|
||||||
uv = uv * 2.0 - 1.0;
|
|
||||||
float d = 1.0 / tan(radians(fov) * 0.5);
|
|
||||||
return normalize(vec3(aspect * uv.x, uv.y, d));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 getWave(vec2 position, vec2 dir, float speed, float frequency, float iTimeshift) {
|
|
||||||
float x = dot(dir, position) * frequency + iTimeshift * speed;
|
|
||||||
float wave = exp(sin(x) - 1.0);
|
|
||||||
float dist = wave * cos(x);
|
|
||||||
return vec2(wave, -dist);
|
|
||||||
}
|
|
||||||
|
|
||||||
float heightmap(vec2 worldPos) {
|
|
||||||
const float scale = 0.06;
|
|
||||||
|
|
||||||
vec2 p = worldPos * scale;
|
|
||||||
vec2 p2 = (artifactOffset.xz - vec2(0.0, 1.0)) * scale;
|
|
||||||
|
|
||||||
float d = (1.0 - smootherstep(0.0, 1.0, clamp(length(p2 - p) * 1.25, 0.0, 1.0))) * 0.87;
|
|
||||||
float angle = 0.0;
|
|
||||||
float freq = 5.0;
|
|
||||||
float speed = 2.0;
|
|
||||||
float weight = 1.9;
|
|
||||||
float wave = 0.0;
|
|
||||||
float waveScale = 0.0;
|
|
||||||
|
|
||||||
vec2 dir;
|
|
||||||
vec2 res;
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
dir = vec2(cos(angle), sin(angle));
|
|
||||||
res = getWave(p, dir, speed, freq, time);
|
|
||||||
p += dir * res.y * weight * 0.05;
|
|
||||||
wave += res.x * weight - d;
|
|
||||||
angle += 12.0;
|
|
||||||
waveScale += weight;
|
|
||||||
weight = mix(weight, 0.0, 0.2);
|
|
||||||
freq *= 1.18;
|
|
||||||
speed *= 1.06;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wave * (1.0 / waveScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
float octahedron(vec3 p, float s) {
|
|
||||||
p = abs(p);
|
|
||||||
return (p.x + p.y + p.z - s) * 0.57735027;
|
|
||||||
}
|
|
||||||
|
|
||||||
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
|
|
||||||
p -= artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
float dist = octahedron(p, 0.8);
|
|
||||||
const float glowDist = 4.8;
|
|
||||||
if (dist < glowDist) {
|
|
||||||
float d = dist + rand(dist) * 1.7;
|
|
||||||
glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
|
||||||
}
|
|
||||||
if (dist < currDist) {
|
|
||||||
currDist = dist;
|
|
||||||
id = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float objects(vec3 p, inout vec3 glowColor, inout int objId) {
|
|
||||||
float dist = CAM_FAR;
|
|
||||||
artifact(p, dist, glowColor, objId);
|
|
||||||
return dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
float artifactDist(vec3 p) {
|
|
||||||
p -= artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
return octahedron(p, 1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
float objectsDist(vec3 p) {
|
|
||||||
return artifactDist(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 objectsNormal(vec3 p, float eps) {
|
|
||||||
vec2 h = vec2(eps, 0);
|
|
||||||
return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 objectsColor(int id, vec3 normal, vec3 ray) {
|
|
||||||
return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
|
|
||||||
id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
|
|
||||||
vec3(1.0, 1.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
|
|
||||||
float dist = 0.0;
|
|
||||||
int id;
|
|
||||||
vec3 rayPos = eye;
|
|
||||||
float depth = CAM_FAR;
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
dist = objects(rayPos, color.rgb, id);
|
|
||||||
depth = distance(rayPos, eye);
|
|
||||||
if (depth > wDepth || dist < 0.01) break;
|
|
||||||
rayPos += ray * dist;
|
|
||||||
}
|
|
||||||
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 waterColor(vec3 ray, vec3 normal, vec3 p) {
|
|
||||||
vec3 color = vec3(0.0);
|
|
||||||
float fogDist = length(p - vec3(0.0, 0.0, -6.0));
|
|
||||||
float dist = 0.0;
|
|
||||||
int objId = 0;
|
|
||||||
vec3 refl = reflect(ray, normal);
|
|
||||||
vec3 rayPos = p + refl * dist;
|
|
||||||
|
|
||||||
if (length(p.xz - artifactOffset.xz) < 8.5 && dot(refl, normalize(artifactOffset - p)) > -0.25) {
|
|
||||||
for (int i = 0; i < 40; i++) {
|
|
||||||
dist = objects(rayPos, color, objId);
|
|
||||||
if (dist < 0.01) {
|
|
||||||
color = objectsColor(objId, objectsNormal(rayPos, 0.001), rayPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rayPos += refl * dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float fresnel = 0.04 + 0.9 * pow(1.0 - max(0.0, dot(-normal, ray)), 7.0);
|
|
||||||
float d = length(artifactOffset - p);
|
|
||||||
const float r = 14.0;
|
|
||||||
float atten = clamp(1.0 - (d * d) / (r * r), 0.0, 1.0);
|
|
||||||
atten *= atten;
|
|
||||||
|
|
||||||
vec3 point = vec3(0.75, 0.55, 0.45) * atten * (1.0 + fresnel) * 0.07;
|
|
||||||
vec3 ambient = dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * max(fresnel, 0.06) * vec3(0.1, 0.5, 1.0) * 0.85;
|
|
||||||
float fog = smootherstep(25.0, 6.0, fogDist) * (1.0 / (fogDist * 0.1));
|
|
||||||
|
|
||||||
return color + (point + ambient) * fog;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 waterNormal(vec2 p, float eps) {
|
|
||||||
vec2 h = vec2(eps, 0.0);
|
|
||||||
return normalize(vec3(heightmap(p - h.xy) - heightmap(p + h.xy), eps * 2.0, heightmap(p - h.yx) - heightmap(p + h.yx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void marchWater(vec3 eye, vec3 ray, inout vec4 color) {
|
|
||||||
const vec3 planeNorm = vec3(0.0, 1.0, 0.0);
|
|
||||||
const float depth = 3.0;
|
|
||||||
float ceilDist = intersectPlane(eye, ray, vec3(0.0, 0.0, 0.0), planeNorm);
|
|
||||||
vec3 normal = vec3(0.0);
|
|
||||||
|
|
||||||
if (dot(planeNorm, ray) > -0.05) {
|
|
||||||
color = vec4(vec3(0.0), CAM_FAR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float height = 0.0;
|
|
||||||
vec3 rayPos = eye + ray * ceilDist;
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
height = heightmap(rayPos.xz) * depth - depth;
|
|
||||||
if (rayPos.y - height < 0.1) {
|
|
||||||
color.w = distance(rayPos, eye);
|
|
||||||
vec3 normPos = (eye + ray * color.w);
|
|
||||||
color.rgb = waterColor(ray, waterNormal(normPos.xz, 0.005), normPos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rayPos += ray * max(rayPos.y - height, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
color = vec4(vec3(0.0), CAM_FAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 march(vec2 uv, vec3 camPos) {
|
|
||||||
mat4 vm = viewMatrix(camFwd, camUp);
|
|
||||||
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
|
||||||
vec4 color = vec4(BACKGROUND, CAM_FAR);
|
|
||||||
vec3 waterColor;
|
|
||||||
marchWater(camPos, ray, color);
|
|
||||||
marchObjects(camPos, ray, color.w, color);
|
|
||||||
return color.rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
float snow(vec2 uv, float scale) {
|
|
||||||
float w = smootherstep(1.0, 0.0, -uv.y * (scale * 0.01));
|
|
||||||
if (w < 0.1) return 0.0;
|
|
||||||
uv += time / scale;
|
|
||||||
uv.y += time / scale;
|
|
||||||
uv.x += sin(uv.y + time * 0.125) / scale;
|
|
||||||
uv *= scale;
|
|
||||||
vec2 s = floor(uv), f = fract(uv);
|
|
||||||
return smootherstep(0.0, min(length(0.5 + 0.5 * sin(11.0 * fract(sin((s + scale) * mat2(7.0, 3.0, 6.0, 5.0)) * 5.0)) - f), 3.0), sin(f.x + f.y) * 0.01) * w;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
||||||
vec2 uv = fragCoord * (vec2(1.0) / resolution.xy);
|
|
||||||
|
|
||||||
float s = sin(time);
|
|
||||||
float c = cos(time);
|
|
||||||
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
|
|
||||||
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
|
|
||||||
|
|
||||||
camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
|
||||||
camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
|
||||||
|
|
||||||
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);
|
|
||||||
|
|
||||||
if (layer_snow) {
|
|
||||||
vec2 p = fragCoord.xy / resolution.xy;
|
|
||||||
vec2 uvSnow = (fragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
|
|
||||||
fragColor += mix(vec4(vec3(snow(uvSnow, 4.0)), 0.5) + vec4(vec3(snow(uvSnow, 3.0)), 0.5), vec4(0.0), vec4(0.7));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
gl_FragColor = vColor;
|
|
||||||
mainImage(gl_FragColor, gl_FragCoord.xy);
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
#version 100
|
|
||||||
|
|
||||||
attribute vec3 position;
|
|
||||||
attribute vec4 color;
|
|
||||||
|
|
||||||
uniform mat4 model;
|
|
||||||
varying vec4 vColor;
|
|
||||||
|
|
||||||
void main(void) {
|
|
||||||
vColor = color;
|
|
||||||
gl_Position = model * vec4(position, 1.0);
|
|
||||||
}
|
|
||||||
@@ -1,285 +0,0 @@
|
|||||||
#version 300 es
|
|
||||||
precision lowp float;
|
|
||||||
|
|
||||||
const float CAM_FAR = 20.0;
|
|
||||||
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
|
|
||||||
|
|
||||||
const float PI = radians(180.0);
|
|
||||||
|
|
||||||
uniform float time;
|
|
||||||
uniform vec2 resolution;
|
|
||||||
uniform bool layer_snow;
|
|
||||||
|
|
||||||
in vec4 vColor;
|
|
||||||
layout (location = 0) out vec4 fragColor;
|
|
||||||
|
|
||||||
vec3 artifactOffset;
|
|
||||||
mat3 artifactRotation;
|
|
||||||
vec3 artifactAxis;
|
|
||||||
vec3 camFwd;
|
|
||||||
vec3 camUp;
|
|
||||||
|
|
||||||
float smootherstep(float edge0, float edge1, float x) {
|
|
||||||
x = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
||||||
return x * x * x * (x * (x * 6.0 - 15.0) + 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float rand(float n) {
|
|
||||||
n = fract(n * 43758.5453);
|
|
||||||
n *= n;
|
|
||||||
return fract(n * 43758.5453);
|
|
||||||
}
|
|
||||||
|
|
||||||
float hash(float n) {
|
|
||||||
return fract(abs(fract(n) * 43758.5453));
|
|
||||||
}
|
|
||||||
|
|
||||||
float noise(float x) {
|
|
||||||
float i = floor(x);
|
|
||||||
float f = fract(x);
|
|
||||||
float u = f * f * (3.0 - 2.0 * f);
|
|
||||||
return mix(hash(i), hash(i + 1.0), u);
|
|
||||||
}
|
|
||||||
|
|
||||||
mat4 viewMatrix(vec3 dir, vec3 up) {
|
|
||||||
vec3 f = normalize(dir);
|
|
||||||
vec3 s = normalize(cross(f, up));
|
|
||||||
return mat4(vec4(s, 0.0), vec4(cross(s, f), 0.0), vec4(-f, 0.0), vec4(0.0, 0.0, 0.0, 1.0));
|
|
||||||
}
|
|
||||||
|
|
||||||
mat3 rotationAlign(vec3 d, vec3 z) {
|
|
||||||
vec3 v = cross(z, d);
|
|
||||||
float c = dot(z, d);
|
|
||||||
float k = 1.0 / (1.0 + c);
|
|
||||||
return mat3(
|
|
||||||
v.x * v.x * k + c,
|
|
||||||
v.y * v.x * k - v.z,
|
|
||||||
v.z * v.x * k + v.y,
|
|
||||||
v.x * v.y * k + v.z,
|
|
||||||
v.y * v.y * k + c,
|
|
||||||
v.z * v.y * k - v.x,
|
|
||||||
v.x * v.z * k - v.y,
|
|
||||||
v.y * v.z * k + v.x,
|
|
||||||
v.z * v.z * k + c
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
|
|
||||||
return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 calcRay(vec2 uv, float fov, float aspect) {
|
|
||||||
uv = uv * 2.0 - 1.0;
|
|
||||||
float d = 1.0 / tan(radians(fov) * 0.5);
|
|
||||||
return normalize(vec3(aspect * uv.x, uv.y, d));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 getWave(vec2 position, vec2 dir, float speed, float frequency, float iTimeshift) {
|
|
||||||
float x = dot(dir, position) * frequency + iTimeshift * speed;
|
|
||||||
float wave = exp(sin(x) - 1.0);
|
|
||||||
float dist = wave * cos(x);
|
|
||||||
return vec2(wave, -dist);
|
|
||||||
}
|
|
||||||
|
|
||||||
float heightmap(vec2 worldPos) {
|
|
||||||
const float scale = 0.06;
|
|
||||||
|
|
||||||
vec2 p = worldPos * scale;
|
|
||||||
vec2 p2 = (artifactOffset.xz - vec2(0.0, 1.0)) * scale;
|
|
||||||
|
|
||||||
float d = (1.0 - smootherstep(0.0, 1.0, clamp(length(p2 - p) * 1.25, 0.0, 1.0))) * 0.87;
|
|
||||||
float angle = 0.0;
|
|
||||||
float freq = 5.0;
|
|
||||||
float speed = 2.0;
|
|
||||||
float weight = 1.9;
|
|
||||||
float wave = 0.0;
|
|
||||||
float waveScale = 0.0;
|
|
||||||
|
|
||||||
vec2 dir;
|
|
||||||
vec2 res;
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
dir = vec2(cos(angle), sin(angle));
|
|
||||||
res = getWave(p, dir, speed, freq, time);
|
|
||||||
p += dir * res.y * weight * 0.05;
|
|
||||||
wave += res.x * weight - d;
|
|
||||||
angle += 12.0;
|
|
||||||
waveScale += weight;
|
|
||||||
weight = mix(weight, 0.0, 0.2);
|
|
||||||
freq *= 1.18;
|
|
||||||
speed *= 1.06;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wave * (1.0 / waveScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
float octahedron(vec3 p, float s) {
|
|
||||||
p = abs(p);
|
|
||||||
return (p.x + p.y + p.z - s) * 0.57735027;
|
|
||||||
}
|
|
||||||
|
|
||||||
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
|
|
||||||
p -= artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
float dist = octahedron(p, 0.8);
|
|
||||||
const float glowDist = 4.8;
|
|
||||||
if (dist < glowDist) {
|
|
||||||
float d = dist + rand(dist) * 1.7;
|
|
||||||
glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
|
||||||
}
|
|
||||||
if (dist < currDist) {
|
|
||||||
currDist = dist;
|
|
||||||
id = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float objects(vec3 p, inout vec3 glowColor, inout int objId) {
|
|
||||||
float dist = CAM_FAR;
|
|
||||||
artifact(p, dist, glowColor, objId);
|
|
||||||
return dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
float artifactDist(vec3 p) {
|
|
||||||
p -= artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
return octahedron(p, 1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
float objectsDist(vec3 p) {
|
|
||||||
return artifactDist(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 objectsNormal(vec3 p, float eps) {
|
|
||||||
vec2 h = vec2(eps, 0);
|
|
||||||
return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 objectsColor(int id, vec3 normal, vec3 ray) {
|
|
||||||
return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
|
|
||||||
id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
|
|
||||||
vec3(1.0, 1.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
|
|
||||||
float dist = 0.0;
|
|
||||||
int id;
|
|
||||||
vec3 rayPos = eye;
|
|
||||||
float depth = CAM_FAR;
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
dist = objects(rayPos, color.rgb, id);
|
|
||||||
depth = distance(rayPos, eye);
|
|
||||||
if (depth > wDepth || dist < 0.01) break;
|
|
||||||
rayPos += ray * dist;
|
|
||||||
}
|
|
||||||
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 waterColor(vec3 ray, vec3 normal, vec3 p) {
|
|
||||||
vec3 color = vec3(0.0);
|
|
||||||
float fogDist = length(p - vec3(0.0, 0.0, -6.0));
|
|
||||||
float dist = 0.0;
|
|
||||||
int objId = 0;
|
|
||||||
vec3 refl = reflect(ray, normal);
|
|
||||||
vec3 rayPos = p + refl * dist;
|
|
||||||
|
|
||||||
if (length(p.xz - artifactOffset.xz) < 8.5 && dot(refl, normalize(artifactOffset - p)) > -0.25) {
|
|
||||||
for (int i = 0; i < 40; i++) {
|
|
||||||
dist = objects(rayPos, color, objId);
|
|
||||||
if (dist < 0.01) {
|
|
||||||
color = objectsColor(objId, objectsNormal(rayPos, 0.001), rayPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rayPos += refl * dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
float fresnel = 0.04 + 0.9 * pow(1.0 - max(0.0, dot(-normal, ray)), 7.0);
|
|
||||||
float d = length(artifactOffset - p);
|
|
||||||
const float r = 14.0;
|
|
||||||
float atten = clamp(1.0 - (d * d) / (r * r), 0.0, 1.0);
|
|
||||||
atten *= atten;
|
|
||||||
|
|
||||||
vec3 point = vec3(0.75, 0.55, 0.45) * atten * (1.0 + fresnel) * 0.07;
|
|
||||||
vec3 ambient = dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * max(fresnel, 0.06) * vec3(0.1, 0.5, 1.0) * 0.85;
|
|
||||||
float fog = smootherstep(25.0, 6.0, fogDist) * (1.0 / (fogDist * 0.1));
|
|
||||||
|
|
||||||
return color + (point + ambient) * fog;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 waterNormal(vec2 p, float eps) {
|
|
||||||
vec2 h = vec2(eps, 0.0);
|
|
||||||
return normalize(vec3(heightmap(p - h.xy) - heightmap(p + h.xy), eps * 2.0, heightmap(p - h.yx) - heightmap(p + h.yx)));
|
|
||||||
}
|
|
||||||
|
|
||||||
void marchWater(vec3 eye, vec3 ray, inout vec4 color) {
|
|
||||||
const vec3 planeNorm = vec3(0.0, 1.0, 0.0);
|
|
||||||
const float depth = 3.0;
|
|
||||||
float ceilDist = intersectPlane(eye, ray, vec3(0.0, 0.0, 0.0), planeNorm);
|
|
||||||
vec3 normal = vec3(0.0);
|
|
||||||
|
|
||||||
if (dot(planeNorm, ray) > -0.05) {
|
|
||||||
color = vec4(vec3(0.0), CAM_FAR);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
float height = 0.0;
|
|
||||||
vec3 rayPos = eye + ray * ceilDist;
|
|
||||||
for (int i = 0; i < 30; i++) {
|
|
||||||
height = heightmap(rayPos.xz) * depth - depth;
|
|
||||||
if (rayPos.y - height < 0.1) {
|
|
||||||
color.w = distance(rayPos, eye);
|
|
||||||
vec3 normPos = (eye + ray * color.w);
|
|
||||||
color.rgb = waterColor(ray, waterNormal(normPos.xz, 0.005), normPos);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
rayPos += ray * max(rayPos.y - height, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
color = vec4(vec3(0.0), CAM_FAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 march(vec2 uv, vec3 camPos) {
|
|
||||||
mat4 vm = viewMatrix(camFwd, camUp);
|
|
||||||
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
|
||||||
vec4 color = vec4(BACKGROUND, CAM_FAR);
|
|
||||||
vec3 waterColor;
|
|
||||||
marchWater(camPos, ray, color);
|
|
||||||
marchObjects(camPos, ray, color.w, color);
|
|
||||||
return color.rgb;
|
|
||||||
}
|
|
||||||
|
|
||||||
float snow(vec2 uv, float scale) {
|
|
||||||
float w = smootherstep(1.0, 0.0, -uv.y * (scale * 0.01));
|
|
||||||
if (w < 0.1) return 0.0;
|
|
||||||
uv += time / scale;
|
|
||||||
uv.y += time / scale;
|
|
||||||
uv.x += sin(uv.y + time * 0.125) / scale;
|
|
||||||
uv *= scale;
|
|
||||||
vec2 s = floor(uv), f = fract(uv);
|
|
||||||
return smootherstep(0.0, min(length(0.5 + 0.5 * sin(11.0 * fract(sin((s + scale) * mat2(7.0, 3.0, 6.0, 5.0)) * 5.0)) - f), 3.0), sin(f.x + f.y) * 0.01) * w;
|
|
||||||
}
|
|
||||||
|
|
||||||
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
|
||||||
vec2 uv = fragCoord * (vec2(1.0) / resolution.xy);
|
|
||||||
|
|
||||||
float s = sin(time);
|
|
||||||
float c = cos(time);
|
|
||||||
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
|
|
||||||
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
|
|
||||||
|
|
||||||
camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
|
||||||
camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
|
||||||
|
|
||||||
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);
|
|
||||||
|
|
||||||
if (layer_snow) {
|
|
||||||
vec2 p = fragCoord.xy / resolution.xy;
|
|
||||||
vec2 uvSnow = (fragCoord.xy * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
|
|
||||||
fragColor += mix(vec4(vec3(snow(uvSnow, 4.0)), 0.5) + vec4(vec3(snow(uvSnow, 3.0)), 0.5), vec4(0.0), vec4(0.7));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = vColor;
|
|
||||||
mainImage(fragColor, gl_FragCoord.xy);
|
|
||||||
}
|
|
||||||
@@ -1,268 +0,0 @@
|
|||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> time: f32;
|
|
||||||
|
|
||||||
@group(0) @binding(0)
|
|
||||||
var<uniform> resolution: vec2<f32>;
|
|
||||||
|
|
||||||
@fragment
|
|
||||||
// fn mainImage(@builtin(frag_coord) fragCoord: vec4<f32>) -> @location(0) vec4<f32>
|
|
||||||
fn fragment_main(fragData: VertexOut) -> @location(0) vec4f {
|
|
||||||
var uv = fragCoord.xy / resolution;
|
|
||||||
var s = sin(time);
|
|
||||||
var c = cos(time);
|
|
||||||
|
|
||||||
var artifactRotation: mat3x3<f32> = mat3x3<f32>(
|
|
||||||
vec3<f32>(c, 0.0, s),
|
|
||||||
vec3<f32>(0.0, 1.0, 0.0),
|
|
||||||
vec3<f32>(-s, 0.0, c)
|
|
||||||
) * rotationAlign(vec3<f32>(0.0, 1.0, 0.0), vec3<f32>(s * 0.2, 1.0, c * 0.2 + 0.3));
|
|
||||||
var artifactOffset = vec3<f32>(s * 0.4, c * 0.3 - 1.7, -6.0);
|
|
||||||
|
|
||||||
var camFwd: vec3<f32> = vec3<f32>(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
|
||||||
var camUp: vec3<f32> = vec3<f32>(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
|
||||||
|
|
||||||
var color: vec3<f32> = march(uv, vec3<f32>(0.0, 1.9, 1.0)) - (length(uv - vec2<f32>(0.5)) - 0.3) * 0.05;
|
|
||||||
return vec4<f32>(color, 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn smootherstep(edge0: f32, edge1: f32, x: f32) -> f32 {
|
|
||||||
var t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
|
||||||
return t * t * t * (t * (t * 6.0 - 15.0) + 10.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rand(n: f32) -> f32 {
|
|
||||||
var n1 = fract(n * 43758.5453);
|
|
||||||
n1 = n1 * n1;
|
|
||||||
return fract(n1 * 43758.5453);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hash(n: f32) -> f32 {
|
|
||||||
return fract(abs(fract(n) * 43758.5453));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn noise(x: f32) -> f32 {
|
|
||||||
var i = floor(x);
|
|
||||||
var f = fract(x);
|
|
||||||
var u = f * f * (3.0 - 2.0 * f);
|
|
||||||
return mix(hash(i), hash(i + 1.0), u);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn viewMatrix(dir: vec3<f32>, up: vec3<f32>) -> mat4x4<f32> {
|
|
||||||
var f = normalize(dir);
|
|
||||||
var s = normalize(cross(f, up));
|
|
||||||
return mat4x4<f32>(
|
|
||||||
vec4<f32>(s, 0.0),
|
|
||||||
vec4<f32>(cross(s, f), 0.0),
|
|
||||||
vec4<f32>(-f, 0.0),
|
|
||||||
vec4<f32>(0.0, 0.0, 0.0, 1.0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rotationAlign(d: vec3<f32>, z: vec3<f32>) -> mat3x3<f32> {
|
|
||||||
var v = cross(z, d);
|
|
||||||
var c = dot(z, d);
|
|
||||||
var k = 1.0 / (1.0 + c);
|
|
||||||
return mat3x3<f32>(
|
|
||||||
vec3<f32>(v.x * v.x * k + c, v.y * v.x * k - v.z, v.z * v.x * k + v.y),
|
|
||||||
vec3<f32>(v.x * v.y * k + v.z, v.y * v.y * k + c, v.z * v.y * k - v.x),
|
|
||||||
vec3<f32>(v.x * v.z * k - v.y, v.y * v.z * k + v.x, v.z * v.z * k + c)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn intersectPlane(origin: vec3<f32>, direction: vec3<f32>, pointt: vec3<f32>, normal: vec3<f32>) -> f32 {
|
|
||||||
return clamp(dot(pointt - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn calcRay(uv: vec2<f32>, fov: f32, aspect: f32) -> vec3<f32> {
|
|
||||||
var uv1 = uv * 2.0 - 1.0;
|
|
||||||
var d = 1.0 / tan(radians(fov) * 0.5);
|
|
||||||
return normalize(vec3<f32>(aspect * uv1.x, uv1.y, d));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn getWave(position: vec2<f32>, dir: vec2<f32>, speed: f32, frequency: f32, iTimeshift: f32) -> vec2<f32> {
|
|
||||||
var x = dot(dir, position) * frequency + iTimeshift * speed;
|
|
||||||
var wave = exp(sin(x) - 1.0);
|
|
||||||
var dist = wave * cos(x);
|
|
||||||
return vec2<f32>(wave, -dist);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn heightmap(worldPos: vec2<f32>) -> f32 {
|
|
||||||
let scale: f32 = 0.06;
|
|
||||||
|
|
||||||
var p = worldPos * scale;
|
|
||||||
var p2 = (artifactOffset.xz - vec2<f32>(0.0, 1.0)) * scale;
|
|
||||||
|
|
||||||
var d = (1.0 - smootherstep(0.0, 1.0, clamp(length(p2 - p) * 1.25, 0.0, 1.0))) * 0.87;
|
|
||||||
var angle: f32 = 0.0;
|
|
||||||
var freq: f32 = 5.0;
|
|
||||||
var speed: f32 = 2.0;
|
|
||||||
var weight: f32 = 1.9;
|
|
||||||
var wave: f32 = 0.0;
|
|
||||||
var waveScale: f32 = 0.0;
|
|
||||||
|
|
||||||
var dir: vec2<f32>;
|
|
||||||
var res: vec2<f32>;
|
|
||||||
|
|
||||||
for (var i: i32 = 0; i < 5; i = i + 1) {
|
|
||||||
dir = vec2<f32>(cos(angle), sin(angle));
|
|
||||||
res = getWave(p, dir, speed, freq, time);
|
|
||||||
p = p + dir * res.y * weight * 0.05;
|
|
||||||
wave = wave + res.x * weight - d;
|
|
||||||
angle = angle + 12.0;
|
|
||||||
waveScale = waveScale + weight;
|
|
||||||
weight = mix(weight, 0.0, 0.2);
|
|
||||||
freq = freq * 1.18;
|
|
||||||
speed = speed * 1.06;
|
|
||||||
}
|
|
||||||
|
|
||||||
return wave * (1.0 / waveScale);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn octahedron(p: vec3<f32>, s: f32) -> f32 {
|
|
||||||
p = abs(p);
|
|
||||||
return (p.x + p.y + p.z - s) * 0.57735027;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn artifact(p: vec3<f32>, currDist: f32, glowColor: vec3<f32>, id: i32) {
|
|
||||||
p = p - artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
var dist = octahedron(p, 0.8);
|
|
||||||
let glowDist: f32 = 4.8;
|
|
||||||
if (dist < glowDist) {
|
|
||||||
var d = dist + rand(dist) * 1.7;
|
|
||||||
glowColor = glowColor + vec3<f32>(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
|
||||||
}
|
|
||||||
if (dist < currDist) {
|
|
||||||
currDist = dist;
|
|
||||||
id = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn objects(p: vec3<f32>, glowColor: vec3<f32>, objId: i32) -> f32 {
|
|
||||||
var dist: f32 = CAM_FAR;
|
|
||||||
artifact(p, dist, glowColor, objId);
|
|
||||||
return dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn artifactDist(p: vec3<f32>) -> f32 {
|
|
||||||
p = p - artifactOffset;
|
|
||||||
p = artifactRotation * p;
|
|
||||||
return octahedron(p, 1.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn objectsDist(p: vec3<f32>) -> f32 {
|
|
||||||
return artifactDist(p);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn objectsNormal(p: vec3<f32>, eps: f32) -> vec3<f32> {
|
|
||||||
var h = vec2<f32>(eps, 0.0);
|
|
||||||
return normalize(vec3<f32>(
|
|
||||||
artifactDist(p + h.xxx) - artifactDist(p - h.xxx),
|
|
||||||
eps * 2.0,
|
|
||||||
artifactDist(p + h.yyy) - artifactDist(p - h.yyy)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn objectsColor(id: i32, normal: vec3<f32>, ray: vec3<f32>) -> vec3<f32> {
|
|
||||||
if (id == 1) { return vec3<f32>(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3<f32>(0.0, 1.0, 0.5))) * 0.5 + 0.5); }
|
|
||||||
if (id == 2) { return vec3<f32>(0.85, 0.65, 0.55) * 1.5; }
|
|
||||||
return vec3<f32>(1.0, 1.0, 0.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn marchObjects(eye: vec3<f32>, ray: vec3<f32>, wDepth: f32, color: vec4<f32>) -> vec4<f32> {
|
|
||||||
var dist: f32 = 0.0;
|
|
||||||
var id: i32;
|
|
||||||
var rayPos: vec3<f32> = eye;
|
|
||||||
var depth: f32 = CAM_FAR;
|
|
||||||
for (var i: i32 = 0; i < 30; i = i + 1) {
|
|
||||||
dist = objects(rayPos, color.rgb, id);
|
|
||||||
depth = distance(rayPos, eye);
|
|
||||||
if (depth > wDepth || dist < 0.01) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rayPos = rayPos + ray * dist;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dist < 0.01) {
|
|
||||||
return vec4<f32>(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth);
|
|
||||||
}
|
|
||||||
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waterColor(ray: vec3<f32>, normal: vec3<f32>, p: vec3<f32>) -> vec3<f32> {
|
|
||||||
var color: vec3<f32> = vec3<f32>(0.0);
|
|
||||||
var fogDist: f32 = length(p - vec3<f32>(0.0, 0.0, -6.0));
|
|
||||||
var dist: f32 = 0.0;
|
|
||||||
var objId: i32 = 0;
|
|
||||||
var refl: vec3<f32> = reflect(ray, normal);
|
|
||||||
var rayPos: vec3<f32> = p + refl * dist;
|
|
||||||
|
|
||||||
if (length(p.xz - artifactOffset.xz) < 8.5 && dot(refl, normalize(artifactOffset - p)) > -0.25) {
|
|
||||||
for (var i: i32 = 0; i < 40; i = i + 1) {
|
|
||||||
dist = objects(rayPos, color, objId);
|
|
||||||
if (dist < 0.01) {
|
|
||||||
color = objectsColor(objId, objectsNormal(rayPos, 0.001), rayPos);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
rayPos = rayPos + refl * dist;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var fresnel: f32 = 0.04 + 0.9 * pow(1.0 - max(0.0, dot(-normal, ray)), 7.0);
|
|
||||||
var d: f32 = length(artifactOffset - p);
|
|
||||||
const r: f32 = 14.0;
|
|
||||||
var atten: f32 = clamp(1.0 - (d * d) / (r * r), 0.0, 1.0);
|
|
||||||
atten = atten * atten;
|
|
||||||
|
|
||||||
var pointt: vec3<f32> = vec3<f32>(0.75, 0.55, 0.45) * atten * (1.0 + fresnel) * 0.07;
|
|
||||||
var ambient: vec3<f32> = dot(normal, normalize(vec3<f32>(0.0, 1.0, 0.5))) * max(fresnel, 0.06) * vec3<f32>(0.1, 0.5, 1.0) * 0.85;
|
|
||||||
var fog: f32 = smootherstep(25.0, 6.0, fogDist) * (1.0 / (fogDist * 0.1));
|
|
||||||
|
|
||||||
return color + (pointt + ambient) * fog;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn waterNormal(p: vec2<f32>, eps: f32) -> vec3<f32> {
|
|
||||||
var h: vec2<f32> = vec2<f32>(eps, 0.0);
|
|
||||||
return normalize(vec3<f32>(
|
|
||||||
heightmap(p - h.xy) - heightmap(p + h.xy),
|
|
||||||
eps * 2.0,
|
|
||||||
heightmap(p - h.yx) - heightmap(p + h.yx)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn marchWater(eye: vec3<f32>, ray: vec3<f32>, color: vec4<f32>) -> vec4<f32> {
|
|
||||||
let planeNorm: vec3<f32> = vec3<f32>(0.0, 1.0, 0.0);
|
|
||||||
let depth: f32 = 3.0;
|
|
||||||
var ceilDist: f32 = intersectPlane(eye, ray, vec3<f32>(0.0, 0.0, 0.0), planeNorm);
|
|
||||||
var normal: vec3<f32> = vec3<f32>(0.0);
|
|
||||||
|
|
||||||
if (dot(planeNorm, ray) > -0.05) {
|
|
||||||
return vec4<f32>(vec3<f32>(0.0), CAM_FAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
var height: f32 = 0.0;
|
|
||||||
var rayPos: vec3<f32> = eye + ray * ceilDist;
|
|
||||||
for (var i: i32 = 0; i < 30; i = i + 1) {
|
|
||||||
height = heightmap(rayPos.xz) * depth - depth;
|
|
||||||
if (rayPos.y - height < 0.1) {
|
|
||||||
color.w = distance(rayPos, eye);
|
|
||||||
var normPos: vec3<f32> = (eye + ray * color.w);
|
|
||||||
color.rgb = waterColor(ray, waterNormal(normPos.xz, 0.005), normPos);
|
|
||||||
return color;
|
|
||||||
}
|
|
||||||
rayPos = rayPos + ray * max(rayPos.y - height, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return vec4<f32>(vec3<f32>(0.0), CAM_FAR);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn march(uv: vec2<f32>, camPos: vec3<f32>) -> vec3<f32> {
|
|
||||||
var vm: mat4x4<f32> = viewMatrix(camFwd, camUp);
|
|
||||||
var ray: vec3<f32> = (vm * vec4<f32>(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
|
||||||
var color: vec4<f32> = vec4<f32>(BACKGROUND, CAM_FAR);
|
|
||||||
var waterColor: vec3<f32>;
|
|
||||||
color = marchWater(camPos, ray, color);
|
|
||||||
color = marchObjects(camPos, ray, color.w, color);
|
|
||||||
return color.rgb;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
struct VertexOut {
|
|
||||||
@builtin(position) position : vec4f,
|
|
||||||
@location(0) color : vec4f
|
|
||||||
};
|
|
||||||
|
|
||||||
@vertex
|
|
||||||
fn vertex_main(@location(0) position: vec4f,
|
|
||||||
@location(1) color: vec4f) -> VertexOut {
|
|
||||||
var output : VertexOut;
|
|
||||||
output.position = position;
|
|
||||||
output.color = color;
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
115
index.html
@@ -1,90 +1,41 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8"/>
|
<meta charset="UTF-8" />
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
<title>celesteflare.cc ~ lunary</title>
|
<title>i0ur.ing ~ welcome</title>
|
||||||
|
|
||||||
<link href="assets/imgs/logo.ico" rel="icon" type="image/x-icon"/>
|
<link data-trunk rel="copy-file" href="public/misc/favicon.ico" />
|
||||||
<link href="main.css" rel="stylesheet" type="text/css"/>
|
<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" />
|
||||||
|
|
||||||
<script async defer src="js/init.js" type="module"></script>
|
<link data-trunk rel="copy-file" href="public/misc/freemono.ttf" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/misc/monocraft.ttf" />
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/blog/testimage.jpg" />
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/shaders/post.bloom.svg" />
|
||||||
|
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/iouring.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/wasm.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/dataforest.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/servfail.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/rust.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/csharp.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/kotlin.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/java.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/fedora.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/artix.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/nixos.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/chimera.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/void.png" />
|
||||||
|
<link data-trunk rel="copy-file" href="public/buttons/aqueer.png" />
|
||||||
|
|
||||||
|
<link data-trunk rel="sass" href="main.scss" />
|
||||||
|
<link data-trunk rel="rust" />
|
||||||
|
<base data-trunk-public-url />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
|
||||||
<canvas id="shader-background"></canvas>
|
|
||||||
<main>
|
|
||||||
<div class="flexcard" id="cards">
|
|
||||||
<img alt="oneko.gif" class="oneko" src="assets/imgs/oneko.gif"/>
|
|
||||||
<div class="card">
|
|
||||||
<div id="profile-card"></div>
|
|
||||||
<div id="navigation">
|
|
||||||
<button class="navi" id="home" type="button">Home</button>
|
|
||||||
<button class="navi" id="portfolio" type="button">Portfolio</button>
|
|
||||||
<button class="navi" id="blog" type="button">Blog</button>
|
|
||||||
</div>
|
|
||||||
<div class="contents" id="content"></div>
|
|
||||||
<hr/>
|
|
||||||
<div class="socials">
|
|
||||||
<p>© celesteflare.cc</p>
|
|
||||||
<a href="https://bsky.app/profile/lunary.celesteflare.cc" target="_blank"><i class="bi bi-cloudy-fill"></i></a>
|
|
||||||
<a href="https://dc.celesteflare.cc" onclick="navigator.clipboard.writeText(`luzey.zip`)"
|
|
||||||
target="_blank"><i class="bi bi-discord"></i></a>
|
|
||||||
<a href="https://www.instagram.com/luzl.r.h" target="_blank"><i class="bi bi-instagram"></i></a>
|
|
||||||
<a href="https://www.github.com/lunarydess" target="_blank"><i class="bi bi-github"></i></a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<noscript
|
|
||||||
style="
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 10;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background-color: hsl(240, 21%, 15%);
|
|
||||||
user-select: none;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
style="
|
|
||||||
position: fixed;
|
|
||||||
inset: 0;
|
|
||||||
width: fit-content;
|
|
||||||
height: fit-content;
|
|
||||||
margin: auto;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<center>
|
|
||||||
<h1
|
|
||||||
style="
|
|
||||||
color: hsl(226, 64%, 88%);
|
|
||||||
text-decoration-color: hsl(226, 64%, 88%);
|
|
||||||
text-align: center;
|
|
||||||
text-rendering: geometricPrecision;
|
|
||||||
top: 50%;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
JavaScript is mandatory. :'(<br/>
|
|
||||||
Did you try it yet?
|
|
||||||
</h1>
|
|
||||||
<img
|
|
||||||
alt="yippee.gif"
|
|
||||||
src="assets/imgs/yippee.gif" style="
|
|
||||||
width: 25%;
|
|
||||||
height: 25%;
|
|
||||||
image-rendering: optimizeQuality;
|
|
||||||
image-resolution: 498x;
|
|
||||||
shape-image-threshold: 10;
|
|
||||||
image-orientation: from-image;
|
|
||||||
border-radius: 8px;
|
|
||||||
outline: hsl(53, 93%, 54%) dotted 2px;
|
|
||||||
pointer-events: none;
|
|
||||||
"/>
|
|
||||||
</center>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
import {insertFileContentIn as insert} from "./navigator.js";
|
|
||||||
import {init as initGallery} from "./gallery.js";
|
|
||||||
|
|
||||||
export async function init() {
|
|
||||||
const naviElements = document.getElementsByClassName("navi");
|
|
||||||
for (let i = 0; i < naviElements.length; i++) {
|
|
||||||
const element = naviElements.item(i);
|
|
||||||
element.addEventListener("click", function () {
|
|
||||||
insert("content", element.id, element.id === "portfolio" ? initGallery : undefined);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
const maxImages = 3;
|
|
||||||
|
|
||||||
const currentImage = {
|
|
||||||
idx: 1,
|
|
||||||
name: null,
|
|
||||||
image: null,
|
|
||||||
link: null,
|
|
||||||
desc: null,
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function init() {
|
|
||||||
const previewImage = document.getElementById("portfolio-preview");
|
|
||||||
const previewTitle = document.getElementById("portfolio-title");
|
|
||||||
const previewDesc = document.getElementById("portfolio-description");
|
|
||||||
|
|
||||||
document.getElementById("portfolio-preview-previous").onclick = function () {
|
|
||||||
nextImage(previewImage, previewTitle, previewDesc, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("portfolio-preview-next").onclick = function () {
|
|
||||||
nextImage(previewImage, previewTitle, previewDesc, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchImage(previewImage, previewTitle, previewDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function nextImage(image, title, description, forward) {
|
|
||||||
if (forward) ++currentImage.idx;
|
|
||||||
else --currentImage.idx;
|
|
||||||
|
|
||||||
if (currentImage.idx > maxImages) currentImage.idx = 1;
|
|
||||||
else if (currentImage.idx < 1) currentImage.idx = maxImages;
|
|
||||||
|
|
||||||
fetchImage(image, title, description);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchImage(image, title, description) {
|
|
||||||
fetch(`assets/projects/${currentImage.idx}.json`)
|
|
||||||
.then(value => value.json().then(currentImageJson => {
|
|
||||||
currentImage.name = currentImageJson.name;
|
|
||||||
currentImage.image = currentImageJson.image;
|
|
||||||
currentImage.link = currentImageJson.link;
|
|
||||||
currentImage.desc = currentImageJson.desc;
|
|
||||||
title.innerText = currentImage.name;
|
|
||||||
description.innerText = currentImage.desc.map(item => JSON.stringify(item)).join('\n').replaceAll("\"", "");
|
|
||||||
console.info(description.innerText)
|
|
||||||
image.setAttribute("src", `assets/projects/${currentImage.image}`);
|
|
||||||
image.onclick = function () {
|
|
||||||
window.open(currentImage.link, "_blank").focus();
|
|
||||||
};
|
|
||||||
}))
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
13
js/init.js
@@ -1,13 +0,0 @@
|
|||||||
import {init as initThemes} from "./themes.js";
|
|
||||||
import {init as initShaders} from "./shaders.js";
|
|
||||||
import {init as initNavigator} from "./navigator.js";
|
|
||||||
import {init as initButtons} from "./buttons.js";
|
|
||||||
|
|
||||||
async function init() {
|
|
||||||
initThemes();
|
|
||||||
initShaders();
|
|
||||||
initNavigator();
|
|
||||||
initButtons();
|
|
||||||
}
|
|
||||||
|
|
||||||
init();
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
import {init as initGallery} from "./gallery.js";
|
|
||||||
|
|
||||||
export async function insertContentIn(destination, content, onload = undefined) {
|
|
||||||
const element = document.getElementById(destination);
|
|
||||||
element.innerHTML = content;
|
|
||||||
if (onload) {
|
|
||||||
element.onload = onload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function insertFileContentIn(destination, htmlFile, onload = undefined) {
|
|
||||||
fetch(`./router/${htmlFile}.html`)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(text => insertContentIn(destination, text, onload))
|
|
||||||
.then(_ => {
|
|
||||||
document.title = `lunary ~ ${htmlFile}`
|
|
||||||
window.history.pushState(null, 'Title', `#${htmlFile}`)
|
|
||||||
}).catch(error => console.error(error));
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function init() {
|
|
||||||
let meow = window.location.href.match(/https*:\/\/.*#.+/g);
|
|
||||||
meow = meow === null || meow === undefined || meow.length === 0 ? "home" : String(meow).substring(String(meow).lastIndexOf("#") + 1);
|
|
||||||
insertFileContentIn("content", meow, meow === "portfolio" ? initGallery : undefined);
|
|
||||||
}
|
|
||||||
234
js/shaders.js
@@ -1,234 +0,0 @@
|
|||||||
import {isWinter, isXmas} from "./util.js";
|
|
||||||
|
|
||||||
export async function init() {
|
|
||||||
const canvas = document.querySelector("#shader-background");
|
|
||||||
|
|
||||||
/* TODO: ...
|
|
||||||
const wgpuContext = canvas.getContext("webgpu");
|
|
||||||
if (wgpuContext !== null) {
|
|
||||||
initWGPU(canvas, wgpuContext);
|
|
||||||
return;
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let context;
|
|
||||||
const wglVersion =
|
|
||||||
(context = (canvas.getContext("experimental-webgl2") || canvas.getContext("webgl2"))) != null ? 2 :
|
|
||||||
(context = (canvas.getContext("experimental-webgl") || canvas.getContext("webgl"))) != null ? 1 : 0;
|
|
||||||
if (wglVersion !== 0) initGL(canvas, context, wglVersion);
|
|
||||||
else console.warn("couldn't find webgpu nor webgl, so no-no fancy shader-background. :(");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function initGL(canvas, context, wglVersion) {
|
|
||||||
console.info(`detected wgl ${wglVersion} meow`);
|
|
||||||
|
|
||||||
const positions = context.createBuffer();
|
|
||||||
context.bindBuffer(context.ARRAY_BUFFER, positions);
|
|
||||||
context.bufferData(context.ARRAY_BUFFER, new Float32Array([
|
|
||||||
-1.0, 1.0, 0.0,
|
|
||||||
-1.0, -1.0, 0.0,
|
|
||||||
1.0, -1.0, 0.0,
|
|
||||||
1.0, 1.0, 0.0
|
|
||||||
]), context.STATIC_DRAW);
|
|
||||||
|
|
||||||
const colors = context.createBuffer();
|
|
||||||
context.bindBuffer(context.ARRAY_BUFFER, colors);
|
|
||||||
context.bufferData(context.ARRAY_BUFFER, new Float32Array([
|
|
||||||
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
|
|
||||||
]), context.STATIC_DRAW);
|
|
||||||
|
|
||||||
const elements_indices = [3, 2, 1, 3, 1, 0];
|
|
||||||
const elements = context.createBuffer();
|
|
||||||
context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, elements);
|
|
||||||
context.bufferData(context.ELEMENT_ARRAY_BUFFER, new Uint16Array(elements_indices), context.STATIC_DRAW);
|
|
||||||
|
|
||||||
const es_key = wglVersion === 2 ? "es3" : "es1";
|
|
||||||
|
|
||||||
const vertex_shader = context.createShader(context.VERTEX_SHADER);
|
|
||||||
let vertex_shader_code = "";
|
|
||||||
await fetch(`./assets/shaders/glsl/${es_key}/background/bg.vsh.glsl`)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(text => vertex_shader_code = text)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
context.shaderSource(vertex_shader, vertex_shader_code);
|
|
||||||
context.compileShader(vertex_shader);
|
|
||||||
let vertex_shader_log = context.getShaderInfoLog(vertex_shader);
|
|
||||||
if (vertex_shader_log == null || vertex_shader_log.trim().length === 0) {
|
|
||||||
console.info("vertex shader loaded.");
|
|
||||||
} else console.warn(`vertex shader couldn't be loaded! (${vertex_shader_log})`);
|
|
||||||
|
|
||||||
let fragment_shader_code = "";
|
|
||||||
await fetch(`./assets/shaders/glsl/${es_key}/background/bg.fsh.glsl`)
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(text => fragment_shader_code = text)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
const fragment_shader = context.createShader(context.FRAGMENT_SHADER);
|
|
||||||
context.shaderSource(fragment_shader, fragment_shader_code);
|
|
||||||
context.compileShader(fragment_shader);
|
|
||||||
let fragment_shader_log = context.getShaderInfoLog(fragment_shader);
|
|
||||||
if (fragment_shader_log == null || fragment_shader_log.trim().length === 0) {
|
|
||||||
console.info("fragment shader loaded.");
|
|
||||||
} else console.warn(`fragment shader couldn't be loaded! (${fragment_shader_log})`);
|
|
||||||
|
|
||||||
const shaderProgram = context.createProgram();
|
|
||||||
context.attachShader(shaderProgram, vertex_shader);
|
|
||||||
context.attachShader(shaderProgram, fragment_shader);
|
|
||||||
context.linkProgram(shaderProgram);
|
|
||||||
if (!context.getProgramParameter(shaderProgram, context.LINK_STATUS))
|
|
||||||
console.warn("shaders couldn't be linked! :(");
|
|
||||||
context.useProgram(shaderProgram);
|
|
||||||
|
|
||||||
const position = context.getAttribLocation(shaderProgram, "position");
|
|
||||||
context.enableVertexAttribArray(position);
|
|
||||||
context.bindBuffer(context.ARRAY_BUFFER, positions);
|
|
||||||
context.vertexAttribPointer(position, 3, context.FLOAT, false, 0, 0);
|
|
||||||
|
|
||||||
const color = context.getAttribLocation(shaderProgram, "color");
|
|
||||||
context.enableVertexAttribArray(color);
|
|
||||||
context.bindBuffer(context.ARRAY_BUFFER, colors);
|
|
||||||
context.vertexAttribPointer(color, 4, context.FLOAT, false, 0, 0);
|
|
||||||
|
|
||||||
context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, elements);
|
|
||||||
|
|
||||||
context.clearColor(0.0, 0.0, 0.0, 1.0);
|
|
||||||
context.clear(context.COLOR_BUFFER_BIT);
|
|
||||||
context.enable(context.DEPTH_TEST);
|
|
||||||
|
|
||||||
const unimloc_matrix = context.getUniformLocation(shaderProgram, "model");
|
|
||||||
const default_matrix = new Float32Array([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]);
|
|
||||||
|
|
||||||
const unimloc_time = context.getUniformLocation(shaderProgram, "time");
|
|
||||||
const unimloc_res = context.getUniformLocation(shaderProgram, "resolution");
|
|
||||||
|
|
||||||
const unimloc_overlay_snow = context.getUniformLocation(shaderProgram, "layer_snow");
|
|
||||||
|
|
||||||
function renderLoop(time) {
|
|
||||||
canvas.width = window.innerWidth;
|
|
||||||
canvas.height = window.innerHeight;
|
|
||||||
|
|
||||||
context.uniformMatrix4fv(unimloc_matrix, false, default_matrix);
|
|
||||||
|
|
||||||
context.uniform1f(unimloc_time, time / 1000.0);
|
|
||||||
context.uniform2fv(unimloc_res, [canvas.width, canvas.height]);
|
|
||||||
|
|
||||||
context.uniform1i(unimloc_overlay_snow, isXmas() || isWinter());
|
|
||||||
|
|
||||||
context.viewport(0, 0, canvas.width, canvas.height);
|
|
||||||
context.clear(context.COLOR_BUFFER_BIT | context.DEPTH_BUFFER_BIT);
|
|
||||||
context.drawElements(context.TRIANGLES, elements_indices.length, context.UNSIGNED_SHORT, 0);
|
|
||||||
|
|
||||||
window.requestAnimationFrame(renderLoop);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.requestAnimationFrame(renderLoop);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
async function initWGPU(canvas, context) {
|
|
||||||
console.info("detected wgpu");
|
|
||||||
|
|
||||||
async function fallback() {
|
|
||||||
console.info("falling back to gl");
|
|
||||||
let context;
|
|
||||||
const wglVersion = (context = (canvas.getContext("experimental-webgl2") || canvas.getContext("webgl2"))) != null ? 2 : (context = (canvas.getContext("experimental-webgl") || canvas.getContext("webgl"))) != null ? 1 : 0;
|
|
||||||
if (wglVersion !== 0) initGL(canvas, context, wglVersion);
|
|
||||||
else console.warn("couldn't find webgl, so no-no fancy shader-background. :(");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!navigator.gpu) {
|
|
||||||
console.info("wgpu context exists but is not supported.");
|
|
||||||
fallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const adapter = await navigator.gpu.requestAdapter();
|
|
||||||
if (!adapter) {
|
|
||||||
console.warn("couldn't find any adapters");
|
|
||||||
fallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const device = await adapter.requestDevice();
|
|
||||||
if (!device) {
|
|
||||||
console.warn("couldn't find any devices");
|
|
||||||
fallback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let shaders = "";
|
|
||||||
await fetch("./assets/shaders/wgsl/background/bg.vsh.wgsl")
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => shaders += data)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
shaders += '\n';
|
|
||||||
await fetch("./assets/shaders/wgsl/background/bg.fsh.wgsl")
|
|
||||||
.then(response => response.text())
|
|
||||||
.then(data => shaders += data)
|
|
||||||
.catch(error => console.error(error));
|
|
||||||
const shaderModule = device.createShaderModule({code: shaders});
|
|
||||||
|
|
||||||
context.configure({
|
|
||||||
device: device,
|
|
||||||
format: navigator.gpu.getPreferredCanvasFormat(),
|
|
||||||
alphaMode: "premultiplied",
|
|
||||||
});
|
|
||||||
|
|
||||||
const vertices = new Float32Array([1.0, 1.0, 1.0, 1.0]);
|
|
||||||
|
|
||||||
const vertexBuffer = device.createBuffer({
|
|
||||||
size: vertices.byteLength,
|
|
||||||
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
||||||
});
|
|
||||||
|
|
||||||
device.queue.writeBuffer(vertexBuffer, 0, vertices, 0, vertices.length);
|
|
||||||
|
|
||||||
const renderPipeline = device.createRenderPipeline({
|
|
||||||
vertex: {
|
|
||||||
module: shaderModule,
|
|
||||||
entryPoint: 'vertex_main',
|
|
||||||
buffers: [{
|
|
||||||
attributes: [{
|
|
||||||
shaderLocation: 0, // position
|
|
||||||
offset: 0,
|
|
||||||
format: 'float32x4'
|
|
||||||
}, {
|
|
||||||
shaderLocation: 1, // color
|
|
||||||
offset: 16,
|
|
||||||
format: 'float32x4'
|
|
||||||
}],
|
|
||||||
arrayStride: 32,
|
|
||||||
stepMode: 'vertex'
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
fragment: {
|
|
||||||
module: shaderModule,
|
|
||||||
entryPoint: 'fragment_main',
|
|
||||||
targets: [{
|
|
||||||
format: navigator.gpu.getPreferredCanvasFormat()
|
|
||||||
}]
|
|
||||||
},
|
|
||||||
primitive: {
|
|
||||||
topology: 'triangle-list'
|
|
||||||
},
|
|
||||||
layout: 'auto'
|
|
||||||
});
|
|
||||||
const commandEncoder = device.createCommandEncoder();
|
|
||||||
|
|
||||||
const passEncoder = commandEncoder.beginRenderPass({
|
|
||||||
colorAttachments: [{
|
|
||||||
clearValue: {r: 0.0, g: 0.0, b: 0.0, a: 1.0},
|
|
||||||
loadOp: 'clear',
|
|
||||||
storeOp: 'store',
|
|
||||||
view: context.getCurrentTexture().createView()
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
passEncoder.setPipeline(renderPipeline);
|
|
||||||
passEncoder.setVertexBuffer(0, vertexBuffer);
|
|
||||||
passEncoder.draw(3);
|
|
||||||
|
|
||||||
passEncoder.end();
|
|
||||||
device.queue.submit([commandEncoder.finish()]);
|
|
||||||
}
|
|
||||||
321
js/themes.js
@@ -1,321 +0,0 @@
|
|||||||
import {hexToRgb} from "./util.js";
|
|
||||||
|
|
||||||
const constants = {
|
|
||||||
palettes: {
|
|
||||||
"Catppuccin": {
|
|
||||||
"Latte": {
|
|
||||||
"base": "#EFF1F5",
|
|
||||||
"mantle": "#E6E9EF",
|
|
||||||
"crust": "#DCE0E8",
|
|
||||||
|
|
||||||
"text": "#4C4F69",
|
|
||||||
|
|
||||||
"pink": "#EA76CB",
|
|
||||||
"purple": "#8839EF",
|
|
||||||
"red": "#D20F39",
|
|
||||||
"light_red": "#E64553",
|
|
||||||
"orange": "#FE640B",
|
|
||||||
"yellow": "#DF8E1D",
|
|
||||||
"green": "#40A02B",
|
|
||||||
"light_green": "#179299",
|
|
||||||
"blue": "#1E66F5",
|
|
||||||
"light_blue": "#7287FD"
|
|
||||||
},
|
|
||||||
"Frappe": {
|
|
||||||
"base": "#303446",
|
|
||||||
"mantle": "#292C3C",
|
|
||||||
"crust": "#232634",
|
|
||||||
|
|
||||||
"text": "#C6D0F5",
|
|
||||||
|
|
||||||
"pink": "#F4B8E4",
|
|
||||||
"purple": "#CA9EE6",
|
|
||||||
"red": "#E78284",
|
|
||||||
"light_red": "#EA999C",
|
|
||||||
"orange": "#EF9F76",
|
|
||||||
"yellow": "#E5C890",
|
|
||||||
"green": "#A6D189",
|
|
||||||
"light_green": "#81C8BE",
|
|
||||||
"blue": "#8CAAEE",
|
|
||||||
"light_blue": "#85C1DC"
|
|
||||||
},
|
|
||||||
"Macchiato": {
|
|
||||||
"base": "#24273A",
|
|
||||||
"mantle": "#1E2030",
|
|
||||||
"crust": "#181926",
|
|
||||||
|
|
||||||
"text": "#CAD3F5",
|
|
||||||
|
|
||||||
"pink": "#F5bDE6",
|
|
||||||
"purple": "#C6A0F6",
|
|
||||||
"red": "#ED8796",
|
|
||||||
"light_red": "#EE99A0",
|
|
||||||
"orange": "#F5A97F",
|
|
||||||
"yellow": "#EED49F",
|
|
||||||
"green": "#A6DA95",
|
|
||||||
"light_green": "#8BD5CA",
|
|
||||||
"blue": "#8AADF4",
|
|
||||||
"light_blue": "#7DC4E4"
|
|
||||||
},
|
|
||||||
"Mocha": {
|
|
||||||
"base": "#1E1E2E",
|
|
||||||
"mantle": "#181825",
|
|
||||||
"crust": "#11111b",
|
|
||||||
|
|
||||||
"text": "#CDD6F4",
|
|
||||||
|
|
||||||
"pink": "#F5C2E7",
|
|
||||||
"purple": "#CBA6F7",
|
|
||||||
"red": "#F38BA8",
|
|
||||||
"light_red": "#EBA0AC",
|
|
||||||
"orange": "#FAB387",
|
|
||||||
"yellow": "#F9E2AF",
|
|
||||||
"green": "#A6E3A1",
|
|
||||||
"light_green": "#94E2D5",
|
|
||||||
"blue": "#89B4FA",
|
|
||||||
"light_blue": "#74C7EC"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"Arc": {
|
|
||||||
"Dark": {
|
|
||||||
"base": "#282C34",
|
|
||||||
"mantle": "#333842",
|
|
||||||
"crust": "#2C313A",
|
|
||||||
|
|
||||||
"text": "#ABB2BF",
|
|
||||||
|
|
||||||
"pink": "#FF6AC1",
|
|
||||||
"purple": "#D38AEA",
|
|
||||||
"red": "#E06C75",
|
|
||||||
"light_red": "#BE5046",
|
|
||||||
"orange": "#DA8548",
|
|
||||||
"yellow": "#E5D07b",
|
|
||||||
"green": "#98C379",
|
|
||||||
"light_green": "#87BF70",
|
|
||||||
"blue": "#61AFEF",
|
|
||||||
"light_blue": "#4DB5BD"
|
|
||||||
},
|
|
||||||
|
|
||||||
"Light": {
|
|
||||||
"base": "#FAFAFA",
|
|
||||||
"mantle": "#EFF0EB",
|
|
||||||
"crust": "#F2F3F7",
|
|
||||||
|
|
||||||
"text": "#545862",
|
|
||||||
|
|
||||||
"pink": "#FF75A0",
|
|
||||||
"purple": "#BF9EEE",
|
|
||||||
"red": "#F47067",
|
|
||||||
"light_red": "#F47067",
|
|
||||||
"orange": "#FF9F50",
|
|
||||||
"yellow": "#F7BB47",
|
|
||||||
"green": "#6BC46D",
|
|
||||||
"light_green": "#42B983",
|
|
||||||
"blue": "#58A6FF",
|
|
||||||
"light_blue": "#73D0FF"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Material": {
|
|
||||||
"Oceanic": {
|
|
||||||
"base": "#015970",
|
|
||||||
"mantle": "#0a2540",
|
|
||||||
"crust": "#0c3b62",
|
|
||||||
|
|
||||||
"text": "#ECEFF4",
|
|
||||||
|
|
||||||
"pink": "#FF3366",
|
|
||||||
"purple": "#AD82EC",
|
|
||||||
"red": "#FF5370",
|
|
||||||
"light_red": "#F78C6C",
|
|
||||||
"orange": "#FFCB6B",
|
|
||||||
"yellow": "#FAB795",
|
|
||||||
"green": "#C3E88D",
|
|
||||||
"light_green": "#A3BE8C",
|
|
||||||
"blue": "#82AAFF",
|
|
||||||
"light_blue": "#80CBC4",
|
|
||||||
},
|
|
||||||
"Darker": {
|
|
||||||
"base": "#1B262C",
|
|
||||||
"mantle": "#0F4C75",
|
|
||||||
"crust": "#3282B8",
|
|
||||||
|
|
||||||
"text": "#F7F7FF",
|
|
||||||
|
|
||||||
"pink": "#FEB236",
|
|
||||||
"purple": "#6B5B95",
|
|
||||||
"red": "#D81159",
|
|
||||||
"light_red": "#DB504A",
|
|
||||||
"orange": "#FFBC42",
|
|
||||||
"yellow": "#F0F66E",
|
|
||||||
"green": "#0EAD69",
|
|
||||||
"light_green": "#07BEB8",
|
|
||||||
"blue": "#0A9396",
|
|
||||||
"light_blue": "#00B2CA",
|
|
||||||
},
|
|
||||||
"Lighter": {
|
|
||||||
"base": "#EAEAEA",
|
|
||||||
"mantle": "#DADADA",
|
|
||||||
"crust": "#C4C4C4",
|
|
||||||
|
|
||||||
"text": "#141414",
|
|
||||||
|
|
||||||
"pink": "#FFB7C5",
|
|
||||||
"purple": "#D7A9E3",
|
|
||||||
"red": "#FF6363",
|
|
||||||
"light_red": "#FF9A8B",
|
|
||||||
"orange": "#FFD56D",
|
|
||||||
"yellow": "#FAED26",
|
|
||||||
"green": "#A8DF65",
|
|
||||||
"light_green": "#5DD39E",
|
|
||||||
"blue": "#7FD1B9",
|
|
||||||
"light_blue": "#68E8A0",
|
|
||||||
},
|
|
||||||
"Palenight": {
|
|
||||||
"base": "#292D3E",
|
|
||||||
"mantle": "#444267",
|
|
||||||
"crust": "#444267",
|
|
||||||
|
|
||||||
"text": "#ECEFF4",
|
|
||||||
|
|
||||||
"pink": "#FF5370",
|
|
||||||
"purple": "#C792EA",
|
|
||||||
"red": "#FF5370",
|
|
||||||
"light_red": "#F07178",
|
|
||||||
"orange": "#FFCB6B",
|
|
||||||
"yellow": "#FFCB6B",
|
|
||||||
"green": "#C3E88D",
|
|
||||||
"light_green": "#AED581",
|
|
||||||
"blue": "#82AAFF",
|
|
||||||
"light_blue": "#80CBC4",
|
|
||||||
},
|
|
||||||
"Deep Ocean": {
|
|
||||||
"base": "#0D2C54",
|
|
||||||
"mantle": "#144668",
|
|
||||||
"crust": "#1A5684",
|
|
||||||
|
|
||||||
"text": "#CCE7E9",
|
|
||||||
|
|
||||||
"pink": "#F77FBE",
|
|
||||||
"purple": "#B041FF",
|
|
||||||
"red": "#FF4E50",
|
|
||||||
"light_red": "#FF6B6B",
|
|
||||||
"orange": "#FFA07A",
|
|
||||||
"yellow": "#FFD97D",
|
|
||||||
"green": "#00CC99",
|
|
||||||
"light_green": "#00E0B7",
|
|
||||||
"blue": "#6D5DFC",
|
|
||||||
"light_blue": "#1EAE98",
|
|
||||||
},
|
|
||||||
"Forest": {
|
|
||||||
"base": "#0C351E",
|
|
||||||
"mantle": "#1A512D",
|
|
||||||
"crust": "#247035",
|
|
||||||
|
|
||||||
"text": "#E6F1F2",
|
|
||||||
|
|
||||||
"pink": "#FF80AB",
|
|
||||||
"purple": "#8E44AD",
|
|
||||||
"red": "#FF5252",
|
|
||||||
"light_red": "#FF7F7F",
|
|
||||||
"orange": "#FFB74D",
|
|
||||||
"yellow": "#FFEE58",
|
|
||||||
"green": "#2ECC71",
|
|
||||||
"light_green": "#2BD6A6",
|
|
||||||
"blue": "#3498DB",
|
|
||||||
"light_blue": "#5BC0EB",
|
|
||||||
},
|
|
||||||
"Sky Blue": {
|
|
||||||
"base": "#2C3E50",
|
|
||||||
"mantle": "#3F5772",
|
|
||||||
"crust": "#4D6E88",
|
|
||||||
|
|
||||||
"text": "#EAECEE",
|
|
||||||
|
|
||||||
"pink": "#FF6B6B",
|
|
||||||
"purple": "#A37ACC",
|
|
||||||
"red": "#FF7F50",
|
|
||||||
"light_red": "#FF8C68",
|
|
||||||
"orange": "#FFAB70",
|
|
||||||
"yellow": "#FFD570",
|
|
||||||
"green": "#1ABC9C",
|
|
||||||
"light_green": "#20C9A6",
|
|
||||||
"blue": "#3498DB",
|
|
||||||
"light_blue": "#5BC0DE",
|
|
||||||
},
|
|
||||||
"Sandy Beach": {
|
|
||||||
"base": "#FFF1E6",
|
|
||||||
"mantle": "#FFD9B3",
|
|
||||||
"crust": "#FFC196",
|
|
||||||
|
|
||||||
"text": "#4A4A4A",
|
|
||||||
|
|
||||||
"pink": "#FF7675",
|
|
||||||
"purple": "#D6A2E8",
|
|
||||||
"red": "#FDAC53",
|
|
||||||
"light_red": "#FFC85B",
|
|
||||||
"orange": "#F39C12",
|
|
||||||
"yellow": "#FFD200",
|
|
||||||
"green": "#91E763",
|
|
||||||
"light_green": "#A2DE67",
|
|
||||||
"blue": "#69C9D0",
|
|
||||||
"light_blue": "#5ECCDD",
|
|
||||||
},
|
|
||||||
"Volcano": {
|
|
||||||
"base": "#1C1116",
|
|
||||||
"mantle": "#452B30",
|
|
||||||
"crust": "#752F35",
|
|
||||||
|
|
||||||
"text": "#ECEBE4",
|
|
||||||
|
|
||||||
"pink": "#FF7582",
|
|
||||||
"purple": "#D5A5D1",
|
|
||||||
"red": "#FF3F34",
|
|
||||||
"light_red": "#FF6F59",
|
|
||||||
"orange": "#FF974F",
|
|
||||||
"yellow": "#FFD166",
|
|
||||||
"green": "#00818A",
|
|
||||||
"light_green": "#00A69D",
|
|
||||||
"blue": "#1B9AAA",
|
|
||||||
"light_blue": "#6AB187",
|
|
||||||
},
|
|
||||||
"Space": {
|
|
||||||
"base": "#2B2D42",
|
|
||||||
"mantle": "#3C415C",
|
|
||||||
"crust": "#495A75",
|
|
||||||
|
|
||||||
"text": "#D9F0FF",
|
|
||||||
|
|
||||||
"pink": "#FF6A88",
|
|
||||||
"purple": "#D16DFF",
|
|
||||||
"red": "#FF7171",
|
|
||||||
"light_red": "#FF8E8E",
|
|
||||||
"orange": "#FFAF7B",
|
|
||||||
"yellow": "#FFD066",
|
|
||||||
"green": "#76E3D6",
|
|
||||||
"light_green": "#7EE8FA",
|
|
||||||
"blue": "#3282B8",
|
|
||||||
"light_blue": "#74B9FF",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export let current = constants.palettes["Arc"]["Dark"];
|
|
||||||
|
|
||||||
export async function init() {
|
|
||||||
document.documentElement.style.setProperty("--background-color", "#000000ff");
|
|
||||||
// TODO: ... | add theme cookie
|
|
||||||
set("Catppuccin", "Mocha");
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function set(theme, variant) {
|
|
||||||
current = constants.palettes[theme][variant];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function update() {
|
|
||||||
document.documentElement.style.setProperty('--background-color', current.base);
|
|
||||||
for (let key in current) {
|
|
||||||
document.documentElement.style.setProperty(`--${key}`, hexToRgb(current[key]).rgb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
45
js/util.js
@@ -1,45 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
export function hexToRgb(hex) {
|
|
||||||
const
|
|
||||||
red = parseInt(hex.slice(1, 3), 16),
|
|
||||||
green = parseInt(hex.slice(3, 5), 16),
|
|
||||||
blue = parseInt(hex.slice(5, 7), 16);
|
|
||||||
return {
|
|
||||||
red: red,
|
|
||||||
green: green,
|
|
||||||
blue: blue,
|
|
||||||
rgb: `${red}, ${green}, ${blue}`,
|
|
||||||
hex: hex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function hexToRgba(hex) {
|
|
||||||
const
|
|
||||||
red = parseInt(hex.slice(1, 3), 16),
|
|
||||||
green = parseInt(hex.slice(3, 5), 16),
|
|
||||||
blue = parseInt(hex.slice(5, 7), 16),
|
|
||||||
alpha = parseInt(hex.slice(7, 9), 16);
|
|
||||||
return {
|
|
||||||
red: red,
|
|
||||||
green: green,
|
|
||||||
blue: blue,
|
|
||||||
alpha: alpha,
|
|
||||||
rgb: `${red}, ${green}, ${blue}`,
|
|
||||||
rgba: `${red}, ${green}, ${blue}, ${alpha}`,
|
|
||||||
hex: hex
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isWinter(date = new Date()) {
|
|
||||||
const month = date.getMonth();
|
|
||||||
return month === 11 ? date.getDay() >= 21 : month === 2 ? date.getDay() < 21 : month < 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isXmas(date = new Date()) {
|
|
||||||
return date.getMonth() === 11 && (date.getDay() >= 24 && date.getDay() <= 26);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isHalloween(date = new Date()) {
|
|
||||||
return date.getMonth() === 10 && date.getDay() === 31;
|
|
||||||
}
|
|
||||||
13
justfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env just --justfile
|
||||||
|
|
||||||
|
setup:
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
|
||||||
|
install:
|
||||||
|
cargo install --locked wasm-pack trunk --force
|
||||||
|
|
||||||
|
debug:
|
||||||
|
trunk serve
|
||||||
|
|
||||||
|
build:
|
||||||
|
trunk build --release
|
||||||
237
main.css
@@ -1,237 +0,0 @@
|
|||||||
@charset "UTF-8";
|
|
||||||
|
|
||||||
@import url("https://cdn.jsdelivr.net/npm/bootstrap-icons/font/bootstrap-icons.min.css");
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: Comfortaa;
|
|
||||||
src: url("assets/fonts/comfortaa.ttf");
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--base: 0, 0, 0;
|
|
||||||
--mantle: 0, 0, 0;
|
|
||||||
--crust: 0, 0, 0;
|
|
||||||
--text: 0, 0, 0;
|
|
||||||
--pink: 0, 0, 0;
|
|
||||||
--purple: 0, 0, 0;
|
|
||||||
--red: 0, 0, 0;
|
|
||||||
--light_red: 0, 0, 0;
|
|
||||||
--orange: 0, 0, 0;
|
|
||||||
--yellow: 0, 0, 0;
|
|
||||||
--green: 0, 0, 0;
|
|
||||||
--light_green: 0, 0, 0;
|
|
||||||
--blue: 0, 0, 0;
|
|
||||||
--light_blue: 0, 0, 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes home {
|
|
||||||
/* @formatter:off */
|
|
||||||
from { opacity: 0 !important; }
|
|
||||||
to { opacity: 1 !important; }
|
|
||||||
/* @formatter:on */
|
|
||||||
}
|
|
||||||
|
|
||||||
#profile-card {
|
|
||||||
margin: 0;
|
|
||||||
image-orientation: from-image;
|
|
||||||
image-rendering: optimizeQuality;
|
|
||||||
image-resolution: from-image;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation {
|
|
||||||
text-align: center;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation button {
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
background-color: rgba(var(--base), 0.6125);
|
|
||||||
border-color: rgba(var(--blue), 0.0);
|
|
||||||
border-style: inset !important;
|
|
||||||
border-width: thin !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration-color: rgba(var(--text), 1.0);
|
|
||||||
padding: 2px 4px;
|
|
||||||
margin-top: 12px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.oneko {
|
|
||||||
image-rendering: optimizeQuality;
|
|
||||||
image-orientation: from-image;
|
|
||||||
image-resolution: from-image;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-flow: wrap;
|
|
||||||
text-align: left;
|
|
||||||
margin-left: 8px;
|
|
||||||
align-self: self-start;
|
|
||||||
justify-content: left;
|
|
||||||
justify-items: left;
|
|
||||||
justify-self: left;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#navigation button:hover {
|
|
||||||
border-color: rgba(var(--text), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
backdrop-filter: blur(2px);
|
|
||||||
background-color: rgba(var(--base), 0.4125);
|
|
||||||
border-radius: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card hr {
|
|
||||||
margin: 0.5em auto !important;
|
|
||||||
border-style: inset !important;
|
|
||||||
border-width: thin !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socials {
|
|
||||||
text-align: right;
|
|
||||||
justify-content: right;
|
|
||||||
justify-items: right;
|
|
||||||
justify-self: right;
|
|
||||||
margin-right: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socials p {
|
|
||||||
text-align: left;
|
|
||||||
font-style: italic;
|
|
||||||
display: inline;
|
|
||||||
float: left;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: 6px;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
justify-items: left;
|
|
||||||
justify-self: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socials i {
|
|
||||||
font-size: large;
|
|
||||||
margin: 0;
|
|
||||||
font-kerning: normal;
|
|
||||||
text-align: center;
|
|
||||||
text-rendering: optimizeLegibility;
|
|
||||||
text-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
|
|
||||||
transition: text-shadow 0.6s cubic-bezier(0.76, 0, 0.24, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.socials i:hover {
|
|
||||||
animation: socials-in 0.6s forwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
.socials i:not(:hover) {
|
|
||||||
animation: socials-out 0.6s backwards;
|
|
||||||
}
|
|
||||||
|
|
||||||
html * {
|
|
||||||
font-family: Comfortaa, "Verdana Pro", "Verdana", "Courier New", Courier, monospace;
|
|
||||||
color: rgb(var(--text));
|
|
||||||
scrollbar-color: rgb(var(--blue)) rgb(var(--mantle));
|
|
||||||
scrollbar-width: auto;
|
|
||||||
z-index: 1 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.profile-bio {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
canvas {
|
|
||||||
background-color: var(--base);
|
|
||||||
display: block !important;
|
|
||||||
position: absolute !important;
|
|
||||||
left: 0 !important;
|
|
||||||
top: 0 !important;
|
|
||||||
z-index: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect {
|
|
||||||
shape-rendering: geometricPrecision;
|
|
||||||
fill-opacity: 0.1;
|
|
||||||
stroke: black;
|
|
||||||
stroke-width: 2px;
|
|
||||||
stroke-opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background-color: rgb(var(--mantle));
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: rgb(var(--blue));
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: rgba(var(--base), 1);
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
justify-content: center;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
width: auto;
|
|
||||||
height: max-content;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-selection {
|
|
||||||
background: rgba(var(--blue), 0.3125);
|
|
||||||
color: rgb(var(--text));
|
|
||||||
}
|
|
||||||
|
|
||||||
::selection {
|
|
||||||
background: rgba(var(--blue), 0.3125);
|
|
||||||
color: rgb(var(--text));
|
|
||||||
}
|
|
||||||
|
|
||||||
.flexcard {
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
height: calc(100vh - 16px);
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes socials-in {
|
|
||||||
from {
|
|
||||||
text-shadow: 0 0 0 #fff,
|
|
||||||
0 0 1px #fff;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
text-shadow: 0 0 4px #fff,
|
|
||||||
0 0 4px #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes socials-out {
|
|
||||||
from {
|
|
||||||
text-shadow: 0 0 4px #fff,
|
|
||||||
0 0 4px #fff;
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
text-shadow: 0 0 0 #fff,
|
|
||||||
0 0 1px #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#cards {
|
|
||||||
flex-direction: column;
|
|
||||||
flex-flow: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#contents {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
81
main.scss
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
@charset "UTF-8";
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: freemono;
|
||||||
|
src: url("freemono.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: monocraft;
|
||||||
|
src: url("monocraft.ttf");
|
||||||
|
}
|
||||||
|
|
||||||
|
html * {
|
||||||
|
font-family: freemono, monocraft, monospace !important;
|
||||||
|
scrollbar-color: hsla(232, 97%, 85%, 0.5) hsla(240, 21.05%, 14.9%, 0.7) !important;
|
||||||
|
scrollbar-width: thin !important;
|
||||||
|
scroll-behavior: smooth !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea {
|
||||||
|
-ms-overflow-style: none !important;
|
||||||
|
scrollbar-width: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.textarea::-webkit-scrollbar {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box {
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
backdrop-filter: blur(4px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input,
|
||||||
|
.select select,
|
||||||
|
.textarea {
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
background-color: white, 0.213125 !important;
|
||||||
|
border-color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.navbar-item,
|
||||||
|
a.navbar-item:hover {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-moz-selection {
|
||||||
|
background: #F5C2E740 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
::selection {
|
||||||
|
background: #F5C2E740 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #F5C2E7FF !important;
|
||||||
|
text-decoration: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
filter: url("post.bloom.svg#process") !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border-color: transparent !important;
|
||||||
|
color: white !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
text-decoration: underline !important;
|
||||||
|
filter: url("post.bloom.svg#process") !important;
|
||||||
|
}
|
||||||
BIN
public/blog/testimage.jpg
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
BIN
public/buttons/aqueer.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
public/buttons/artix.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/buttons/chimera.png
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
public/buttons/csharp.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/buttons/dataforest.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
public/buttons/fedora.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/buttons/iouring.png
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
public/buttons/java.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
BIN
public/buttons/kotlin.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/buttons/nixos.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
public/buttons/rust.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
public/buttons/servfail.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
public/buttons/void.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
public/buttons/wasm.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
public/misc/404.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/misc/favicon.ico
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
public/misc/freemono.ttf
Normal file
BIN
public/misc/monocraft.ttf
Normal file
|
Before Width: | Height: | Size: 309 B After Width: | Height: | Size: 315 B |
BIN
public/misc/profile.avif
Normal file
|
After Width: | Height: | Size: 224 KiB |
179
public/shaders/es3/bg.frag
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
const float CAM_FAR = 20.0;
|
||||||
|
const vec3 BACKGROUND = vec3(0.0, 0.0, 0.0);
|
||||||
|
|
||||||
|
uniform float time;
|
||||||
|
uniform vec2 resolution;
|
||||||
|
uniform bool layer_snow;
|
||||||
|
|
||||||
|
in vec4 vColor;
|
||||||
|
layout (location = 0) out vec4 fragColor;
|
||||||
|
|
||||||
|
vec3 artifactOffset;
|
||||||
|
mat3 artifactRotation;
|
||||||
|
vec3 artifactAxis;
|
||||||
|
vec3 camFwd;
|
||||||
|
vec3 camUp;
|
||||||
|
|
||||||
|
float rand(float n) {
|
||||||
|
return fract(sin(n) * 43758.5453);
|
||||||
|
}
|
||||||
|
|
||||||
|
float noise(float x) {
|
||||||
|
float i = floor(x);
|
||||||
|
float f = fract(x);
|
||||||
|
return mix(rand(i), rand(i + 1.0), f * f * (3.0 - 2.0 * f));
|
||||||
|
}
|
||||||
|
|
||||||
|
mat4 viewMatrix(vec3 dir, vec3 up) {
|
||||||
|
vec3 f = normalize(dir);
|
||||||
|
vec3 s = normalize(cross(f, up));
|
||||||
|
vec3 u = cross(s, f);
|
||||||
|
|
||||||
|
return mat4(
|
||||||
|
vec4(s, 0.0),
|
||||||
|
vec4(u, 0.0),
|
||||||
|
vec4(-f, 0.0),
|
||||||
|
vec4(0.0, 0.0, 0.0, 1.0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mat3 rotationAlign(vec3 d, vec3 z) {
|
||||||
|
vec3 v = cross(z, d);
|
||||||
|
float c = dot(z, d);
|
||||||
|
float k = 1.0 / (1.0 + c);
|
||||||
|
|
||||||
|
float k_vx = v.x * k;
|
||||||
|
float k_vy = v.y * k;
|
||||||
|
float k_vz = v.z * k;
|
||||||
|
|
||||||
|
return mat3(
|
||||||
|
k_vx * v.x + c, k_vx * v.y - v.z, k_vx * v.z + v.y,
|
||||||
|
k_vy * v.x + v.z, k_vy * v.y + c, k_vy * v.z - v.x,
|
||||||
|
k_vz * v.x - v.y, k_vz * v.y + v.x, k_vz * v.z + c
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
float intersectPlane(vec3 origin, vec3 direction, vec3 point, vec3 normal) {
|
||||||
|
return clamp(dot(point - origin, normal) / dot(direction, normal), -1.0, 9991999.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 calcRay(vec2 uv, float fov, float aspect) {
|
||||||
|
uv = uv * 2.0 - 1.0;
|
||||||
|
float d = 1.0 / tan(radians(fov) * 0.5);
|
||||||
|
return normalize(vec3(aspect * uv.x, uv.y, d));
|
||||||
|
}
|
||||||
|
|
||||||
|
float octahedron(vec3 p, float s) {
|
||||||
|
const float factor = 0.57735027;
|
||||||
|
p = abs(p);
|
||||||
|
return (p.x + p.y + p.z - s) * factor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void artifact(vec3 p, inout float currDist, inout vec3 glowColor, inout int id) {
|
||||||
|
p -= artifactOffset;
|
||||||
|
p = artifactRotation * p;
|
||||||
|
float dist = octahedron(p, 0.8);
|
||||||
|
const float glowDist = 4.8;
|
||||||
|
if (dist < glowDist) {
|
||||||
|
float d = dist + rand(dist) * 1.7;
|
||||||
|
glowColor += vec3(0.75, 0.55, 0.45) * clamp(1.0 - pow((d * (1.0 / glowDist)), 5.0), 0.0, 1.0) * 0.035;
|
||||||
|
}
|
||||||
|
if (dist < currDist) {
|
||||||
|
currDist = dist;
|
||||||
|
id = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float objects(vec3 p, inout vec3 glowColor, inout int objId) {
|
||||||
|
float dist = CAM_FAR;
|
||||||
|
artifact(p, dist, glowColor, objId);
|
||||||
|
return dist;
|
||||||
|
}
|
||||||
|
|
||||||
|
float artifactDist(vec3 p) {
|
||||||
|
p -= artifactOffset;
|
||||||
|
p = artifactRotation * p;
|
||||||
|
return octahedron(p, 1.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 objectsNormal(vec3 p, float eps) {
|
||||||
|
vec2 h = vec2(eps, 0);
|
||||||
|
return normalize(vec3(artifactDist(p + h.xyy) - artifactDist(p - h.xyy), eps * 2.0, artifactDist(p + h.yyx) - artifactDist(p - h.yyx)));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 objectsColor(int id, vec3 normal, vec3 ray) {
|
||||||
|
return id == 1 ? vec3(0.85, 0.65, 0.55) * mix(0.8, 1.5, dot(normal, normalize(vec3(0.0, 1.0, 0.5))) * 0.5 + 0.5) :
|
||||||
|
id == 2 ? vec3(0.85, 0.65, 0.55) * 1.5 :
|
||||||
|
vec3(1.0, 1.0, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void marchObjects(vec3 eye, vec3 ray, float wDepth, inout vec4 color) {
|
||||||
|
float dist = 0.0;
|
||||||
|
int id;
|
||||||
|
vec3 rayPos = eye;
|
||||||
|
float depth = CAM_FAR;
|
||||||
|
for (int i = 0; i < 30; i++) {
|
||||||
|
dist = objects(rayPos, color.rgb, id);
|
||||||
|
depth = distance(rayPos, eye);
|
||||||
|
if (depth > wDepth || dist < 0.01) break;
|
||||||
|
rayPos += ray * dist;
|
||||||
|
}
|
||||||
|
color = dist < 0.01 ? vec4(objectsColor(id, objectsNormal(rayPos, 0.01), ray), depth) : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
vec3 march(vec2 uv, vec3 camPos) {
|
||||||
|
mat4 vm = viewMatrix(camFwd, camUp);
|
||||||
|
vec3 ray = (vm * vec4(calcRay(uv, 80.0, resolution.x / resolution.y), 1.0)).xyz;
|
||||||
|
vec4 color = vec4(BACKGROUND, CAM_FAR);
|
||||||
|
marchObjects(camPos, ray, color.w, color);
|
||||||
|
return color.rgb;
|
||||||
|
}
|
||||||
|
|
||||||
|
float snow(vec2 uv, float scale) {
|
||||||
|
float w = smoothstep(1.0, 0.0, -uv.y * (scale * 0.01));
|
||||||
|
if (w < 0.1) return 0.0;
|
||||||
|
|
||||||
|
float timeScale = time / scale;
|
||||||
|
uv += vec2(timeScale, timeScale);
|
||||||
|
uv.x += sin(uv.y + time * 0.125) / scale;
|
||||||
|
uv *= scale;
|
||||||
|
|
||||||
|
vec2 s = floor(uv);
|
||||||
|
vec2 f = fract(uv);
|
||||||
|
|
||||||
|
float sinValueX = sin((s.x + scale) * 7.0) * 5.0;
|
||||||
|
float sinValueY = sin((s.y + scale) * 5.0) * 5.0;
|
||||||
|
float combinedSinValue = sinValueX + sinValueY;
|
||||||
|
|
||||||
|
float lengthValue = length(0.5 + 0.5 * sin(11.0 * fract(combinedSinValue)) - f);
|
||||||
|
|
||||||
|
return smoothstep(0.0, min(lengthValue, 3.0), sin(f.x + f.y) * 0.01) * w;
|
||||||
|
}
|
||||||
|
|
||||||
|
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
|
||||||
|
vec2 uv = fragCoord / resolution.xy;
|
||||||
|
float s = sin(time);
|
||||||
|
float c = cos(time);
|
||||||
|
|
||||||
|
artifactRotation = mat3(c, 0, s, 0, 1, 0, -s, 0, c) * rotationAlign(vec3(0.0, 1.0, 0.0), vec3(s * 0.2, 1.0, c * 0.2 + 0.3));
|
||||||
|
artifactOffset = vec3(s * 0.4, c * 0.3 - 1.7, -6.0);
|
||||||
|
|
||||||
|
camFwd = vec3(0.0, 0.7 + noise(time * 0.8 + 4.0) * 0.08 - 0.04, 1.0);
|
||||||
|
camUp = vec3(noise(time * 1.2) * 0.02 - 0.01, 1.0, 0.0);
|
||||||
|
|
||||||
|
fragColor = vec4(march(uv, vec3(0.0, 1.9, 1.0)) - (length(uv - 0.5) - 0.3) * 0.05, 1.0);
|
||||||
|
|
||||||
|
if (layer_snow) {
|
||||||
|
vec2 uvSnow = (fragCoord * 2.0 - resolution.xy) / min(resolution.x, resolution.y);
|
||||||
|
fragColor += vec4(vec3(snow(uvSnow, 4.0)), 0.5) * 0.7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = vColor;
|
||||||
|
mainImage(fragColor, gl_FragCoord.xy);
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
#version 300 es
|
#version 300 es
|
||||||
|
|
||||||
layout(location = 0) in vec3 position;
|
layout(location = 0) in vec3 position;
|
||||||
layout(location = 1) in vec4 color;
|
layout(location = 1) in vec4 color;
|
||||||
|
|
||||||
@@ -7,6 +6,6 @@ uniform mat4 model;
|
|||||||
out vec4 vColor;
|
out vec4 vColor;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
vColor = color;
|
vColor = color;
|
||||||
gl_Position = model * vec4(position, 1.0);
|
gl_Position = model * vec4(position, 1.0);
|
||||||
}
|
}
|
||||||
25
public/shaders/post.bloom.svg
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" width="0" height="0">
|
||||||
|
<filter id="process">
|
||||||
|
<feGaussianBlur stdDeviation="1"/>
|
||||||
|
<feComponentTransfer>
|
||||||
|
<feFuncR type="identity"/>
|
||||||
|
<feFuncG type="identity"/>
|
||||||
|
<feFuncB type="identity"/>
|
||||||
|
<feFuncA type="identity"/>
|
||||||
|
</feComponentTransfer>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
<feGaussianBlur stdDeviation="2"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
<feGaussianBlur stdDeviation="3"/>
|
||||||
|
<feMerge>
|
||||||
|
<feMergeNode/>
|
||||||
|
<feMergeNode in="SourceGraphic"/>
|
||||||
|
</feMerge>
|
||||||
|
</filter>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 691 B |
@@ -1,28 +0,0 @@
|
|||||||
<div class="home" id="intro"><p>
|
|
||||||
todo! :)
|
|
||||||
</p></div>
|
|
||||||
<style>
|
|
||||||
@import url(main.css);
|
|
||||||
|
|
||||||
.home {
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-flow: row;
|
|
||||||
animation: home 1s forwards;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#intro p {
|
|
||||||
background: linear-gradient(135deg,
|
|
||||||
#55cdfc, #55cdfc,
|
|
||||||
#f7a8b8, #f7a8b8,
|
|
||||||
#ffffff, #ffffff);
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<div class="home" id="intro"><p>
|
|
||||||
Hey, I am Lucielle, welcome. ♡<br>
|
|
||||||
she/her • developer • artist<br>
|
|
||||||
</p></div>
|
|
||||||
<style>
|
|
||||||
@import url(main.css);
|
|
||||||
|
|
||||||
.home {
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
flex-flow: row;
|
|
||||||
animation: home 1s forwards;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#intro p {
|
|
||||||
background: linear-gradient(135deg,
|
|
||||||
#55cdfc, #55cdfc,
|
|
||||||
#f7a8b8, #f7a8b8,
|
|
||||||
#ffffff, #ffffff);
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
<div class="portfolio">
|
|
||||||
<img alt="waow.png" height="180" id="portfolio-preview" src="" width="320"/>
|
|
||||||
<div id="portfolio-navi">
|
|
||||||
<p id="portfolio-title"></p>
|
|
||||||
<div id="portfolio-navi-btns">
|
|
||||||
<button id="portfolio-preview-previous" type="button"><</button>
|
|
||||||
<button id="portfolio-preview-next" type="button">></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p id="portfolio-description"></p>
|
|
||||||
</div>
|
|
||||||
<style>
|
|
||||||
@import url(main.css);
|
|
||||||
|
|
||||||
.portfolio {
|
|
||||||
display: flex;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-flow: column;
|
|
||||||
animation: home 1s forwards;
|
|
||||||
justify-content: center;
|
|
||||||
justify-items: center;
|
|
||||||
justify-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-preview {
|
|
||||||
image-rendering: optimizeQuality;
|
|
||||||
image-orientation: from-image;
|
|
||||||
image-resolution: from-image;
|
|
||||||
margin-top: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.portfolio button {
|
|
||||||
backdrop-filter: blur(4px);
|
|
||||||
background-color: rgba(var(--base), 0.6125);
|
|
||||||
border-color: rgba(var(--blue), 0.0);
|
|
||||||
border-style: inset !important;
|
|
||||||
border-width: thin !important;
|
|
||||||
border-radius: 4px;
|
|
||||||
text-decoration-color: rgba(var(--text), 1.0);
|
|
||||||
padding: 2px 4px;
|
|
||||||
margin-top: 12px;
|
|
||||||
text-align: center;
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.portfolio button:hover {
|
|
||||||
border-color: rgba(var(--text), 1.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-title {
|
|
||||||
text-align: left;
|
|
||||||
margin-left: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-description {
|
|
||||||
flex-direction: row;
|
|
||||||
flex-flow: wrap;
|
|
||||||
justify-content: space-around;
|
|
||||||
text-align: left;
|
|
||||||
margin-top: 0;
|
|
||||||
max-width: 320px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-navi {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-flow: wrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-navi-btns {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-flow: wrap;
|
|
||||||
justify-content: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#portfolio-navi-btns button {
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.portfolio button {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
0
src/lib.rs
Normal file
480
src/main.rs
Normal file
@@ -0,0 +1,480 @@
|
|||||||
|
#![feature(iter_intersperse)]
|
||||||
|
extern crate core;
|
||||||
|
|
||||||
|
use js_sys::Float32Array;
|
||||||
|
use log::{info, warn};
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext as GL2, window};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
mod pages;
|
||||||
|
use crate::pages::about::About;
|
||||||
|
use crate::pages::blog::author::Author;
|
||||||
|
use crate::pages::blog::authors::Authors;
|
||||||
|
use crate::pages::blog::entries::Entries;
|
||||||
|
use crate::pages::blog::entry::Entry;
|
||||||
|
use crate::pages::findme::FindMe;
|
||||||
|
use crate::pages::projects::projects::Projects;
|
||||||
|
use pages::not_found::PageNotFound;
|
||||||
|
|
||||||
|
#[derive(Routable, PartialEq, Eq, Clone, Debug)]
|
||||||
|
pub enum Route {
|
||||||
|
#[at("/")]
|
||||||
|
About,
|
||||||
|
|
||||||
|
#[at("/blog/entries/:id")]
|
||||||
|
Entry { id: u8 },
|
||||||
|
#[at("/blog/entries")]
|
||||||
|
Entries,
|
||||||
|
#[at("/blog/authors/:id")]
|
||||||
|
Author { id: u8 },
|
||||||
|
#[at("/blog/authors")]
|
||||||
|
Authors,
|
||||||
|
|
||||||
|
#[at("/projects")]
|
||||||
|
Projects,
|
||||||
|
|
||||||
|
#[at("/findme")]
|
||||||
|
FindMe,
|
||||||
|
|
||||||
|
#[not_found]
|
||||||
|
#[at("/404")]
|
||||||
|
NotFound,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct App {
|
||||||
|
node_ref: NodeRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for App {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
node_ref: NodeRef::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<canvas width="100%" height="100%" style="
|
||||||
|
background-color: black !important;
|
||||||
|
position: fixed !important;
|
||||||
|
left: 0 !important;
|
||||||
|
top: 0 !important;
|
||||||
|
width: 100vw !important;
|
||||||
|
height: 100vh !important;
|
||||||
|
z-index: 0 !important;
|
||||||
|
" ref={self.node_ref.clone()} />
|
||||||
|
<BrowserRouter>
|
||||||
|
<header style="
|
||||||
|
z-index: 2 !important;
|
||||||
|
display: flex !important;
|
||||||
|
color: white !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
max-width: 100vw !important;
|
||||||
|
min-height: 20px !important;
|
||||||
|
height: 20px !important;
|
||||||
|
max-height: 20px !important;
|
||||||
|
filter: url(post.bloom.svg) !important;
|
||||||
|
">
|
||||||
|
<h3>{r"»[ "}</h3>
|
||||||
|
<h3><Link<Route> to={Route::About}>{r"about"}</Link<Route>></h3>
|
||||||
|
<h3>{r"|"}</h3>
|
||||||
|
<h3><Link<Route> to={Route::Entries}>{r"blog"}</Link<Route>></h3>
|
||||||
|
<h3>{r"|"}</h3>
|
||||||
|
<h3><Link<Route> to={Route::FindMe}>{r"findme"}</Link<Route>></h3>
|
||||||
|
<h3>{r"|"}</h3>
|
||||||
|
<h3><Link<Route> to={Route::Projects}>{r"projects"}</Link<Route>></h3>
|
||||||
|
<h3>{r" ]«"}</h3>
|
||||||
|
</header>
|
||||||
|
<main style="
|
||||||
|
z-index: 1 !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
color: white !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
min-width: fit-content !important;
|
||||||
|
width: calc(100vw - 18 px) !important;
|
||||||
|
flex-wrap: nowrap !important;
|
||||||
|
justify-content: space-between !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
align-self: center !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
max-height: calc(100vh - 42px) !important;
|
||||||
|
">
|
||||||
|
/*
|
||||||
|
min-height: 70vh !important;
|
||||||
|
height: 80vw !important;
|
||||||
|
*/
|
||||||
|
<div style="
|
||||||
|
background-color: rgba(0, 0, 0, 0.3125) !important;
|
||||||
|
backdrop-filter: blur(6px) !important;
|
||||||
|
padding: 4px 0px 0px 4px !important;
|
||||||
|
border-radius: 1.0rem !important;
|
||||||
|
border-style: solid !important;
|
||||||
|
border-color: black !important;
|
||||||
|
border-width: 1px !important;
|
||||||
|
z-index: 1 !important;
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
overflow-y: auto !important;
|
||||||
|
min-width: 70vw !important;
|
||||||
|
width: 70vh !important;
|
||||||
|
max-width: calc(100vw - 18px) !important;
|
||||||
|
height: 100% !important;
|
||||||
|
margin-top: auto !important;
|
||||||
|
margin-bottom: auto !important;
|
||||||
|
scrollbar-width: thin !important;
|
||||||
|
scrollbar-color: transparent transparent !important;
|
||||||
|
"><Switch<Route> render={switch} />
|
||||||
|
{self.view_buttons()}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</BrowserRouter>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rendered(&mut self, _ctx: &Context<Self>, first_render: bool) {
|
||||||
|
if first_render {
|
||||||
|
self.view_canvas();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn switch(routes: Route) -> Html {
|
||||||
|
match routes {
|
||||||
|
Route::About => {
|
||||||
|
html! { <About /> }
|
||||||
|
}
|
||||||
|
|
||||||
|
Route::Entries => {
|
||||||
|
html! { <Entries /> }
|
||||||
|
}
|
||||||
|
Route::Entry { id } => {
|
||||||
|
html! { <Entry id={id as u8} /> }
|
||||||
|
}
|
||||||
|
Route::Authors => {
|
||||||
|
html! { <Authors /> }
|
||||||
|
}
|
||||||
|
Route::Author { id } => {
|
||||||
|
html! { <Author id={id as u8} /> }
|
||||||
|
}
|
||||||
|
|
||||||
|
Route::FindMe => {
|
||||||
|
html! { <FindMe /> }
|
||||||
|
}
|
||||||
|
|
||||||
|
Route::Projects => {
|
||||||
|
html! { <Projects /> }
|
||||||
|
}
|
||||||
|
Route::NotFound => {
|
||||||
|
html! { <PageNotFound /> }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl App {
|
||||||
|
fn view_buttons(&self) -> Html {
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
z-index: 1 !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
min-width: 70vw !important;
|
||||||
|
width: 70vh !important;
|
||||||
|
max-width: calc(100vw - 18px) !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
overflow: hidden !important;
|
||||||
|
margin-right: auto !important;
|
||||||
|
margin-left: auto !important;
|
||||||
|
">
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://i0ur.ing/"><img loading="eager" alt="iouring" src="iouring.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://webassembly.org/"><img loading="eager" alt="wasm" src="wasm.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://beta.servfail.network/"><img loading="eager" alt="servfail" src="servfail.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://www.dataforest.net/en/"><img loading="eager" alt="dataforest" src="dataforest.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://fedoraproject.org/"><img loading="eager" alt="fedora" src="fedora.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://artixlinux.org/"><img loading="eager" alt="artix" src="artix.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://nixos.org/"><img loading="eager" alt="nixos" src="nixos.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://chimera-linux.org/"><img loading="eager" alt="void" src="chimera.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://voidlinux.org/"><img loading="eager" alt="void" src="void.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://www.rust-lang.org/"><img loading="eager" alt="rust" src="rust.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://dotnet.microsoft.com/en-us/"><img loading="eager" alt="csharp" src="csharp.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://kotlinlang.org/"><img loading="eager" alt="kotlin" src="kotlin.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
margin-right: 0.2rem !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
" href="https://www.java.com/"><img loading="eager" alt="java" src="java.png" /></a>
|
||||||
|
<a style="
|
||||||
|
width: 88px !important;
|
||||||
|
height: 31px !important;
|
||||||
|
image-rendering: pixelated !important;
|
||||||
|
image-resolution: from-image !important;
|
||||||
|
image-orientation: from-image !important;
|
||||||
|
"><img loading="eager" alt="aqueer" src="aqueer.png" /></a>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view_canvas(&self) {
|
||||||
|
let canvas = self.node_ref.cast::<HtmlCanvasElement>().unwrap();
|
||||||
|
|
||||||
|
let gl = canvas
|
||||||
|
.get_context("webgl2")
|
||||||
|
.unwrap_or(canvas.get_context("webgl").unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<GL2>()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let vertex_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||||
|
gl.buffer_data_with_array_buffer_view(
|
||||||
|
GL2::ARRAY_BUFFER,
|
||||||
|
&Float32Array::from(
|
||||||
|
vec![
|
||||||
|
-1.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1.0, 0.0,
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
),
|
||||||
|
GL2::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let color_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||||
|
gl.buffer_data_with_array_buffer_view(
|
||||||
|
GL2::ARRAY_BUFFER,
|
||||||
|
&Float32Array::from(
|
||||||
|
vec![
|
||||||
|
1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 1.0,
|
||||||
|
]
|
||||||
|
.as_slice(),
|
||||||
|
),
|
||||||
|
GL2::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let element_buffer = gl.create_buffer().unwrap();
|
||||||
|
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||||
|
let element_indices = vec![3, 2, 1, 3, 1, 0];
|
||||||
|
gl.buffer_data_with_array_buffer_view(
|
||||||
|
GL2::ELEMENT_ARRAY_BUFFER,
|
||||||
|
&js_sys::Uint16Array::from(element_indices.as_slice()),
|
||||||
|
GL2::STATIC_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
let vertex_shader_code = include_str!("../public/shaders/es3/bg.vert");
|
||||||
|
// info!("{}", vertex_shader_code);
|
||||||
|
let vertex_shader = gl.create_shader(GL2::VERTEX_SHADER).unwrap();
|
||||||
|
gl.shader_source(&vertex_shader, vertex_shader_code);
|
||||||
|
gl.compile_shader(&vertex_shader);
|
||||||
|
let vertex_shader_log = gl.get_shader_info_log(&vertex_shader);
|
||||||
|
if !vertex_shader_log.as_ref().unwrap().is_empty() {
|
||||||
|
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let fragment_shader_code = include_str!("../public/shaders/es3/bg.frag");
|
||||||
|
let fragment_shader = gl.create_shader(GL2::FRAGMENT_SHADER).unwrap();
|
||||||
|
gl.shader_source(&fragment_shader, fragment_shader_code);
|
||||||
|
gl.compile_shader(&fragment_shader);
|
||||||
|
let fragment_shader_log = gl.get_shader_info_log(&fragment_shader);
|
||||||
|
if !fragment_shader_log.as_ref().unwrap().is_empty() {
|
||||||
|
info!("{}", vertex_shader_log.as_ref().unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let shader_program = gl.create_program().unwrap();
|
||||||
|
|
||||||
|
gl.attach_shader(&shader_program, &vertex_shader);
|
||||||
|
gl.attach_shader(&shader_program, &fragment_shader);
|
||||||
|
|
||||||
|
gl.link_program(&shader_program);
|
||||||
|
if !gl
|
||||||
|
.get_program_parameter(&shader_program, GL2::LINK_STATUS)
|
||||||
|
.as_bool()
|
||||||
|
.unwrap()
|
||||||
|
{
|
||||||
|
warn!("shader couldn't be linked!");
|
||||||
|
}
|
||||||
|
gl.use_program(Some(&shader_program));
|
||||||
|
|
||||||
|
let position = gl.get_attrib_location(&shader_program, "position") as u32;
|
||||||
|
gl.enable_vertex_attrib_array(position);
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&vertex_buffer));
|
||||||
|
gl.vertex_attrib_pointer_with_i32(position, 3, GL2::FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
let color = gl.get_attrib_location(&shader_program, "color") as u32;
|
||||||
|
gl.enable_vertex_attrib_array(color);
|
||||||
|
gl.bind_buffer(GL2::ARRAY_BUFFER, Some(&color_buffer));
|
||||||
|
gl.vertex_attrib_pointer_with_i32(color, 4, GL2::FLOAT, false, 0, 0);
|
||||||
|
|
||||||
|
gl.bind_buffer(GL2::ELEMENT_ARRAY_BUFFER, Some(&element_buffer));
|
||||||
|
|
||||||
|
gl.enable(GL2::DEPTH_TEST);
|
||||||
|
gl.clear_color(0.0, 0.0, 0.0, 1.0);
|
||||||
|
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||||
|
|
||||||
|
let default_matrix = vec![
|
||||||
|
1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
|
||||||
|
];
|
||||||
|
let unim_matrix = gl.get_uniform_location(&shader_program, "model");
|
||||||
|
let unim_time = gl.get_uniform_location(&shader_program, "time");
|
||||||
|
let unim_res = gl.get_uniform_location(&shader_program, "resolution");
|
||||||
|
let unim_overlay_time = gl.get_uniform_location(&shader_program, "layer_snow");
|
||||||
|
|
||||||
|
let mut timestamp = 0.0;
|
||||||
|
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||||
|
gl.draw_elements_with_i32(
|
||||||
|
GL2::TRIANGLES,
|
||||||
|
element_indices.len() as i32,
|
||||||
|
GL2::UNSIGNED_SHORT,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
fn request_animation_frame(f: &Closure<dyn FnMut()>) {
|
||||||
|
window()
|
||||||
|
.unwrap()
|
||||||
|
.request_animation_frame(f.as_ref().unchecked_ref())
|
||||||
|
.expect("should register `requestAnimationFrame` OK");
|
||||||
|
}
|
||||||
|
|
||||||
|
let cb = Arc::new(RefCell::new(None));
|
||||||
|
*cb.borrow_mut() = Some(Closure::wrap(Box::new({
|
||||||
|
let cb = cb.clone();
|
||||||
|
move || {
|
||||||
|
timestamp += 20.0;
|
||||||
|
|
||||||
|
canvas.set_width(window().unwrap().inner_width().unwrap().as_f64().unwrap() as u32);
|
||||||
|
canvas.set_height(window().unwrap().inner_height().unwrap().as_f64().unwrap() as u32);
|
||||||
|
|
||||||
|
gl.uniform_matrix4fv_with_f32_array(unim_matrix.as_ref(), false, default_matrix.as_slice());
|
||||||
|
|
||||||
|
gl.uniform1f(unim_time.as_ref(), timestamp / 2000.0);
|
||||||
|
gl.uniform2fv_with_f32_array(
|
||||||
|
unim_res.as_ref(),
|
||||||
|
vec![canvas.width() as f32, canvas.height() as f32].as_slice(),
|
||||||
|
);
|
||||||
|
|
||||||
|
gl.uniform1i(unim_overlay_time.as_ref(), 0); // TODO: ... | add is xmas check
|
||||||
|
|
||||||
|
gl.viewport(0, 0, canvas.width() as i32, canvas.height() as i32);
|
||||||
|
gl.clear(GL2::COLOR_BUFFER_BIT | GL2::DEPTH_BUFFER_BIT);
|
||||||
|
gl.draw_elements_with_i32(
|
||||||
|
GL2::TRIANGLES,
|
||||||
|
element_indices.len() as i32,
|
||||||
|
GL2::UNSIGNED_SHORT,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
|
||||||
|
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||||
|
}
|
||||||
|
}) as Box<dyn FnMut()>));
|
||||||
|
|
||||||
|
request_animation_frame(cb.borrow().as_ref().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Trace));
|
||||||
|
yew::Renderer::<App>::new().render();
|
||||||
|
}
|
||||||
95
src/pages/about.rs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct About;
|
||||||
|
impl Component for About {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
<img alt="insert pfp here" style="
|
||||||
|
float: left !important;
|
||||||
|
margin-right: 10px !important;
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
" loading="eager" width="96px" height="96px" src="profile.avif" />
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
max-height: 96px !important;
|
||||||
|
">
|
||||||
|
<p style="
|
||||||
|
display: flex !important;
|
||||||
|
color: transparent !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
align-items: center !important;
|
||||||
|
width: fit-content !important;
|
||||||
|
background-clip: text !important;
|
||||||
|
background-image: linear-gradient(45deg, #5BCEFAFF, #F5A9B8FF, #FFFFFFFF) !important;
|
||||||
|
-webkit-background-clip: text !important;
|
||||||
|
margin: unset !important;
|
||||||
|
filter: url(post.bloom.svg#process) !important;
|
||||||
|
">
|
||||||
|
<img loading="eager" alt="ket" src="oneko.gif" />{r" lia · they/she · transfemby"}</p>
|
||||||
|
<p style="margin: unset !important;">
|
||||||
|
<p style="margin: unset !important;">{r"certified catgirl™"}</p>
|
||||||
|
<p style="margin: unset !important;">{r"software engineer"}</p>
|
||||||
|
<p style="margin: unset !important;">{r"professional yapper"}</p>
|
||||||
|
<p style="margin: unset !important;">{r"neurospicy and disabled"}</p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<p style="
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-style: italic !important;
|
||||||
|
font-size: xx-large !important;
|
||||||
|
">{r">intro_"}<br /><p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: medium !important;
|
||||||
|
">{r#"
|
||||||
|
name's lia or lucy. we are creating.
|
||||||
|
trying to provide a safe space in the internet.
|
||||||
|
our goal's to make this world a better ~ less trashy ~ place.
|
||||||
|
may sound naive as fuck but fuck it we ball.
|
||||||
|
audhd (certified) and probably bpd or ptsd. i'm not a psychologist tho.
|
||||||
|
interact with caution, may bite.
|
||||||
|
political view's prolly very obvious. should be enough to mention i'm social.
|
||||||
|
figure it yourself. use your brain.
|
||||||
|
"#}</p>{r">hobbies_"}<br /><p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: medium !important;
|
||||||
|
">{r#"
|
||||||
|
watching anime, designing something kewl, playing games, starting a new project or just yapping with friends, nearly everything's included.
|
||||||
|
i especially enjoy cooking or even baking if enough spoons are to avail.
|
||||||
|
which anime? has to be very specific, i'm really picky and judgy regarding them.
|
||||||
|
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
|
||||||
|
has to be something i can somewhat imagine in my fucky-wucky head.
|
||||||
|
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
|
||||||
|
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
|
||||||
|
projects? you can find public ones in my projects tab.
|
||||||
|
"#}/*</p>{r"hobbies"}<br /><p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: medium !important;
|
||||||
|
">{r#"
|
||||||
|
watching anime, designing something kewl, playing games, starting a new project or just yapping/meeting up with friends, nearly everything's included.
|
||||||
|
i especially enjoy cooking or even baking if enough spoons are to avail.
|
||||||
|
which anime? has to be very specific, i'm really picky and judgy regarding them.
|
||||||
|
i uh - design stuff when enough creativity is there. nothing specific. maybe just a button, banner, icon or just anything.
|
||||||
|
has to be something i can somewhat imagine in my fucky-wucky head.
|
||||||
|
gaymes? gay! no uhh - i play ranging from minecraft and stardew, to puzzly metroidvanias like hollow knight and pseudoregalia, or challenging platformers like celeste, nearly anything.
|
||||||
|
i also enjoy playing rhythm games like osu! or muse dash. returning to terraria sometimes and also some old-ish games.
|
||||||
|
projects? you can find public ones in my projects tab.
|
||||||
|
"#}*/</p>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
71
src/pages/blog/author.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
use crate::pages::blog::content;
|
||||||
|
use crate::pages::blog::content::{BlogEntry, Post};
|
||||||
|
use yew::prelude::*;
|
||||||
|
use crate::pages::blog::authorcard::AuthorCard;
|
||||||
|
use crate::pages::blog::entrycard::EntryCard;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Author {
|
||||||
|
author: content::Author,
|
||||||
|
}
|
||||||
|
impl Component for Author {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = Props;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
author: content::Author::from_id(ctx.props().id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||||
|
self.author = content::Author::from_id(ctx.props().id);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let Self { author } = self;
|
||||||
|
let id = author.id;
|
||||||
|
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
|
||||||
|
.filter(|&id_offset| {
|
||||||
|
Post::from_id(id_offset as u8)
|
||||||
|
.authors
|
||||||
|
.iter()
|
||||||
|
.any(|id1| *id1 == id)
|
||||||
|
})
|
||||||
|
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
|
||||||
|
.collect();
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin-bottom: 2rem !important;
|
||||||
|
">
|
||||||
|
<h2 style="text-align: center !important;">{ "about me" }</h2>
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
"><AuthorCard {id} /></div>
|
||||||
|
<h2 style="text-align: center !important;">{ "my posts" }</h2>
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
">{ for cards }</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/pages/blog/authorcard.rs
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::components::Link;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::Author;
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsAuthorCard {
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct AuthorCard {
|
||||||
|
author: Author,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for AuthorCard {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = PropsAuthorCard;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
author: Author::from_id(ctx.props().id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn changed(&mut self, ctx: &Context<Self>, _old_props: &Self::Properties) -> bool {
|
||||||
|
self.author = Author::from_id(ctx.props().id);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let Self { author } = self;
|
||||||
|
let keywords = author.keywords.iter().map(|keyword| html! {
|
||||||
|
<p style="margin: unset !important;">{*keyword}</p>
|
||||||
|
}).collect::<Html>();
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
background-color: #ECBEE130 !important;
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
text-align: left !important;
|
||||||
|
margin: 1rem 1rem 0 0 !important;
|
||||||
|
">
|
||||||
|
<img style="
|
||||||
|
float: left !important;
|
||||||
|
margin-right: 0.3rem !important;
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
" alt="insert pfp here" width="96" height="96" src={author.image_url} />
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
margin-top: 0.3rem !important;
|
||||||
|
margin-right: 0.3rem !important;
|
||||||
|
">
|
||||||
|
<h3 style="margin: unset !important;">
|
||||||
|
<Link<Route> to={Route::Author { id: author.id }}>
|
||||||
|
{ &*author.name }
|
||||||
|
</Link<Route>>
|
||||||
|
</h3>
|
||||||
|
{ keywords }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
src/pages/blog/authors.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::blog::authorcard::AuthorCard;
|
||||||
|
use crate::pages::blog::content::{Author, BlogEntry};
|
||||||
|
|
||||||
|
pub struct Authors;
|
||||||
|
impl Component for Authors {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin-top: 2rem !important;
|
||||||
|
margin-bottom: 2rem !important;
|
||||||
|
">{ for Author::AUTHORS.map(|author| {
|
||||||
|
let id = author.id;
|
||||||
|
html! { <AuthorCard {id} /> }
|
||||||
|
}) }</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
299
src/pages/blog/content.rs
Normal file
@@ -0,0 +1,299 @@
|
|||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Author {
|
||||||
|
pub id: u8,
|
||||||
|
pub image_url: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub keywords: &'static [&'static str],
|
||||||
|
pub about: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Post {
|
||||||
|
pub id: u8,
|
||||||
|
pub authors: &'static [u8],
|
||||||
|
pub title: &'static str,
|
||||||
|
pub utcdate: &'static str,
|
||||||
|
pub content: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlogEntry for Author {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
*Self::AUTHORS
|
||||||
|
.get(entry.id as usize)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(&Self {
|
||||||
|
id: u8::MAX,
|
||||||
|
image_url: "",
|
||||||
|
name: "not found",
|
||||||
|
keywords: [].as_slice(),
|
||||||
|
about: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BlogEntry for Post {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self {
|
||||||
|
*Self::POSTS
|
||||||
|
.get(entry.id as usize)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or(&Self {
|
||||||
|
id: u8::MAX,
|
||||||
|
authors: &[u8::MAX],
|
||||||
|
title: "not found",
|
||||||
|
utcdate: "1970-01-01",
|
||||||
|
content: "",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Entry {
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Entry {
|
||||||
|
pub fn from_id(seed: u8) -> Self {
|
||||||
|
Self { id: seed }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait BlogEntry: Sized {
|
||||||
|
fn from_entry(entry: &mut Entry) -> Self;
|
||||||
|
fn from_id(id: u8) -> Self {
|
||||||
|
Self::from_entry(&mut Entry::from_id(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
const AUTHORS: [&'static Author; 2] = [
|
||||||
|
&Author {
|
||||||
|
id: 0,
|
||||||
|
image_url: "profile.avif",
|
||||||
|
name: "lia",
|
||||||
|
keywords: &[
|
||||||
|
"certified catgirl™",
|
||||||
|
"software engineer",
|
||||||
|
"professional yapper",
|
||||||
|
"neurospicy and disabled",
|
||||||
|
],
|
||||||
|
about: "",
|
||||||
|
},
|
||||||
|
&Author {
|
||||||
|
id: 1,
|
||||||
|
image_url: "",
|
||||||
|
name: "mreowww",
|
||||||
|
keywords: &["meow", "mrrp", "mew"],
|
||||||
|
about: "",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const POSTS: [&'static Post; 2] = [
|
||||||
|
&Post {
|
||||||
|
id: 0,
|
||||||
|
authors: &[0],
|
||||||
|
title: "haj, world!",
|
||||||
|
utcdate: "2025-08-24",
|
||||||
|
content: r#"
|
||||||
|
### my first blogpost
|
||||||
|
haj! and welcome to my first blogpost.
|
||||||
|
this blogpost won't have that much content.
|
||||||
|
fun fact: this post becomes html from markdown :3
|
||||||
|
pretty cool, ain't it? we think so too.
|
||||||
|
it's for including images, formatted text, code and content much easier.
|
||||||
|
i also added a too overcomplicated blog backend that allows authors and posts by u8::MAX, cause why not.
|
||||||
|
won't post that much either way, so why bother with higher values.
|
||||||
|
nonetheless... have a great day, evening, whatever daytime it's for you rn and stay safe. ♡
|
||||||
|
you may take a look at my other blogposts. this one was basically practially just a test.
|
||||||
|
"#,
|
||||||
|
},
|
||||||
|
&Post {
|
||||||
|
id: 1,
|
||||||
|
authors: &[0],
|
||||||
|
title: "sweet little poison",
|
||||||
|
utcdate: "2025-08-25",
|
||||||
|
content: r#"
|
||||||
|
### > setting up iocaine with docker and caddy. ~
|
||||||
|
sup, today we're going to give ya a crash course on how to homebrew poison for ai. ^.^
|
||||||
|
we'll be utilizing docker for containerization and caddy as a reverse-proxy.
|
||||||
|
|
||||||
|
#### > preparing docker and files ~
|
||||||
|
first, we're going to create a new external network and recreate our file struct.
|
||||||
|
in this example we're going to name it "caddy". very creative, i know.
|
||||||
|
we achieve this by running following pile of commands:
|
||||||
|
```sh
|
||||||
|
docker network create caddy && \
|
||||||
|
mkdir -p ./caddy/pages/example.com ./iocaine ./socks && \
|
||||||
|
touch ./docker-compose.yml ./caddy/proxy ./iocaine/config.toml && \
|
||||||
|
touch ./caddy/pages/example.com/index.html && \
|
||||||
|
printf '<html><body><h1>hellow body consisting of blood and flesh</h1></body></html>' \
|
||||||
|
> ./caddy/pages/example.com/index.html
|
||||||
|
```
|
||||||
|
|
||||||
|
after running that we should be set and ready for the next few steps.
|
||||||
|
if you're curious, this is how it should look like right now:
|
||||||
|
```sh
|
||||||
|
Documents/example » tree .
|
||||||
|
.
|
||||||
|
├── caddy
|
||||||
|
│ ├── pages
|
||||||
|
│ │ └── example.com
|
||||||
|
│ │ └── index.html
|
||||||
|
│ └── proxy
|
||||||
|
├── docker-compose.yml
|
||||||
|
├── iocaine
|
||||||
|
│ └── config.toml
|
||||||
|
└── socks
|
||||||
|
```
|
||||||
|
|
||||||
|
#### > downloading data and configuring iocaine ~
|
||||||
|
|
||||||
|
next things next: we're going to download a robots.json, some markov-chain-stuffies and a words.txt.
|
||||||
|
for now, we will be using the default stuff from the official docs cause why not:
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.3 \
|
||||||
|
-L https://archive.org/download/GeorgeOrwells1984/1984_djvu.txt \
|
||||||
|
-o ./iocaine/1984.txt && \
|
||||||
|
curl --proto '=https' --tlsv1.3 \
|
||||||
|
-L https://archive.org/download/ost-english-brave_new_world_aldous_huxley/Brave_New_World_Aldous_Huxley_djvu.txt \
|
||||||
|
-o ./iocaine/brave-new-world.txt && \
|
||||||
|
curl --proto '=https' --tlsv1.2 \
|
||||||
|
-L https://git.savannah.gnu.org/cgit/miscfiles.git/plain/web2 \
|
||||||
|
-o ./iocaine/words.txt && \
|
||||||
|
curl --proto '=https' --tlsv1.3 \
|
||||||
|
-L https://github.com/ai-robots-txt/ai.robots.txt/raw/refs/heads/main/robots.json \
|
||||||
|
-o ./iocaine/robots.json
|
||||||
|
```
|
||||||
|
|
||||||
|
most importantly, we have to scrape nam-shub-of-enki from gergely's git.
|
||||||
|
here's a way to curl and extract it automatically in the correct folder:
|
||||||
|
```sh
|
||||||
|
curl --proto '=https' --tlsv1.3 \
|
||||||
|
-L https://git.madhouse-project.org/api/packages/iocaine/generic/nam-shub-of-enki/20250711.0/nam-shub-of-enki-20250711.0.tar.zst \
|
||||||
|
-o ./iocaine/nam-shub-of-enki.tar.zst && \
|
||||||
|
sudo tar -xvf ./iocaine/nam-shub-of-enki.tar.zst -C ./iocaine
|
||||||
|
```
|
||||||
|
|
||||||
|
now, we're going to prepare our iocaine/config.toml like this:
|
||||||
|
```sh
|
||||||
|
cat > ./iocaine/config.toml <<'YAML'
|
||||||
|
[server]
|
||||||
|
bind = "/run/iocaine/waow.socket"
|
||||||
|
unix_listen_access = "everybody"
|
||||||
|
|
||||||
|
[server.control]
|
||||||
|
bind = "/run/iocaine/listen.socket"
|
||||||
|
unix_listen_access = "owner"
|
||||||
|
|
||||||
|
[server.request-handler]
|
||||||
|
path = "/data"
|
||||||
|
language = "roto"
|
||||||
|
|
||||||
|
[sources]
|
||||||
|
words = "/data/words.txt"
|
||||||
|
markov = [ "/data/1984.txt", "/data/brave-new-world.txt" ]
|
||||||
|
|
||||||
|
[metrics]
|
||||||
|
enable = false
|
||||||
|
YAML
|
||||||
|
```
|
||||||
|
|
||||||
|
#### > cooking up our docker compose file for deployment ~
|
||||||
|
|
||||||
|
i have fucked around with docker a little while to make this somehow work.
|
||||||
|
here's a command which will insert needed content into our docker-compose.yml:
|
||||||
|
```sh
|
||||||
|
cat > ./docker-compose.yml <<'YAML'
|
||||||
|
services:
|
||||||
|
caddy:
|
||||||
|
image: caddy:alpine
|
||||||
|
container_name: proxy.caddy
|
||||||
|
hostname: proxy.caddy
|
||||||
|
restart: unless-stopped
|
||||||
|
cap_add:
|
||||||
|
- NET_ADMIN
|
||||||
|
ports:
|
||||||
|
- "80:80"
|
||||||
|
- "443:443"
|
||||||
|
- "443:443/udp"
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
volumes:
|
||||||
|
- ./socks:/run/iocaine:ro
|
||||||
|
- ./caddy/pages:/var/www/html
|
||||||
|
- ./caddy/data:/data
|
||||||
|
- ./caddy/config:/config
|
||||||
|
- ./caddy/proxy:/etc/caddy/Caddyfile:ro
|
||||||
|
depends_on:
|
||||||
|
- iocaine
|
||||||
|
iocaine:
|
||||||
|
image: git.madhouse-project.org/iocaine/iocaine:2
|
||||||
|
container_name: proxy.iocaine
|
||||||
|
hostname: proxy.iocaine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:42069:42069"
|
||||||
|
networks:
|
||||||
|
- caddy
|
||||||
|
volumes:
|
||||||
|
- ./socks:/run/iocaine:rw
|
||||||
|
- ./iocaine:/data
|
||||||
|
environment:
|
||||||
|
- IOCAINE__SERVER__BIND="/run/iocaine/waow.socket"
|
||||||
|
- IOCAINE__SERVER__UNIX_LISTEN_ACCESS="everybody"
|
||||||
|
- IOCAINE__SERVER__REQUEST_HANDLER__PATH="/data"
|
||||||
|
- IOCAINE__SERVER__REQUEST_HANDLER__LANGUAGE="roto"
|
||||||
|
- IOCAINE__SERVER__CONTROL__BIND="/run/iocaine/listen.socket"
|
||||||
|
- IOCAINE__SERVER__CONTROL__UNIX_LISTEN_ACCESS="owner"
|
||||||
|
- IOCAINE__SOURCES__WORDS="/data/words.txt"
|
||||||
|
- IOCAINE__SOURCES__MARKOV=["/data/1984.txt", "/data/brave-new-world.txt"]
|
||||||
|
- IOCAINE__METRICS__ENABLE=false
|
||||||
|
- NSOE__AI_ROBOTS_TXT_PATH=/data/robots.json
|
||||||
|
command: --config-file /data/config.toml start
|
||||||
|
networks:
|
||||||
|
caddy:
|
||||||
|
external: true
|
||||||
|
YAML
|
||||||
|
```
|
||||||
|
|
||||||
|
#### > finishing up with caddy and deploying ~
|
||||||
|
our caddyfile needs some love to work.
|
||||||
|
here's what i ripped from the official docs:
|
||||||
|
```sh
|
||||||
|
cat > ./iocaine/proxy <<'TXT'
|
||||||
|
(iocaine) {
|
||||||
|
@read method GET HEAD
|
||||||
|
@not-read not {
|
||||||
|
method GET HEAD
|
||||||
|
}
|
||||||
|
reverse_proxy @read unix//run/iocaine/waow.socket {
|
||||||
|
#reverse_proxy @read proxy.iocaine:42069 {
|
||||||
|
@fallback status 421
|
||||||
|
handle_response @fallback {
|
||||||
|
{blocks.handler}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handle @not-read {
|
||||||
|
{blocks.default}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
example.com {
|
||||||
|
import iocaine {
|
||||||
|
handler {
|
||||||
|
reverse_proxy http://example:8080
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
# this is the behaviour if neither a GET nor HEAD request comes in
|
||||||
|
reverse_proxy http://example:8080
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TXT
|
||||||
|
```
|
||||||
|
now the last thing that's left is a plain and simple:
|
||||||
|
`docker compose up -d`
|
||||||
|
and we should be good to go!
|
||||||
|
|
||||||
|
i hope this post made it easier/helped you with setting up iocaine on your own server. ♡
|
||||||
|
it's good to have this set up just to fuck around with fucking parisitic generative ai stealing your things.
|
||||||
|
gatekeep your stuff from big corpos that are trying to make their business from your experience and skills.
|
||||||
|
"#,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
55
src/pages/blog/entries.rs
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::BlogEntry;
|
||||||
|
use crate::pages::blog::content::Post;
|
||||||
|
use crate::pages::blog::entrycard::EntryCard;
|
||||||
|
|
||||||
|
pub struct Entries {}
|
||||||
|
|
||||||
|
impl Component for Entries {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<h1 style="margin: unset !important;">{ "entries" }</h1>
|
||||||
|
<h2 style="
|
||||||
|
margin: unset !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
">{ "here are some things i yapped about" }</h2>
|
||||||
|
{ self.view_posts(ctx) }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Entries {
|
||||||
|
fn view_posts(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let cards: Vec<_> = (0..Post::POSTS.len()) // TODO: ... | add var
|
||||||
|
.filter(|&id_offset| {
|
||||||
|
Post::from_id(id_offset as u8)
|
||||||
|
.authors
|
||||||
|
.iter()
|
||||||
|
.all(|id| *id != u8::MAX)
|
||||||
|
})
|
||||||
|
.map(|id_offset| html! {<EntryCard id={id_offset as u8} />})
|
||||||
|
.collect();
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin: unset !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
width: 100% !important;
|
||||||
|
">
|
||||||
|
{ for cards }
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
77
src/pages/blog/entry.rs
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_markdown::Markdown;
|
||||||
|
use yew_router::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::blog::content;
|
||||||
|
use crate::pages::blog::content::{Author, BlogEntry};
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Properties)]
|
||||||
|
pub struct Props {
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct PostState {
|
||||||
|
pub inner: content::Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reducible for PostState {
|
||||||
|
type Action = u8;
|
||||||
|
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||||
|
Self {
|
||||||
|
inner: content::Post::from_id(action.into()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn Entry(props: &Props) -> Html {
|
||||||
|
let id = props.id;
|
||||||
|
|
||||||
|
let post = use_reducer(|| PostState {
|
||||||
|
inner: content::Post::from_id(id.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let post_dispatcher = post.dispatcher();
|
||||||
|
use_effect_with(id, move |id| {
|
||||||
|
post_dispatcher.dispatch(*id);
|
||||||
|
|| {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let post = &post.inner;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<>
|
||||||
|
<h1 style="
|
||||||
|
color: #F5C2E7FF !important;
|
||||||
|
margin: unset !important;
|
||||||
|
filter: url(post.bloom.svg#process) !important;
|
||||||
|
">{ &*post.title }</h1>
|
||||||
|
<div style="flex-direction: column !important;">
|
||||||
|
<h2 style="margin: unset !important;">
|
||||||
|
{ "written by " } {
|
||||||
|
post.authors.iter().map(|id| {
|
||||||
|
let author = Author::from_id(*id);
|
||||||
|
html! {
|
||||||
|
<Link<Route> to={crate::Route::Author { id: author.id }}>
|
||||||
|
{ &*author.name }
|
||||||
|
</Link<Route>>
|
||||||
|
}
|
||||||
|
}).intersperse(html! { ", " }).collect::<Html>()
|
||||||
|
} { format!(" on {}", &post.utcdate) }
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<Markdown
|
||||||
|
src={post.content}
|
||||||
|
hard_line_breaks={true}
|
||||||
|
send_debug_info={Callback::noop()}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
}
|
||||||
98
src/pages/blog/entrycard.rs
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
use std::rc::Rc;
|
||||||
|
use yew::prelude::*;
|
||||||
|
use yew_router::components::Link;
|
||||||
|
|
||||||
|
use crate::pages::blog::content::{Author, BlogEntry, Post};
|
||||||
|
|
||||||
|
use crate::Route;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsEntryCard {
|
||||||
|
pub id: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Debug)]
|
||||||
|
pub struct PostState {
|
||||||
|
inner: Post,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reducible for PostState {
|
||||||
|
type Action = u8;
|
||||||
|
|
||||||
|
fn reduce(self: Rc<Self>, action: u8) -> Rc<Self> {
|
||||||
|
Self {
|
||||||
|
inner: Post::from_id(action.into()),
|
||||||
|
}
|
||||||
|
.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[function_component]
|
||||||
|
pub fn EntryCard(props: &PropsEntryCard) -> Html {
|
||||||
|
let id = props.id;
|
||||||
|
|
||||||
|
let post = use_reducer_eq(|| PostState {
|
||||||
|
inner: Post::from_id(id.into()),
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
let post_dispatcher = post.dispatcher();
|
||||||
|
use_effect_with(id, move |id| {
|
||||||
|
post_dispatcher.dispatch(*id);
|
||||||
|
|
||||||
|
|| {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let post = &post.inner;
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
background-color: #ECBEE130 !important;
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
text-align: left !important;
|
||||||
|
margin: 1rem 1rem 0 0 !important;
|
||||||
|
">
|
||||||
|
<Link<Route> to={Route::Entry { id: post.id }}>
|
||||||
|
<h2 style="
|
||||||
|
margin: unset !important;
|
||||||
|
margin-top: 0.3rem !important;
|
||||||
|
margin-left: 0.3rem !important;
|
||||||
|
margin-right: 0.3rem !important;
|
||||||
|
">{ &*post.title }</h2>
|
||||||
|
</Link<Route>>
|
||||||
|
<h3 style="
|
||||||
|
margin: unset !important;
|
||||||
|
margin-left: 0.3rem !important;
|
||||||
|
margin-right: 0.3rem !important;
|
||||||
|
">
|
||||||
|
{ format!("published {}", post.utcdate) }
|
||||||
|
</h3>
|
||||||
|
<div style="flex-direction: column !important;">
|
||||||
|
<h3 style="
|
||||||
|
margin: unset !important;
|
||||||
|
margin-left: 0.3rem !important;
|
||||||
|
margin-right: 0.3rem !important;
|
||||||
|
margin-bottom: 0.3rem !important;
|
||||||
|
">
|
||||||
|
{ "written by " }
|
||||||
|
{
|
||||||
|
post.authors.iter().map(|id| {
|
||||||
|
let author = Author::from_id(*id);
|
||||||
|
html! {
|
||||||
|
<Link<Route> to={crate::Route::Author { id: author.id }}>
|
||||||
|
{ &*author.name }
|
||||||
|
</Link<Route>>
|
||||||
|
}
|
||||||
|
}).intersperse(html! { ", " }).collect::<Html>()
|
||||||
|
}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/pages/blog/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
pub mod author;
|
||||||
|
pub mod authorcard;
|
||||||
|
pub mod authors;
|
||||||
|
pub mod content;
|
||||||
|
pub mod entries;
|
||||||
|
pub mod entry;
|
||||||
|
pub mod entrycard;
|
||||||
138
src/pages/findme.rs
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
use web_sys::window;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct FindMe;
|
||||||
|
impl Component for FindMe {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let clipboard = window().expect("meow").navigator().clipboard();
|
||||||
|
|
||||||
|
let handle_copy_matrix = {
|
||||||
|
let clipboard = clipboard.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let _ = clipboard.write_text(&*"@iouring:hi.stellaris.fyi".to_string());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle_copy_telegram = {
|
||||||
|
let clipboard = clipboard.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let _ = clipboard.write_text(&*"@luc1ell3".to_string());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let handle_copy_discord = {
|
||||||
|
let clipboard = clipboard.clone();
|
||||||
|
Callback::from(move |_| {
|
||||||
|
let _ = clipboard.write_text(&*"@donotusedisc0rdkthxbye".to_string());
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !mportant;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
width: 100% !important;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
margin: 1rem 1rem 1rem 1rem !important;
|
||||||
|
">
|
||||||
|
<p style="
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: xx-large !important;
|
||||||
|
text-align: center !important;
|
||||||
|
margin: unset !important;
|
||||||
|
">{r"follow us"}</p>
|
||||||
|
<p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: large !important;
|
||||||
|
">{" × "}<a style="
|
||||||
|
color: #F5C2E7FF !important;
|
||||||
|
" href="https://ice.stellaris.fyi/@iouring">{r"fediverse"}</a><br />
|
||||||
|
{" × "}<a style="
|
||||||
|
color: #B4BEFEFF !important;
|
||||||
|
" href="https://bsky.app/profile/i0ur.ing">{r"bluesky"}</a><br />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
margin: 1rem 1rem 1rem 1rem !important;
|
||||||
|
">
|
||||||
|
<p style="
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: xx-large !important;
|
||||||
|
text-align: center !important;
|
||||||
|
margin: unset !important;
|
||||||
|
">{r"write us"}</p>
|
||||||
|
<p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: large !important;
|
||||||
|
">
|
||||||
|
{"× "}
|
||||||
|
<button style="
|
||||||
|
color: #94E2D5FF !important;
|
||||||
|
font-size: large !important;
|
||||||
|
" onclick={handle_copy_matrix}>{r"matrix"}</button>
|
||||||
|
<br />
|
||||||
|
{"× "}
|
||||||
|
<button style="
|
||||||
|
color: #B4BEFEFF !important;
|
||||||
|
font-size: large !important;
|
||||||
|
" onclick={handle_copy_telegram}>{r"telegr"}</button>
|
||||||
|
<br />
|
||||||
|
{"× "}
|
||||||
|
<button style="
|
||||||
|
color: #F38BA8FF !important;
|
||||||
|
font-size: large !important;
|
||||||
|
" onclick={handle_copy_discord}>{r"ewcord"}</button>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
margin: 1rem 1rem 1rem 1rem !important;
|
||||||
|
">
|
||||||
|
<p style="
|
||||||
|
font-weight: bold !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: xx-large !important;
|
||||||
|
text-align: center !important;
|
||||||
|
margin: unset !important;
|
||||||
|
">{r"git gud"}</p>
|
||||||
|
<p style="
|
||||||
|
font-weight: normal !important;
|
||||||
|
font-style: normal !important;
|
||||||
|
font-size: large !important;
|
||||||
|
">{" × "}<a style="
|
||||||
|
color: #F5C2E7FF !important;
|
||||||
|
" href="https://git.celesteflare.cc/i0uring">{r"my own!!!"}</a><br />
|
||||||
|
{" × "}<a style="
|
||||||
|
color: #CBA6F7FF !important;
|
||||||
|
" href="https://git.gay/luciel">{r"the gay one"}</a><br />
|
||||||
|
{" × "}<a style="
|
||||||
|
color: #B4BEFEFF !important;
|
||||||
|
" href="https://git.rimuru.club/i0uring">{r"git of fren"}</a><br />
|
||||||
|
{" × "}<a style="
|
||||||
|
color: #89B4FAFF !important;
|
||||||
|
" href="https://codeberg.org/i0uring">{r"a mountain what"}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
6
src/pages/mod.rs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
pub mod about;
|
||||||
|
pub mod blog;
|
||||||
|
pub mod findme;
|
||||||
|
pub mod projects;
|
||||||
|
|
||||||
|
pub mod not_found;
|
||||||
31
src/pages/not_found.rs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct PageNotFound;
|
||||||
|
|
||||||
|
impl Component for PageNotFound {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
text-align: center !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
align-items: center !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
">
|
||||||
|
<img loading="eager" alt="huh" style="
|
||||||
|
width: 96px !important;
|
||||||
|
height: 96px !important;
|
||||||
|
" width="96" height="96" src="404.png" />
|
||||||
|
<h1>{ "what" }</h1>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
src/pages/projects/content.rs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
|
pub struct Project {
|
||||||
|
pub url: &'static str,
|
||||||
|
pub name: &'static str,
|
||||||
|
pub desc: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProjectEntry for Project {}
|
||||||
|
|
||||||
|
pub trait ProjectEntry: Sized {
|
||||||
|
const PROJECTS_ONGOING: [&'static Project; 3] = [
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/stellaris/mod_headsup",
|
||||||
|
name: "headsup mod",
|
||||||
|
desc: "extensible hud mc mod",
|
||||||
|
},
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/app_catnip",
|
||||||
|
name: "catnip",
|
||||||
|
desc: "all-rounder ide in the making",
|
||||||
|
},
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/app_nekochat",
|
||||||
|
name: "neko chat",
|
||||||
|
desc: "my planned matrix client",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROJECTS_FINISHED: [&'static Project; 4] = [
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/lib_tinyevents",
|
||||||
|
name: "tiny events",
|
||||||
|
desc: "a java 21+ event-sys",
|
||||||
|
},
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/dotfiles",
|
||||||
|
name: "dotfiles",
|
||||||
|
desc: "personal set of configurations",
|
||||||
|
},
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/lib_swingify",
|
||||||
|
name: "tiny events",
|
||||||
|
desc: "simplifies java swing window creation",
|
||||||
|
},
|
||||||
|
&Project {
|
||||||
|
url: "https://git.celesteflare.cc/i0uring/dotfiles",
|
||||||
|
name: "modern netty",
|
||||||
|
desc: "adds experimental netty handlers to mc",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
3
src/pages/projects/mod.rs
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
pub mod content;
|
||||||
|
pub mod projectcard;
|
||||||
|
pub mod projects;
|
||||||
54
src/pages/projects/projectcard.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
use crate::pages::projects::content::Project;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Properties)]
|
||||||
|
pub struct PropsAuthorCard {
|
||||||
|
pub project: Project,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct ProjectCard {
|
||||||
|
project: Project,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for ProjectCard {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = PropsAuthorCard;
|
||||||
|
|
||||||
|
fn create(ctx: &Context<Self>) -> Self {
|
||||||
|
Self {
|
||||||
|
project: ctx.props().project,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let Self { project } = self;
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
background-color: #ECBEE130 !important;
|
||||||
|
border-radius: 0.5rem !important;
|
||||||
|
width: fit-content !important;
|
||||||
|
height: fit-content !important;
|
||||||
|
text-align: left !important;
|
||||||
|
margin: 1rem 1rem 0 0 !important;
|
||||||
|
">
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
margin: 0.3rem 0.3rem 0.3rem 0.3rem !important;
|
||||||
|
">
|
||||||
|
<h2 style="margin: unset !important;">
|
||||||
|
<a href={project.url}>{ &*project.name }</a>
|
||||||
|
</h2>
|
||||||
|
<h3 style="margin: unset !important;">
|
||||||
|
{ &*project.desc }
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
54
src/pages/projects/projects.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::pages::projects::content::{Project, ProjectEntry};
|
||||||
|
use crate::pages::projects::projectcard::ProjectCard;
|
||||||
|
use yew::prelude::*;
|
||||||
|
|
||||||
|
pub struct Projects;
|
||||||
|
impl Component for Projects {
|
||||||
|
type Message = ();
|
||||||
|
type Properties = ();
|
||||||
|
|
||||||
|
fn create(_ctx: &Context<Self>) -> Self {
|
||||||
|
Self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn view(&self, _ctx: &Context<Self>) -> Html {
|
||||||
|
let projects_ongoing = Project::PROJECTS_ONGOING.map(|project| {
|
||||||
|
let project = *project;
|
||||||
|
html! { <ProjectCard {project} /> }
|
||||||
|
});
|
||||||
|
|
||||||
|
let projects_finished = Project::PROJECTS_FINISHED.map(|project| {
|
||||||
|
let project = *project;
|
||||||
|
html! { <ProjectCard {project} /> }
|
||||||
|
});
|
||||||
|
|
||||||
|
html! {
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
margin-bottom: 2rem !important;
|
||||||
|
">
|
||||||
|
<h2 style="text-align: center !important;">{ "ongoing projects" }</h2>
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
">{ for projects_ongoing }</div>
|
||||||
|
<h2 style="text-align: center !important;">{ "finished projects" }</h2>
|
||||||
|
<div style="
|
||||||
|
display: flex !important;
|
||||||
|
width: 100% !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
flex-wrap: wrap !important;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
">{ for projects_finished }</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||