1
Fork 0
mirror of https://github.com/RGBCube/Site synced 2025-07-31 13:07:46 +00:00

treewide: site redesign

This commit is contained in:
RGBCube 2025-06-01 21:32:33 +03:00
parent 3d663619e6
commit 4b146bbe4e
Signed by: RGBCube
SSH key fingerprint: SHA256:CzqbPcfwt+GxFYNnFVCqoN5Itn4YFrshg1TrnACpA5M
80 changed files with 1842 additions and 1117 deletions

View file

@ -2,13 +2,4 @@
layout: text.vto
---
<div class="content">{{ content }}</div>
{{ if tags.length !== 0 }}
<p>Tags: {{ tags.join(", ") }}</p>
{{ /if }}
<p>
Also, if you are a dinosaur that enjoys good technology, check out my
<a href="/blog.rss">RSS Feed</a>.
</p>
<div class="article-content">{{ content }}</div>

View file

@ -1,165 +0,0 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0{{ viewportFixed ? ', maximum-scale=1.0, user-scalable=0' : '' }}">
<meta name="darkreader-lock">
<title>{{ title }}</title>
<meta property="og:title" content="{{ title }}">
<meta property="og:site_name" content="{{ title }}">
<meta name="twitter:title" content="{{ title }}">
<meta name="description" content="{{ description }}">
<meta property="og:description" content="{{ description }}">
<meta name="twitter:description" content="{{ description }}">
<meta name="theme-color" content="{{ color }}">
<link rel="alternate" type="application/rss+xml" href="/blog.rss">
<link rel="alternate" type="application/feed+json" href="/blog.json">
<meta property="og:type" content="{{ type }}">
<meta property="og:locale" content="en">
<link rel="canonical" href="{{ url |> url(true) }}">
<meta property="og:url" content="{{ url |> url(true) }}">
<meta name="twitter:url" content="{{ url |> url(true) }}">
<meta name="author" content="RGBCube">
<meta property="og:article:author" content="RGBCube">
<meta name="twitter:creator" content="HSVSphere">
{{ if tags.length !== 0 }}
<meta name="keywords" content="{{ tags.join(', ') }}">
<meta property="og:article:tag" content="{{ tags.join(', ') }}">
{{ /if }}
{{ if date }} <!-- For some reason this is always true, even though I do not set it. -->
<meta property="og:article:published_time" content="{{ date }}">
{{ /if }}
{{ if thumbnail }}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ thumbnail }}">
<meta property="og:image" content="{{ thumbnail }}">
{{ else }}
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/assets/icon.webp">
{{ /if }}
<link rel="icon" href="/assets/icon.gif">
<link rel="apple-touch-icon" href="/assets/icon.webp">
<link rel="preload" as="font" type="font/woff2" href="/assets/BaiJamjureeMedium.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/BaiJamjureeMediumItalic.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/BaiJamjureeBold.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/BaiJamjureeBoldItalic.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/JetBrainsMonoMedium.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/JetBrainsMonoMediumItalic.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/JetBrainsMonoBold.woff2" crossorigin>
<link rel="preload" as="font" type="font/woff2" href="/assets/JetBrainsMonoBoldItalic.woff2" crossorigin>
</head>
<body>
<style>
@font-face {
font-family: "Bai Jamjuree";
font-weight: normal;
src: url("/assets/BaiJamjureeMedium.woff2") format("woff2");
}
@font-face {
font-family: "Bai Jamjuree";
font-style: italic;
font-weight: normal;
src: url("/assets/BaiJamjureeMediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "Bai Jamjuree";
font-weight: bold;
src: url("/assets/BaiJamjureeBold.woff2") format("woff2");
}
@font-face {
font-family: "Bai Jamjuree";
font-style: italic;
font-weight: bold;
src: url("/assets/BaiJamjureeBoldItalic.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-weight: normal;
src: url("/assets/JetBrainsMonoMedium.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-style: italic;
font-weight: normal;
src: url("/assets/JetBrainsMonoMediumItalic.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-weight: bold;
src: url("/assets/JetBrainsMonoBold.woff2") format("woff2");
}
@font-face {
font-family: "JetBrains Mono";
font-style: italic;
font-weight: bold;
src: url("/assets/JetBrainsMonoBoldItalic.woff2") format("woff2");
}
@media (prefers-color-scheme: dark) {
:root {
--background: black;
--foreground: white;
--link: yellow;
}
}
@media (prefers-color-scheme: light) {
:root {
--background: white;
--foreground: black;
--link: red;
}
}
html {
font-family: "Bai Jamjuree";
}
code,
pre {
font-family: "JetBrains Mono";
}
* {
box-sizing: border-box;
position: relative;
min-width: 0;
}
html,
body {
background-color: var(--background);
color: var(--foreground);
height: 100%;
width: 100%;
margin: 0;
}
a {
text-decoration-line: none;
}
</style>
{{ content }}
</body>
</html>

View file

@ -1,117 +0,0 @@
interface CubeProps {
front?: Node;
back?: Node;
left?: Node;
right?: Node;
top?: Node;
bottom?: Node;
}
const Cube = (props: CubeProps) => (
<>
<style
dangerouslySetInnerHTML={{
__html: `
html {
font-size: min(9vw, 9vh, 4.5rem);
overscroll-behavior: none;
}
a {
color: black;
font-weight: bold;
}
.frame {
background-color: white;
width: min-content;
padding: 0 0.3rem;
border-radius: 1rem;
user-select: none;
}
.frame:hover {
background-color: var(--link);
}
.scene {
height: 100dvh;
width: 100dvw;
perspective: 15rem;
display: flex;
align-items: center;
justify-content: center;
}
.cube {
height: 5rem;
width: 5rem;
position: relative;
transform: translateZ(-calc(2.5rem - 1px));
transform-style: preserve-3d;
}
.face {
width: 5rem;
height: 5rem;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
}
.front {
transform: rotateY(0deg) translateZ(calc(2.5rem - 1px));
}
.top {
/* Guess what? Yeah, you guessed right. Safari can't render shit. */
transform: rotateX(89.99999999999999deg) translateZ(calc(2.5rem - 1px));
}
.back {
transform: rotateY(180deg) translateZ(calc(2.5rem - 1px));
}
.bottom {
transform: rotateX(-89.99999999999999deg) translateZ(calc(2.5rem - 1px));
}
.right {
transform: rotateY(89.99999999999999deg) translateZ(calc(2.5rem - 1px));
}
.left {
transform: rotateY(-89.99999999999999deg) translateZ(calc(2.5rem - 1px));
}
`,
}}
>
</style>
<div className="scene">
<div className="cube">
<div className="face front">{props.front}</div>
<div className="face back">{props.back}</div>
<div className="face left">{props.left}</div>
<div className="face right">{props.right}</div>
<div className="face top">{props.top}</div>
<div className="face bottom">{props.bottom}</div>
</div>
</div>
<script src="/assets/cube.js"></script>
</>
);
export default Cube;

259
site/_includes/cube.vto Normal file
View file

@ -0,0 +1,259 @@
<style>
:root {
--cube-width: 5rem;
}
html {
font-size: min(9vw, 9vh, 4.5rem);
}
/* Guess what? Yeah, you guessed right. Safari can't render shit. */
.cube-face-front { transform: rotateY(0deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
.cube-face-top { transform: rotateX( 89.99999999999999deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
.cube-face-back { transform: rotateY(180deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
.cube-face-bottom { transform: rotateX(-89.99999999999999deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
.cube-face-right { transform: rotateY( 89.99999999999999deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
.cube-face-left { transform: rotateY(-89.99999999999999deg) translateZ(calc(var(--cube-width) / 2 - 1px)); }
</style>
<cube-scene class="
h-dvh w-dvw
overscroll-none
flex items-center justify-center
perspective-[calc(var(--cube-width)*3)]
">
<cube-itself class="
size-(--cube-width)
transform-3d transform-[translateZ(-calc(var(--cube-width)/2-1px))]
">
{{> const style = `
size-(--cube-width) absolute
flex items-center justify-center
` }}
<cube-face draggable="false" class="{{ style }} cube-face-front"> {{ cube_face_front }} </cube-face>
<cube-face draggable="false" class="{{ style }} cube-face-back"> {{ cube_face_back }} </cube-face>
<cube-face draggable="false" class="{{ style }} cube-face-left"> {{ cube_face_left }} </cube-face>
<cube-face draggable="false" class="{{ style }} cube-face-right"> {{ cube_face_right }} </cube-face>
<cube-face draggable="false" class="{{ style }} cube-face-top"> {{ cube_face_top }} </cube-face>
<cube-face draggable="false" class="{{ style }} cube-face-bottom"> {{ cube_face_bottom }} </cube-face>
</cube-itself>
</cube-scene>
<script>
"use strict";
const Vec = (x, y, z) => ({
x,
y,
z,
length() {
return Math.sqrt(this.x ** 2 + this.y ** 2 + this.z ** 2);
},
scale(factor) {
return Vec(
this.x * factor,
this.y * factor,
this.z * factor,
);
},
normalize() {
let length = this.length();
length = length == 0 ? 1 : length;
return Vec(
this.x / length,
this.y / length,
this.z / length,
);
},
});
Vec.ZERO = Vec(0, 0, 0);
Vec.sum = (a, b) => Vec(
a.x + b.x,
a.y + b.y,
a.z + b.z,
);
Vec.sub = (a, b) => Vec(
a.x - b.x,
a.y - b.y,
a.z - b.z,
);
const Quat = (x, y, z, w) => ({
x,
y,
z,
w,
});
Quat.fromAxis = (axis) => {
const angle = axis.length();
axis = axis.normalize();
const half = angle / 2;
const sinHalf = Math.sin(half);
const cosHalf = Math.cos(half);
const x = axis.x * sinHalf;
const y = axis.y * sinHalf;
const z = axis.z * sinHalf;
const w = cosHalf;
return Quat(x, y, z, w);
};
Quat.mul = (a, b) => Quat(
a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y,
a.w * b.y - a.x * b.z + a.y * b.w + a.z * b.x,
a.w * b.z + a.x * b.y - a.y * b.x + a.z * b.w,
a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z,
);
const friction = 3;
const sensitivity = 0.01;
// One minute.
const screensaverTimeoutMs = 1 * 60 * 1000;
const mouse = {
down: false,
lastMove: -screensaverTimeoutMs,
previous: Vec.ZERO,
};
const orient = {
element: document.querySelector("cube-itself"),
quat: Quat(0, 0, 0, 1),
set(q) {
this.quat = q;
this.element.style.transform =
`rotate3d(${q.x}, ${q.y}, ${q.z}, ${Math.acos(q.w) * 2}rad)`;
},
get() {
return this.quat;
},
};
let velocity = Vec.ZERO;
let impulseThisFrame = Vec.ZERO;
const handleUp = () => {
mouse.down = false;
};
document.addEventListener("mouseup", handleUp);
document.addEventListener("touchend", handleUp);
const handleDown = (event) => {
mouse.down = true;
velocity = Vec.ZERO;
};
document.addEventListener("mousedown", handleDown);
document.addEventListener("touchstart", handleDown);
const handleMove = (event) => {
// Disables scrolling.
event.preventDefault();
if (!mouse.down) return;
const newMouse = Vec(event.clientX, event.clientY, 0);
const timeDelta = (globalThis.performance.now() - mouse.lastMove) / 1000;
if (timeDelta > 0.1) {
// This is a fresh scroll.
mouse.previous = newMouse;
}
const delta = Vec.sub(newMouse, mouse.previous);
mouse.previous = newMouse;
mouse.lastMove = globalThis.performance.now();
const axis = Vec(-delta.y, delta.x, 0)
.normalize()
.scale(delta.length() * sensitivity);
impulseThisFrame = Vec.sum(impulseThisFrame, axis);
const rotation = Quat.fromAxis(axis);
orient.set(Quat.mul(rotation, orient.get()));
};
document.addEventListener("mousemove", handleMove);
document.addEventListener("touchmove", (event) => {
const delta = event.changedTouches[0];
event.clientX = delta.clientX;
event.clientY = delta.clientY;
handleMove(event);
});
let lastUpdate = 0;
const updateFrame = (timestamp) => {
if (lastUpdate == 0) lastUpdate = timestamp;
const delta = (timestamp - lastUpdate) / 1000;
lastUpdate = timestamp;
if (mouse.down) {
velocity = impulseThisFrame.scale(1 / delta);
impulseThisFrame = Vec.ZERO;
requestAnimationFrame(updateFrame);
return;
}
const decay = Math.exp(-delta * friction);
const effectiveDelta = friction > 0 ? (1 - decay) / friction : delta;
let theta = effectiveDelta * velocity.length();
velocity.x *= decay;
velocity.y *= decay;
velocity.z *= decay;
if (friction > 0 && velocity.length() < 0.00001) {
theta += velocity.length() / friction;
velocity.x = 0;
velocity.y = 0;
velocity.z = 0;
}
if (globalThis.performance.now() - mouse.lastMove > screensaverTimeoutMs) {
const impulse = Vec(0.7, 0.7, -0.7);
velocity = Vec.sum(impulse.scale(effectiveDelta * 3), velocity);
}
const axis = Vec(velocity.x, velocity.y, velocity.z)
.normalize()
.scale(theta);
const rotation = Quat.fromAxis(axis);
orient.set(Quat.mul(rotation, orient.get()));
requestAnimationFrame(updateFrame);
};
updateFrame(0);
</script>

View file

@ -0,0 +1,63 @@
---
layout: null
---
<head>
<!-- CHARSET -->
<meta charset="UTF-8">
<!-- VIEWPORT -->
<meta name="viewport" content="width=device-width,initial-scale=1.0{{ if prevent_zoom }},maximum-scale=1.0,user-scalable=0{{ /if }}">
<!-- NUKE DARKREADER -->
<meta name="darkreader-lock">
<!-- META -->
{{ if title }} <title>{{ title }}</title> {{ /if }}
{{ if description }} <meta name="description" content="{{ description }}"> {{ /if }}
{{ if author }} <meta name="author" content="{{ author }}"> {{ /if }}
{{ if keywords }} <meta name="keywords" content="{{ keywords.join(",") }}"> {{ /if }}
<!-- FANCY -->
<link rel="icon" href="/assets/icons/icon.gif">
<meta name="theme-color" content="{{ color }}">
<!-- ALTERNATIVE FORMATS -->
<link rel="alternate" type="application/rss+xml" href="/blog.rss">
<link rel="alternate" type="application/feed+json" href="/blog.json">
<!-- CANONICAL URL -->
<link rel="canonical" href="{{ url |> url(true) }}">
<!-- OPENGRAPH CONTAINMENT ZONE -->
{{ if title }} <meta property="og:title" name="title" content="{{ title }}"> {{ /if }}
{{ if description}} <meta property="og:description" content="{{ description }}"> {{ /if }}
<meta property="og:type" content="{{ type ?? "website" }}">
<meta property="og:locale" content="en_US">
<meta property="og:site_name" content="{{ site_name }}">
<meta property="og:url" content="{{ url |> url(true) }}">
{{ if thumbnail }}
<meta property="og:image" name="image" content="{{ thumbnail |> url(true) }}">
{{ else }}
<meta property="og:image" name="image" content="{{ "/assets/icons/icon.webp" |> url(true) }}">
{{ /if }}
<!-- TWITTER CONTAINMENT ZONE -->
{{ if title }} <meta name="twitter:title" content="{{ title }}"> {{ /if }}
<meta name="twitter:creator" content="HSVSphere">
{{ if thumbnail }}
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:image" content="{{ thumbnail |> url(true) }}">
{{ else }}
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="{{ "/assets/icons/icon.webp" |> url(true) }}">
{{ /if }}
<!-- STYLES -->
<link href="/assets/css/fonts.css" rel="stylesheet" inline>
<link href="/assets/css/default.css" rel="stylesheet" inline>
</head>
<body class="bg-white text-black dark:bg-black dark:text-white">
{{ content }}
</body>

View file

@ -1,196 +1,47 @@
---
layout: base.vto
layout: default.vto
---
<style>
/* Centering */
html,
body {
display: flex;
justify-content: center;
}
<div class="flex justify-center">
<div class="block text-xl w-[min(100vw,50rem)]">
.centered {
font-size: large;
<nav class="
transform-[rotateX(180deg)]
text-black text-2xl font-bold
absolute w-[inherit] z-999
overflow-x-auto
">
<div class="transform-[rotateX(180deg)] flex items-start justify-evenly">
{{> const style = `
flex-auto text-center px-3 pt-3 pb-2
bg-white font-[ecrou] border-2 border-black
origin-bottom
md:hover:text-[300%] ease-[cubic-bezier(1,-0.07,0.57,1.56)] md:duration-200
display: initial;
width: min(100vw - 2rem, 50rem);
}
md:hover:not-active:after:content-['?']
md:active:after:content-['!!'] active:after:italic
/* Make wrapping pretty */
h1,
h2,
h3,
h4 {
text-wrap: balance;
}
p {
text-wrap: pretty;
}
/* Rotate nav, tables and codeblocks so the scrollbar is at the top */
.rotated {
transform: rotateX(180deg);
}
/* Make tables and other elements scroll horizontally */
.overflow {
overflow-x: auto;
}
/* Style nav */
nav {
font-size: larger;
word-break: normal;
overflow-wrap: normal;
overflow-x: auto;
background-color: white;
padding: 0.3rem 0.6rem;
/* Rotated 180deg so it's top instead of bottom. */
border-top-left-radius: 1rem;
border-top-right-radius: 1rem;
}
@media (prefers-color-scheme: light) {
nav {
border: 0.15rem solid black;
border-bottom-width: 0;
}
}
nav a {
color: black;
font-weight: bold;
margin-right: 0.6rem;
}
/* Make images fit */
p:has(img) {
display: flex;
justify-content: center;
}
img {
max-width: 100%;
height: auto;
}
/* Style content */
.content {
overflow-wrap: break-word;
}
a {
color: var(--link);
}
a:hover {
font-style: italic;
}
blockquote {
border-left: 0.15rem solid var(--foreground);
padding: 0.05rem 1rem;
margin: 1.3rem 1rem;
}
@media (prefers-color-scheme: dark) {
blockquote {
background-color: #222;
}
}
@media (prefers-color-scheme: light) {
blockquote {
background-color: #EEE;
}
}
/* Style tables */
table {
border-collapse: collapse;
white-space: nowrap;
}
/* Doesn't seem to work with the scrolling? */
/* thead {
position: sticky;
top: 0;
} */
th, td {
border: 0.15rem solid var(--foreground);
padding: 0.3rem;
}
th {
background-color: var(--foreground);
color: var(--background);
}
/* Codeblocks */
.rotated:has(pre) {
padding: 0.6rem;
border: 0.15rem solid var(--foreground);
}
pre {
margin: 0;
}
@media (prefers-color-scheme: dark) {
.hljs-attr { color: lightblue; }
.hljs-built_in { color: firebrick; }
.hljs-keyword { color: firebrick; }
.hljs-number { color: mediumslateblue; }
.hljs-string { color: limegreen; }
.hljs-title { color: lightcoral; }
.hljs-type { color: aquamarine; }
}
@media (prefers-color-scheme: light) {
.hljs-attr { color: darkblue; }
.hljs-built_in { color: darkred; }
.hljs-keyword { color: darkred; }
.hljs-number { color: darkslateblue; }
.hljs-string { color: darkgreen; }
.hljs-title { color: darkgoldenrod; }
.hljs-type { color: darkcyan; }
}
/* The end */
hr {
border: 0.15rem solid var(--foreground);
}
footer {
display: flex;
justify-content: center;
font-size: small;
margin-bottom: 1rem;
padding-top: 0.5rem;
}
</style>
<div class="centered">
<nav class="rotated">
<div class="rotated">
<a href="/">HOME</a>
<a href="/about">ABOUT</a>
<a href="/blog">BLOG</a>
<a href="/contact">CONTACT</a>
</div>
</nav>
nuclear
` }}
<style>.right-delete:has(+ *:hover) { border-right-width: 0; }</style>
<a class="nav-link {{ style }} right-delete" href="/">home</a>
<a class="nav-link {{ style }} right-delete not-hover:border-l-0" href="/about/">about</a>
<a class="nav-link {{ style }} right-delete not-hover:border-l-0" href="/blog/">blog</a>
<a class="nav-link {{ style }} not-hover:border-l-0" href="/contact/">contact</a>
</div>
</nav>
{{> const padding = 4 }} <!-- Keep in sync with -mx-N in default.css. -->
<div class="text-content p-{{ padding }} pt-{{ padding + 12 }}">
{{ content }}
<hr>
<footer>Copyright {{ new Date().getFullYear() }} © RGBCube</footer>
</div>
<hr class="border-2 border-black dark:border-white">
<footer class="flex justify-center text-sm pt-1 pb-2 wrap-anywhere">
Copyright {{ Temporal.Now.plainDateISO().year }} ©
<a href="/"><img class="pl-0.5 pt-0.25 size-5" src="/assets/icons/icon.gif" alt="RGBCube"></a>
</footer>
</div>
</div>