refactor: large refactor around monorepo

Just a commit point while I'm testing stuff. Already decided at this
point to simplify and revert away from PayloadCMS.
This commit is contained in:
2026-01-04 13:18:34 -06:00
parent 31b1804fc9
commit af81d8e048
110 changed files with 8392 additions and 12918 deletions
+1
View File
@@ -0,0 +1 @@
engine-strict=true
+46
View File
@@ -0,0 +1,46 @@
{
"name": "@xevion/web",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite dev --port 5000",
"build": "vite build",
"preview": "vite preview --port 5000",
"start": "node build/index.js",
"prepare": "svelte-kit sync || echo ''",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"deploy": "echo 'Use Docker deployment'",
"clean": "rm -rf .svelte-kit build .turbo node_modules"
},
"dependencies": {
"@fontsource-variable/inter": "^5.1.0",
"@fontsource/hanken-grotesk": "^5.1.0",
"@fontsource/schibsted-grotesk": "^5.2.8",
"@xevion/db": "workspace:*",
"bits-ui": "^2.8.2",
"clsx": "^2.1.1",
"drizzle-orm": "^0.44.7",
"p5": "^1.11.7",
"pino": "^9.6.0",
"pino-pretty": "^13.0.0",
"postgres": "^3.4.5",
"svelte-wrap-balancer": "^0.0.4",
"tailwind-merge": "^3.3.1"
},
"devDependencies": {
"@iconify/json": "^2.2.424",
"@sveltejs/adapter-node": "^5.2.9",
"@sveltejs/kit": "^2.21.0",
"@sveltejs/vite-plugin-svelte": "^6.2.1",
"@tailwindcss/vite": "^4.1.11",
"@types/p5": "^1.7.7",
"svelte": "^5.45.6",
"svelte-adapter-bun": "^1.0.1",
"svelte-check": "^4.3.4",
"tailwindcss": "^4.1.11",
"unplugin-icons": "^22.5.0",
"vite": "^7.2.6"
}
}
+108
View File
@@ -0,0 +1,108 @@
@import "tailwindcss";
@theme {
/* Custom colors */
--color-zinc-850: #1d1d20;
/* Custom font sizes */
--font-size-10xl: 10rem;
/* Drop shadows */
--drop-shadow-extreme: 0 0 50px black;
/* Font families */
--font-inter: "Inter Variable", sans-serif;
--font-hanken: "Hanken Grotesk", sans-serif;
--font-schibsted: "Schibsted Grotesk", sans-serif;
/* Background images */
--background-image-gradient-radial: radial-gradient(
50% 50% at 50% 50%,
var(--tw-gradient-stops)
);
/* Animations */
--animate-bg-fast: fade 0.5s ease-in-out 0.5s forwards;
--animate-bg: fade 2.5s ease-in-out 1.5s forwards;
--animate-fade-in: fade-in 2.5s ease-in-out forwards;
--animate-title: title 3s ease-out forwards;
--animate-fade-left: fade-left 3s ease-in-out forwards;
--animate-fade-right: fade-right 3s ease-in-out forwards;
}
@keyframes fade {
0% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
@keyframes fade-in {
0% {
opacity: 0%;
}
75% {
opacity: 0%;
}
100% {
opacity: 100%;
}
}
@keyframes fade-left {
0% {
transform: translateX(100%);
opacity: 0%;
}
30% {
transform: translateX(0%);
opacity: 100%;
}
100% {
opacity: 0%;
}
}
@keyframes fade-right {
0% {
transform: translateX(-100%);
opacity: 0%;
}
30% {
transform: translateX(0%);
opacity: 100%;
}
100% {
opacity: 0%;
}
}
@keyframes title {
0% {
line-height: 0%;
letter-spacing: 0.25em;
opacity: 0;
}
25% {
line-height: 0%;
opacity: 0%;
}
80% {
opacity: 100%;
}
100% {
line-height: 100%;
opacity: 100%;
}
}
html,
body {
@apply font-inter overflow-x-hidden text-white;
}
body {
@apply h-full;
}
+11
View File
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="107" height="128" viewBox="0 0 107 128"><title>svelte-logo</title><path d="M94.157 22.819c-10.4-14.885-30.94-19.297-45.792-9.835L22.282 29.608A29.92 29.92 0 0 0 8.764 49.65a31.5 31.5 0 0 0 3.108 20.231 30 30 0 0 0-4.477 11.183 31.9 31.9 0 0 0 5.448 24.116c10.402 14.887 30.942 19.297 45.791 9.835l26.083-16.624A29.92 29.92 0 0 0 98.235 78.35a31.53 31.53 0 0 0-3.105-20.232 30 30 0 0 0 4.474-11.182 31.88 31.88 0 0 0-5.447-24.116" style="fill:#ff3e00"/><path d="M45.817 106.582a20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.503 18 18 0 0 1 .624-2.435l.49-1.498 1.337.981a33.6 33.6 0 0 0 10.203 5.098l.97.294-.09.968a5.85 5.85 0 0 0 1.052 3.878 6.24 6.24 0 0 0 6.695 2.485 5.8 5.8 0 0 0 1.603-.704L69.27 76.28a5.43 5.43 0 0 0 2.45-3.631 5.8 5.8 0 0 0-.987-4.371 6.24 6.24 0 0 0-6.698-2.487 5.7 5.7 0 0 0-1.6.704l-9.953 6.345a19 19 0 0 1-5.296 2.326 20.72 20.72 0 0 1-22.237-8.243 19.17 19.17 0 0 1-3.277-14.502 17.99 17.99 0 0 1 8.13-12.052l26.081-16.623a19 19 0 0 1 5.3-2.329 20.72 20.72 0 0 1 22.237 8.243 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-.624 2.435l-.49 1.498-1.337-.98a33.6 33.6 0 0 0-10.203-5.1l-.97-.294.09-.968a5.86 5.86 0 0 0-1.052-3.878 6.24 6.24 0 0 0-6.696-2.485 5.8 5.8 0 0 0-1.602.704L37.73 51.72a5.42 5.42 0 0 0-2.449 3.63 5.79 5.79 0 0 0 .986 4.372 6.24 6.24 0 0 0 6.698 2.486 5.8 5.8 0 0 0 1.602-.704l9.952-6.342a19 19 0 0 1 5.295-2.328 20.72 20.72 0 0 1 22.237 8.242 19.17 19.17 0 0 1 3.277 14.503 18 18 0 0 1-8.13 12.053l-26.081 16.622a19 19 0 0 1-5.3 2.328" style="fill:#fff"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,25 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { Snippet } from "svelte";
import Dots from "./Dots.svelte";
let {
class: className = "",
backgroundClass = "",
children,
}: {
class?: string;
backgroundClass?: string;
children?: Snippet;
} = $props();
</script>
<div class="pointer-events-none fixed inset-0 -z-20 bg-black"></div>
<Dots class={[
backgroundClass
]} />
<main class={cn("relative min-h-screen text-zinc-50", className)}>
{#if children}
{@render children()}
{/if}
</main>
+360
View File
@@ -0,0 +1,360 @@
<script lang="ts">
import { cn } from "$lib/utils";
import type { ClassValue } from "clsx";
import { onMount, onDestroy } from "svelte";
let { class: className = "" }: { class?: ClassValue } = $props();
let canvas: HTMLCanvasElement;
let gl: WebGLRenderingContext | null = null;
let animationId: number | null = null;
// Noise sampling scale - larger values create smoother, more gradual flow patterns
const SCALE = 1000;
// Maximum displacement distance from grid position (in pixels)
const LENGTH = 10;
// Distance between grid points (in pixels) - controls dot density
const SPACING = 20;
// Global animation speed multiplier - higher values make everything move faster
const TIMESCALE = 10.25 / 1000;
// Rotation/angle animation speed multiplier
const ANGLE_TIME_SCALE = 2.0;
// Pulsing/length animation speed multiplier
const LENGTH_TIME_SCALE = 1.5;
// Base opacity of dots (0-1)
const OPACITY = 0.9;
// Radius of each dot (in pixels)
const RADIUS = 3.5;
// How much opacity varies with angle (0-1)
const ANGLE_OPACITY_AMPLITUDE = 0.8;
// Minimum opacity from angle calculation
const ANGLE_OPACITY_FLOOR = 0.1;
// Lower bound of random per-dot opacity
const RANDOM_OPACITY_MIN = 0.5;
// Upper bound of random per-dot opacity
const RANDOM_OPACITY_MAX = 1.0;
// Simplex noise GLSL implementation
const vertexShader = `
attribute vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
}
`;
const fragmentShader = `
precision mediump float;
uniform vec2 u_resolution;
uniform float u_time;
uniform float u_seed;
uniform float u_dpr;
uniform float u_scale;
uniform float u_length;
uniform float u_spacing;
uniform float u_opacity;
uniform float u_radius;
uniform float u_angleTimeScale;
uniform float u_lengthTimeScale;
uniform float u_angleOpacityAmp;
uniform float u_angleOpacityFloor;
uniform float u_randomOpacityMin;
uniform float u_randomOpacityMax;
const float PI = 3.14159265359;
// Simplex 3D noise
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
float snoise(vec3 v) {
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
vec3 i = floor(v + dot(v, C.yyy));
vec3 x0 = v - i + dot(i, C.xxx);
vec3 g = step(x0.yzx, x0.xyz);
vec3 l = 1.0 - g;
vec3 i1 = min(g.xyz, l.zxy);
vec3 i2 = max(g.xyz, l.zxy);
vec3 x1 = x0 - i1 + C.xxx;
vec3 x2 = x0 - i2 + C.yyy;
vec3 x3 = x0 - D.yyy;
i = mod289(i + u_seed);
vec4 p = permute(permute(permute(
i.z + vec4(0.0, i1.z, i2.z, 1.0))
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
float n_ = 0.142857142857;
vec3 ns = n_ * D.wyz - D.xzx;
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
vec4 x_ = floor(j * ns.z);
vec4 y_ = floor(j - 7.0 * x_);
vec4 x = x_ *ns.x + ns.yyyy;
vec4 y = y_ *ns.x + ns.yyyy;
vec4 h = 1.0 - abs(x) - abs(y);
vec4 b0 = vec4(x.xy, y.xy);
vec4 b1 = vec4(x.zw, y.zw);
vec4 s0 = floor(b0)*2.0 + 1.0;
vec4 s1 = floor(b1)*2.0 + 1.0;
vec4 sh = -step(h, vec4(0.0));
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
vec3 p0 = vec3(a0.xy, h.x);
vec3 p1 = vec3(a0.zw, h.y);
vec3 p2 = vec3(a1.xy, h.z);
vec3 p3 = vec3(a1.zw, h.w);
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
p0 *= norm.x;
p1 *= norm.y;
p2 *= norm.z;
p3 *= norm.w;
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
m = m * m;
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
}
// Hash function for random per-point opacity
float hash(vec2 p) {
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
}
// Convert snoise [-1,1] to p5-style noise [0,1]
float noise01(vec3 v) {
return (snoise(v) + 1.0) * 0.5;
}
void main() {
vec2 pixelCoord = gl_FragCoord.xy;
// Find nearest grid point (account for DPR)
float spacing = u_spacing * u_dpr;
float scaleDpr = u_scale * u_dpr;
vec2 gridCoord = floor(pixelCoord / spacing) * spacing;
// Calculate distance to all nearby grid points (9 neighbors)
float minDist = 1000000.0;
vec2 closestPoint = vec2(0.0);
float pointOpacity = 0.0;
for (float dx = -1.0; dx <= 1.0; dx += 1.0) {
for (float dy = -1.0; dy <= 1.0; dy += 1.0) {
vec2 testGrid = gridCoord + vec2(dx * spacing, dy * spacing);
// Get force direction at this grid point (matching original p5 formula)
// Original: (noise(x/SCALE, y/SCALE, z) - 0.5) * 2 * TWO_PI
float rad = (noise01(vec3(testGrid / scaleDpr, u_time * u_angleTimeScale)) - 0.5) * 4.0 * PI;
// Original: (noise(x/SCALE, y/SCALE, z*2) + 0.5) * LENGTH
float len = (noise01(vec3(testGrid / scaleDpr, u_time * u_lengthTimeScale)) + 0.5) * u_length * u_dpr;
// Calculate displaced position
vec2 displacedPoint = testGrid + vec2(cos(rad), sin(rad)) * len;
float dist = distance(pixelCoord, displacedPoint);
if (dist < minDist) {
minDist = dist;
closestPoint = testGrid;
pointOpacity = hash(testGrid) * (u_randomOpacityMax - u_randomOpacityMin) + u_randomOpacityMin;
}
}
}
// Recalculate angle for opacity calculation
float rad = (noise01(vec3(closestPoint / scaleDpr, u_time * u_angleTimeScale)) - 0.5) * 4.0 * PI;
// Draw circle with configurable radius
float circle = 1.0 - smoothstep(0.0, u_radius * u_dpr, minDist);
// Calculate opacity based on angle
float angleOpacity = (abs(cos(rad)) * u_angleOpacityAmp + u_angleOpacityFloor) * pointOpacity * u_opacity;
// Light gray dots with calculated opacity
gl_FragColor = vec4(vec3(200.0/255.0), circle * angleOpacity);
}
`;
function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | null {
const shader = gl.createShader(type);
if (!shader) return null;
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('Shader compile error:', gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl: WebGLRenderingContext, vertexShader: WebGLShader, fragmentShader: WebGLShader): WebGLProgram | null {
const program = gl.createProgram();
if (!program) return null;
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error('Program link error:', gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return null;
}
return program;
}
function resizeCanvas() {
if (!canvas) return;
const dpr = window.devicePixelRatio || 1;
canvas.width = window.innerWidth * dpr;
canvas.height = window.innerHeight * dpr;
canvas.style.width = `${window.innerWidth}px`;
canvas.style.height = `${window.innerHeight}px`;
if (gl) {
gl.viewport(0, 0, canvas.width, canvas.height);
}
}
onMount(() => {
gl = canvas.getContext('webgl', { alpha: true, premultipliedAlpha: false });
if (!gl) {
console.error('WebGL not supported');
return;
}
console.log('WebGL context created');
const vShader = createShader(gl, gl.VERTEX_SHADER, vertexShader);
const fShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShader);
if (!vShader || !fShader) {
console.error('Shader creation failed');
return;
}
console.log('Shaders compiled successfully');
const program = createProgram(gl, vShader, fShader);
if (!program) return;
// Set up geometry (full screen quad)
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]), gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(program, 'a_position');
const resolutionLocation = gl.getUniformLocation(program, 'u_resolution');
const timeLocation = gl.getUniformLocation(program, 'u_time');
const seedLocation = gl.getUniformLocation(program, 'u_seed');
const dprLocation = gl.getUniformLocation(program, 'u_dpr');
const scaleLocation = gl.getUniformLocation(program, 'u_scale');
const lengthLocation = gl.getUniformLocation(program, 'u_length');
const spacingLocation = gl.getUniformLocation(program, 'u_spacing');
const opacityLocation = gl.getUniformLocation(program, 'u_opacity');
const radiusLocation = gl.getUniformLocation(program, 'u_radius');
const angleTimeScaleLocation = gl.getUniformLocation(program, 'u_angleTimeScale');
const lengthTimeScaleLocation = gl.getUniformLocation(program, 'u_lengthTimeScale');
const angleOpacityAmpLocation = gl.getUniformLocation(program, 'u_angleOpacityAmp');
const angleOpacityFloorLocation = gl.getUniformLocation(program, 'u_angleOpacityFloor');
const randomOpacityMinLocation = gl.getUniformLocation(program, 'u_randomOpacityMin');
const randomOpacityMaxLocation = gl.getUniformLocation(program, 'u_randomOpacityMax');
gl.useProgram(program);
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// Enable blending for transparency
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
const dpr = window.devicePixelRatio || 1;
// Set static uniforms (these don't change per frame)
gl.uniform1f(seedLocation, Math.random() * 1000);
gl.uniform1f(dprLocation, dpr);
gl.uniform1f(scaleLocation, SCALE);
gl.uniform1f(lengthLocation, LENGTH);
gl.uniform1f(spacingLocation, SPACING);
gl.uniform1f(opacityLocation, OPACITY);
gl.uniform1f(radiusLocation, RADIUS);
gl.uniform1f(angleTimeScaleLocation, ANGLE_TIME_SCALE);
gl.uniform1f(lengthTimeScaleLocation, LENGTH_TIME_SCALE);
gl.uniform1f(angleOpacityAmpLocation, ANGLE_OPACITY_AMPLITUDE);
gl.uniform1f(angleOpacityFloorLocation, ANGLE_OPACITY_FLOOR);
gl.uniform1f(randomOpacityMinLocation, RANDOM_OPACITY_MIN);
gl.uniform1f(randomOpacityMaxLocation, RANDOM_OPACITY_MAX);
resizeCanvas();
let startTime = Date.now();
function render() {
if (!gl || !canvas) return;
const time = (Date.now() - startTime) / 1000 * TIMESCALE;
gl.uniform2f(resolutionLocation, canvas.width, canvas.height);
gl.uniform1f(timeLocation, time);
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 6);
animationId = requestAnimationFrame(render);
}
render();
window.addEventListener('resize', resizeCanvas);
return () => {
window.removeEventListener('resize', resizeCanvas);
};
});
onDestroy(() => {
if (animationId !== null) {
cancelAnimationFrame(animationId);
}
if (gl) {
gl.getExtension('WEBGL_lose_context')?.loseContext();
}
});
</script>
<!-- Dots overlay with fade-in animation -->
<canvas
bind:this={canvas}
class={cn(
"pointer-events-none fixed inset-0 -z-10",
className
)}
></canvas>
+1
View File
@@ -0,0 +1 @@
// place files you want to import through the `$lib` alias in this folder.
+33
View File
@@ -0,0 +1,33 @@
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "@xevion/db";
import { projectsRelations } from "@xevion/db";
const extendedSchema = {
...schema,
relations_projects: projectsRelations,
};
let _db: ReturnType<typeof drizzle> | null = null;
export function getDb() {
if (!_db) {
const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
throw new Error("DATABASE_URL environment variable is not set");
}
const sql = postgres(connectionString);
_db = drizzle(sql, { schema: extendedSchema });
}
return _db;
}
// For backward compatibility
export const db = new Proxy({} as ReturnType<typeof drizzle>, {
get(target, prop) {
return getDb()[prop as keyof ReturnType<typeof drizzle>];
},
});
+6
View File
@@ -0,0 +1,6 @@
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+21
View File
@@ -0,0 +1,21 @@
<script lang="ts">
import "../app.css";
import "@fontsource-variable/inter";
import "@fontsource/hanken-grotesk/900.css";
import "@fontsource/schibsted-grotesk/400.css";
import "@fontsource/schibsted-grotesk/500.css";
import "@fontsource/schibsted-grotesk/600.css";
let { children } = $props();
</script>
<svelte:head>
<link rel="icon" href="/favicon.ico" />
<title>Xevion.dev</title>
<meta
name="description"
content="The personal website of Xevion, a full-stack software developer."
/>
</svelte:head>
{@render children()}
+85
View File
@@ -0,0 +1,85 @@
<script lang="ts">
import AppWrapper from "$lib/components/AppWrapper.svelte";
import IconSimpleIconsGithub from "~icons/simple-icons/github";
import IconSimpleIconsLinkedin from "~icons/simple-icons/linkedin";
import IconSimpleIconsDiscord from "~icons/simple-icons/discord";
import MaterialSymbolsMailRounded from "~icons/material-symbols/mail-rounded";
import MaterialSymbolsVpnKey from "~icons/material-symbols/vpn-key";
import IconLucideRss from "~icons/lucide/rss";
</script>
<AppWrapper class="overflow-x-hidden font-schibsted">
<!-- Top Navigation Bar -->
<div class="flex w-full justify-end items-center pt-5 px-6 pb-9">
<div class="flex gap-4 items-center">
<!-- <a href="/rss" class="text-zinc-400 hover:text-zinc-200">
<IconLucideRss class="size-5" />
</a> -->
</div>
</div>
<!-- Main Content -->
<div class="flex items-center flex-col">
<div
class="max-w-2xl mx-6 border-b border-zinc-700 divide-y divide-zinc-700"
>
<!-- Name & Occupation -->
<div class="flex flex-col pb-4">
<span class="text-3xl font-bold text-white">Ryan Walters,</span>
<span class="text-2xl font-normal text-zinc-400">
Full-Stack Software Engineer
</span>
</div>
<div class="py-4 text-zinc-200">
<p class="text-[0.95em]">
A fanatical software engineer with expertise and passion for sound,
scalable and high-performance applications. I'm always working on
something new. <br />
Sometimes innovative &mdash; sometimes crazy.
</p>
</div>
<div class="py-3">
<span class="text-zinc-200">Connect with me</span>
<div class="flex gap-x-2 pl-3 pt-3 pb-2">
<a
href="https://github.com/Xevion"
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<IconSimpleIconsGithub class="size-4 text-zinc-300" />
<span class="text-sm text-zinc-100">GitHub</span>
</a>
<a
href="https://linkedin.com/in/ryancwalters"
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<IconSimpleIconsLinkedin class="size-4 text-zinc-300" />
<span class="text-sm text-zinc-100">LinkedIn</span>
</a>
<a
href="#"
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<IconSimpleIconsDiscord class="size-4 text-zinc-300" />
<span class="text-sm text-zinc-100">Discord</span>
</a>
<a
href="mailto:your.email@example.com"
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<MaterialSymbolsMailRounded class="size-4.5 text-zinc-300" />
<span class="text-sm text-zinc-100">Email</span>
</a>
<a
href="#"
class="flex items-center gap-x-1.5 px-1.5 py-1 rounded-sm bg-zinc-900 shadow-sm hover:bg-zinc-800 transition-colors"
>
<MaterialSymbolsVpnKey class="size-4.5 text-zinc-300" />
<span class="text-sm text-zinc-100">PGP Key</span>
</a>
</div>
</div>
</div>
</div>
</AppWrapper>
+12
View File
@@ -0,0 +1,12 @@
<script lang="ts">
import AppWrapper from "$lib/components/AppWrapper.svelte";
</script>
<AppWrapper>
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<h1 class="font-hanken text-4xl text-zinc-200 md:text-5xl">Blog</h1>
<p class="mt-4 text-lg text-zinc-400">Coming soon...</p>
</div>
</div>
</AppWrapper>
@@ -0,0 +1,21 @@
import { getDb } from "$lib/server/db";
import { projects } from "@xevion/db";
import { eq, desc } from "drizzle-orm";
import type { PageServerLoad } from "./$types";
export const load: PageServerLoad = async (event) => {
const db = getDb(event);
// Use Drizzle relations for efficient join query
const projectsWithLinks = await db.query.projects.findMany({
where: eq(projects.status, "published"),
orderBy: [desc(projects.updatedAt)],
with: {
links: true,
},
});
return {
projects: projectsWithLinks,
};
};
+61
View File
@@ -0,0 +1,61 @@
<script lang="ts">
import AppWrapper from "$lib/components/AppWrapper.svelte";
import Balancer from "svelte-wrap-balancer";
import { cn } from "$lib/utils";
let { data } = $props();
</script>
<AppWrapper>
<div
class="relative z-10 mx-auto grid grid-cols-1 justify-center gap-y-4 px-4 py-20 align-middle sm:grid-cols-2 md:max-w-[50rem] lg:max-w-[75rem] lg:grid-cols-3 lg:gap-y-9"
>
<div class="mb-3 text-center sm:col-span-2 md:mb-5 lg:col-span-3 lg:mb-7">
<h1 class="pb-3 font-hanken text-4xl text-zinc-200 opacity-100 md:text-5xl">
Projects
</h1>
<Balancer>
<p class="text-lg text-zinc-400">
created, maintained, or contributed to by me...
</p>
</Balancer>
</div>
{#each data.projects as project (project.id)}
{@const links = project.links}
{@const useAnchor = links.length > 0}
{@const href = useAnchor ? links[0].url : undefined}
<div class="max-w-fit">
<svelte:element
this={useAnchor ? "a" : "div"}
{href}
target={useAnchor ? "_blank" : undefined}
rel={useAnchor ? "noreferrer" : undefined}
title={project.name}
class="flex items-center justify-start overflow-hidden rounded bg-black/10 pb-2.5 pl-3 pr-5 pt-1 text-zinc-400 transition-colors hover:bg-zinc-500/10 hover:text-zinc-50"
>
<div class="flex h-full w-14 items-center justify-center pr-5">
<i
class={cn(
project.icon ?? "fa-heart",
"fa-solid text-3xl text-opacity-80 saturate-0"
)}
></i>
</div>
<div class="overflow-hidden">
<span class="text-sm md:text-base lg:text-lg">
{project.name}
</span>
<p
class="truncate text-xs opacity-70 md:text-sm lg:text-base"
title={project.shortDescription}
>
{project.shortDescription}
</p>
</div>
</svelte:element>
</div>
{/each}
</div>
</AppWrapper>
+15
View File
@@ -0,0 +1,15 @@
import { redirect } from "@sveltejs/kit";
import { getDb } from "$lib/server/db";
import { metadata } from "@xevion/db";
import type { RequestHandler } from "./$types";
export const GET: RequestHandler = async (event) => {
const db = getDb(event);
// TODO: Query the metadata global for resume URL
// For now, redirect to a placeholder
// const meta = await db.select().from(metadata).limit(1);
// Placeholder redirect until we have the schema set up
redirect(302, "https://example.com/resume.pdf");
};
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 648 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
==================================================================
https://keybase.io/xevion
--------------------------------------------------------------------
I hereby claim:
* I am an admin of https://xevion.dev
* I am xevion (https://keybase.io/xevion) on keybase.
* I have a public key with fingerprint 04EE F43A E775 B57F 5029 572E 1EF2 4DA2 70E7 9ECD
To do so, I am signing this object:
{
"body": {
"key": {
"eldest_kid": "012026f3d2df48d71d6ca5a33418101c284f7a73aac8944dc52edbfef17bb92369600a",
"fingerprint": "04eef43ae775b57f5029572e1ef24da270e79ecd",
"host": "keybase.io",
"key_id": "1ef24da270e79ecd",
"kid": "010167e9d3b055e24b9e6b83ec0e59f3e8daf5a5cbfe4f3f7edb7e1aa1e6a1f1dfd00a",
"uid": "120150a540a9487c406e6cf5e0423419",
"username": "xevion"
},
"merkle_root": {
"ctime": 1607280978,
"hash_meta": "ee4f985b1f5b4915e4963c7ce2945ac76673edc180e59ffdeafca8e57fcac740",
"seqno": 18330340
},
"revoke": {
"sig_ids": [
"4d1ae3b707b77cd3199a1a4d1c4becab985c2baecbc6c478da499b27d7fe7f230f"
]
},
"service": {
"hostname": "xevion.dev",
"protocol": "https:"
},
"type": "web_service_binding",
"version": 1
},
"ctime": 1607280982,
"expire_in": 157680000,
"prev": "12ae982e85b7fea5f216f2fe50d4145330bbf762a009c9183dc22d134da279ab",
"seqno": 32,
"tag": "signature"
}
which yields the signature:
-----BEGIN PGP MESSAGE-----
Version: Keybase OpenPGP v2.1.13
Comment: https://keybase.io/crypto
yMP0AnicbZN9bBRFGMavxUJaKbaAAkUElkSBVpidnd3ZPYyA0ICAjYBAaYVzZvZd
etz17rgvKNiqICpQ+YjhIyBobARUvhQaiESFhgQrVr7EohRKFSUCJYGKgYrU2SIx
Gvefzb77vO/zzG9mDmV28GSkPHL9mfciFzfVpRypiSU8vrpBUxcoPGyXKd4FSgDa
XxC0IRb3Bfy24lWQihE2HM3GtkNMm6q2IZjONI2opopUgU3iUEY1xoRpEWILHYPN
HXBUyrmFNcMyEGJKnuL4Q7MgGon6Q3F3LAFwiMaAUp3r1NERtnSKQQUHE5thioBa
IGzZWBKOuR0yHGcxGOIPy5r88LXH+x/9vdxINShYtsaRrgMm3AKDmxoIBLrlaGDa
zNGZLmRW4mgOlakpqIypYDDVUW3Hvps7cdcHI1VHTCeIWcSkgiADDOHogAiWKCxX
GINoiJWCVM+DpD8cUsrzlFKIBoLgi4bDcRetiPtdgWogik1kUVMuj8VKfKUQZ7IP
ZBTL1Lnq6JxYqg7EMjRBBWCL6ExQw6Aa2EI129fg2MAcwUyQ/IT8S5BMEYM5obB0
MDUNaQTJCFFIhgPgusf8syS1mOItVoitMtA4RZRTKmxNtSymMlkVhINgXKYQmDMQ
XBiCUAmLWBbH1KYOUAdryFFmlLtu0aRftA93t+lfyx9iQ1IGikTD8bAIB2W9JB6P
xLwulnhZxBXOBe77e4SP+0O2PCKyIwnRmIvPq0rlf4iZOE+BeRF/FHx+V6FTw0Ty
cX2knbtRDKQKJEUZlekOVg0HO6Ajm6hEl1Q4d6iBGUKWsCQmW2Bsq1r7IbIY/weh
Jq3ibJacKbmFWDwRBaW85uDz93lSMjwd01Ldu+PJSM+6d6MmHsjyVJ6P9Nze0jCO
v7JjUvCHdZ0K2sb0a1mRzFvb/VLfWk/jniVNlT6cf6JgW+m3t6JK9QtfTqvNH/v7
/QU3Dn8UxBfmrOzy/tPjGnqfahrQkdYeaDZHhCdXxNe/++ofbZnPtUVsz5rak4v3
bpvQ66uuT149/frMal/WzSt8+1B4dsvlPunHznyc03Vgn26hQZlDNiyZ+wXPT+09
uqIsc+TxziWJr4u7p530NOdcGvvB1W8e2P74mQG7qi7+tMjbevi1vluRfW7fzty0
hTkpqwo3li0TuY34WMHS5jtjro0s+5QUrfFXLjq116P30xdW3G4qOLIycKi48tzn
garNA9dMKY1UTbvua9l5Pe3sSy/n0vD4lEWzKz4cmKA3lW5HJwy+nZc9rP+KX3MP
tyWzT2WR5niwpbmqfveCTo1XWjOH7lrnOdgwTr2xP2tjYdfut2Y/ukEL+FqLr7UO
O7K5Qw91xoO35z6xLHtqed3pLYE5iZGn72QsbV1iNZc89Vj6psFF62syerzVeDY5
aVUpPf/2O6O8O5Tl+Wuzx5/YOWp+aq9V00fM9o+ad3nrpM6Jarh0fPRR1Dh6XzVJ
zf4x3ml8Uf9Plr3ZM2XvjOX1dY2/FU1pSk+bvP8G7t/hl89mlpftyZ44vUufg2Lp
ntXbEkW8b/3xC8MnPJwTrKE/B18c8EbhUA75Kxu++75+4uqrY5K58//EhfuG7z77
UPFfKC78RA==
=XyA8
-----END PGP MESSAGE-----
And finally, I am proving ownership of this host by posting or
appending to this document.
View my publicly-auditable identity here: https://keybase.io/xevion
==================================================================
+3
View File
@@ -0,0 +1,3 @@
# allow crawling everything by default
User-agent: *
Disallow:
+19
View File
@@ -0,0 +1,19 @@
{
"name": "Xevion.dev",
"short_name": "Xevion.dev",
"icons": [
{
"src": "/android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#ffffff",
"background_color": "#000000",
"display": "standalone"
}
+19
View File
@@ -0,0 +1,19 @@
import adapter from "svelte-adapter-bun";
import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter({
out: "build",
precompress: false,
}),
alias: {
$components: "src/lib/components",
},
},
};
export default config;
+21
View File
@@ -0,0 +1,21 @@
{
"extends": "./.svelte-kit/tsconfig.json",
"compilerOptions": {
"rewriteRelativeImportExtensions": true,
"allowJs": true,
"checkJs": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"skipLibCheck": true,
"sourceMap": true,
"strict": true,
"moduleResolution": "bundler",
"types": ["unplugin-icons/types/svelte"]
}
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
//
// To make changes to top-level options such as include and exclude, we recommend extending
// the generated config; see https://svelte.dev/docs/kit/configuration#typescript
}
+8
View File
@@ -0,0 +1,8 @@
import { sveltekit } from "@sveltejs/kit/vite";
import tailwindcss from "@tailwindcss/vite";
import { defineConfig } from "vite";
import Icons from "unplugin-icons/vite";
export default defineConfig({
plugins: [tailwindcss(), sveltekit(), Icons({ compiler: "svelte" })],
});