changing frameworks

This commit is contained in:
2025-11-25 16:37:04 +00:00
parent ada53b87e2
commit 34bd66dd0d
67 changed files with 3125 additions and 491 deletions

38
nginx/vue/README.md Normal file
View File

@@ -0,0 +1,38 @@
# my-vue-app
This template should help get you started developing with Vue 3 in Vite.
## Recommended IDE Setup
[VS Code](https://code.visualstudio.com/) + [Vue (Official)](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur).
## Recommended Browser Setup
- Chromium-based browsers (Chrome, Edge, Brave, etc.):
- [Vue.js devtools](https://chromewebstore.google.com/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
- [Turn on Custom Object Formatter in Chrome DevTools](http://bit.ly/object-formatters)
- Firefox:
- [Vue.js devtools](https://addons.mozilla.org/en-US/firefox/addon/vue-js-devtools/)
- [Turn on Custom Object Formatter in Firefox DevTools](https://fxdx.dev/firefox-devtools-custom-object-formatters/)
## Customize configuration
See [Vite Configuration Reference](https://vite.dev/config/).
## Project Setup
```sh
npm install
```
### Compile and Hot-Reload for Development
```sh
npm run dev
```
### Compile and Minify for Production
```sh
npm run build
```

13
nginx/vue/index.html Normal file
View File

@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
nginx/vue/jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

2557
nginx/vue/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

23
nginx/vue/package.json Normal file
View File

@@ -0,0 +1,23 @@
{
"name": "nginx-html",
"version": "0.0.0",
"private": true,
"type": "module",
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"vue": "^3.5.22",
"vue-router": "^4.6.3"
},
"devDependencies": {
"@vitejs/plugin-vue": "^6.0.1",
"vite": "^7.1.11",
"vite-plugin-vue-devtools": "^8.0.3"
}
}

View File

@@ -0,0 +1,272 @@
/* Fonts */
@font-face {
font-family: "AldoTheApache";
src: url("/fonts/AldotheApache.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "RobotFont";
src: url("/fonts/Robot_Font.otf") format("opentype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "m12";
src: url("/fonts/m12.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "big_noodle_titling";
src: url("/fonts/big_noodle_titling.ttf") format("truetype");
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: "CreatoDisplay";
src: url("/fonts/CreatoDisplay-Bold.otf") format("opentype");
font-weight: normal;
font-style: normal;
}
/* Variables */
:root {
/* Blue - Beige */
/* --primary: #153448;
--secondary: #3C5B6F;
--tertiary: #948979;
--quaternary: #f5bb78;
--background: #DFD0B8; */
/* Blue - Turqouise */
/* --primary: #161D6F;
--secondary: #0B2F9F;
--tertiary: #98DED9;
--quaternary: #C7FFD8;
--background: #C2EFD1; */
/* Red - Blue */
/* --primary: #ff204e; */
/* --secondary: #a0153e; */
/* --tertiary: #5d0341; */
/* --quaternary: #3a0e41; */
/* --background: #00224d; */
/* Blue - Brown */
/* --primary: #35374B; */
/* --secondary: #344955; */
/* --tertiary: #50727b; */
/* --quaternary: #78a083; */
/* --background: #c7b077; */
/* Black - White */
--primary: black;
--secondary: black;
--tertiary: black;
--quaternary: #cccccc;
--background: white;
/* Blue - White */
/* --primary: #201e43; */
/* --secondary: #134b70; */
/* --tertiary: #508c9b; */
/* --quaternary: #cceeee; */
/* --background: #eeeeee; */
--font-heading: big_noodle_titling;
--font-text: CreatoDisplay;
--font-size-text: 90%;
--font-size-heading: 2.5em;
--font-size-tableheading: 1.2em;
}
/* A5 Page */
.a5page {
/* overflow: scroll; */
display: flex;
flex-direction: column;
font-family: var(--font-text);
height: 148mm;
width: 210mm;
padding: 5mm;
box-sizing: border-box;
background-color: var(--background);
box-shadow: 0 20px 20px rgba(0, 0, 0, 0.2);
border: solid 2px var(--primary);
}
/* A4 Page */
.a4page {
font-family: var(--font-text);
width: 210mm;
/* Standard A4 width */
height: 297mm;
/* Standard A4 height */
padding: 10mm;
box-sizing: border-box;
background-color: var(--background);
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
border: 1px solid var(--primary);
overflow: auto;
/* Enables scrolling when content exceeds height */
margin: 0 auto;
/* Centers the page horizontally */
}
/* Component Styling */
body {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
line-height: 1.6;
margin: 0;
padding: 0;
}
h1,
h2,
h3,
h4 {
color: var(--primary);
font-family: var(--font-heading);
text-transform: capitalize;
text-align: center;
}
h1 {
font-size: var(--font-size-heading);
}
h2 {
margin: 0px;
border-bottom: 2px solid var(--primary);
}
p {
color: var(--secondary);
font-size: var(--font-size-text);
margin-top: 0.3em;
margin-bottom: 0.5em;
}
table {
color: var(--secondary);
width: 100%;
border-collapse: collapse;
}
td {
/* border: 2px solid var(--tertiary); */
border-top: 1px solid var(--tertiary);
padding: 1px 0px 1px 10px;
font-size: var(--font-size-text);
text-align: center;
}
th {
border: 2px solid var(--tertiary);
padding: 1px 0px 1px 7px;
font-family: var(--font-heading);
font-size: var(--font-size-tableheading);
background-color: var(--quaternary);
text-align: left;
}
a {
text-decoration: none;
color: inherit;
}
a:hover,
a:visited {
color: inherit;
}
/* Classes */
/* Cover Navigation (for ease of use) */
.cover-nav {
position: fixed;
top: 0.5vh;
/* Position the element at the top of the screen */
left: 80vw;
/* Position the element at the left of the screen */
border: 2px solid var(--tertiary);
width: 19.5vw;
/* Make the element span the width of the screen (optional) */
background-color: var(--background);
/* Set a background color to avoid overlap issues */
z-index: 1000;
/* Ensures the element is above other content */
}
.cover-nav td,
tr {
font-family: var(--font-text);
border: 0;
}
.cover-nav th {
text-align: center;
align-items: center;
border-bottom: 2px solid var(--tertiary);
}
@media print {
.no-print {
display: none !important;
}
}
/* Cover letter styling */
textarea {
width: 100%;
height: 100%;
padding: 10px;
box-sizing: border-box;
border: 0px solid var(--primary);
resize: none;
font-family: var(--font-text);
}
/* Contact At Top of Page */
.contact {
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 2px solid var(--primary);
}
.contact-details {
display: flex;
flex-direction: column;
text-align: right;
}
.contact-details p {
margin: 2px 0;
}
/* Interests and Skills at bottom of page */
.interests {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-top: solid 2px var(--primary);
}
.interests td,
tr,
th {
border: 0;
}
.row-leftalign {
/* background-image: url("https://www.fridakahlo.org/assets/img/paintings/without-hope.jpg"); */
text-align: left;
}

View File

@@ -0,0 +1,56 @@
@media print {
.no-print {
display: none !important;
}
}
body {
background-image: linear-gradient(to bottom, aqua, blue);
font-family: "Times New Roman", Times, serif;
width: 100vw;
height: 100vh;
/*background-image: url("../img/background.png");
background-size: cover;*/
}
main {
width: 148mm; /* A5 width */
height: 210mm; /* A5 height */
max-width: 100%; /* responsive on smaller screens */
margin: 20px auto; /* center horizontally with some spacing */
padding: 20px;
background: white; /* paper color */
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); /* subtle paper shadow */
overflow-y: auto; /* scroll vertically if content overflows */
border: 1px solid #ccc; /* optional border for realism */
box-sizing: border-box; /* include padding in size */
font-family: "Times New Roman", serif; /* optional paper-like font */
}
p {
color: black;
}
h1,
h2,
h3,
h4 {
color: black;
}
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
width: 100%;
background-color: beige;
}
.album-img {
width: 50mm;
height: 50mm;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

113
nginx/vue/public/index.html Normal file
View File

@@ -0,0 +1,113 @@
<!doctype html>
<html lang="en">
<head>
<script
defer
src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"
></script>
<script>
function spotifyPlayer() {
return {
album_image: "/img/Untitled.png",
artist_name: "",
song_name: "",
song_url: "",
playing: false,
async fetchNowPlaying() {
try {
const res = await fetch(
"https://www.adam-french.co.uk/api/spotify",
);
const data = await res.json();
if (data.playing == false) {
this.album_image = "/img/Untitled.png";
return;
}
this.album_image = data.album_image;
this.artist_name = data.artist_name;
this.song_name = data.song_name;
this.song_url = data.song_url;
this.playing = data.playing;
} catch (err) {
console.error("Failed to fetch Spotify data", err);
}
},
};
}
</script>
<!--<script type="module">
import {renderToCanvas} from './js/mobile-automata.mjs'
const canvas = document.getElementById('automataCanvas');
renderToCanvas(canvas, canvas.width, canvas.height);
</script>-->
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AF</title>
<link rel="icon" type="img/x-icon" href="img/favicon.ico" />
<link rel="stylesheet" href="css/styles.css" />
</head>
<body>
<canvas
id="automataCanvas"
style="
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
display: block;
z-index: -1;
"
></canvas>
<main>
<h1>Welcome</h1>
<h2>whoami?</h2>
<p>Hi im Adam</p>
<h2>cv</h2>
<a href="/pages/cv/index.html">CV</a>
<h2>bookmarks</h2>
<a href="/pages/bookmarks.html">bookmarks</a>
<h2>Listening to:</h2>
<div
x-data="spotifyPlayer()"
x-init="fetchNowPlaying(); setInterval(fetchNowPlaying, 60000)"
class="spotify-card"
>
<img :src="album_image" class="album-img" alt="" />
<div class="spotify-info">
<div x-text="song_name || 'No song playing'"></div>
<div x-text="artist_name"></div>
<div
x-text="playing ? 'Playing' : ''"
:class="{'playing': playing}"
></div>
<a :href="song_url"></a>
</div>
</div>
<!--<h2> </h2>
<p>
Sometimes there's this fire that sends shivers down my back.
It'll come when I'm lis
</p>
-->
<!--<h2>Shrines</h1>
<a href="/pages/shrines/evangelion.html">Evangelion</a>
<a href="/pages/shrines/skipskipbenben.html">Skip skip ben ben</a>
<a href="/pages/shrines/demoman.html">demoman</a>-->
<!--<a href="pages/shrines/gto.html">GTO</a>-->
</main>
</body>
</html>

26
nginx/vue/src/App.vue Normal file
View File

@@ -0,0 +1,26 @@
<script setup>
import { RouterLink, RouterView } from "vue-router";
</script>
<template>
<header>
<img
alt="Vue logo"
class="logo"
src="@/assets/logo.svg"
width="125"
height="125"
/>
<div class="wrapper">
<nav>
<RouterLink to="/">Home</RouterLink>
<RouterLink to="/about">About</RouterLink>
</nav>
</div>
</header>
<RouterView />
</template>
<style scoped></style>

View File

@@ -0,0 +1,347 @@
function integerDigits(n, b = 10, length = null) {
// Get the list of digits in base b
const digits = [];
while (n > 0) {
digits.push(n % b);
n = Math.floor(n / b);
}
digits.reverse(); // Reverse the list to get digits in big endian order
// Pad with zeros if length is specified
if (length !== null) {
const padding = Array(Math.max(0, length - digits.length)).fill(0);
return padding.concat(digits);
}
return digits;
}
function* cartesianProduct(...arrays) {
// Generator for cartesian product
if (arrays.length === 0) {
yield [];
return;
}
const [first, ...rest] = arrays;
if (rest.length === 0) {
for (const item of first) {
yield [item];
}
} else {
for (const item of first) {
for (const combo of cartesianProduct(...rest)) {
yield [item, ...combo];
}
}
}
}
function tuplesFromList(lst, n) {
const arrays = Array(n).fill(lst);
return Array.from(cartesianProduct(...arrays));
}
function tuplesFromMultipleLists(...lists) {
return Array.from(cartesianProduct(...lists));
}
function flattenTuples(tuples) {
return tuples.flat();
}
function partition(lst, n) {
const result = [];
for (let i = 0; i < lst.length; i += n) {
result.push(lst.slice(i, i + n));
}
return result;
}
function pick(pickList, lst) {
const trues = [];
const falses = [];
for (let i = 0; i < pickList.length; i++) {
if (pickList[i]) {
trues.push(lst[i]);
} else {
falses.push(lst[i]);
}
}
return [trues, falses];
}
function factorial(n) {
if (n < 0) return NaN;
if (n === 0 || n === 1) return 1;
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
}
function unrankPermutation(r, lst) {
const n = lst.length;
r -= 1; // Convert r to 0-indexed
const permutation = [];
const availableElements = [...lst];
for (let i = n; i > 0; i--) {
const fact = factorial(i - 1); // (n-1)!
const index = Math.floor(r / fact); // Find the index of the current element
permutation.push(availableElements.splice(index, 1)[0]); // Add the element and remove it from available
r %= fact; // Update r to find the next element
}
return permutation;
}
export function toMaRule(sn, dn, n, k) {
if (n < 1 || n % 2 === 0) {
throw new Error("n must be >= 1 and odd");
}
const inputs = tuplesFromList([...Array(k).keys()], n);
const directions = integerDigits(dn, 2, Math.pow(k, n)).map((x) =>
Math.pow(-1, x),
);
const snDigits = integerDigits(sn, k, n * Math.pow(k, n));
const outputs = partition(snDigits, n);
const rules = {};
for (let i = 0; i < inputs.length; i++) {
rules[JSON.stringify(inputs[i])] = [outputs[i], directions[i]];
}
return rules;
}
export function toReversibleMaRule(bn, pn, n, k) {
if (n < 1 || n % 2 === 0) {
throw new Error("n must be >= 1 and odd");
}
const inputs = tuplesFromList([...Array(k).keys()], n);
const blockers = tuplesFromList([...Array(k).keys()], n - 2);
const blockSelect = pick(integerDigits(bn, 2, Math.pow(k, n - 2)), blockers);
const rightBlockers = blockSelect[0];
const leftBlockers = blockSelect[1];
const twoFair = tuplesFromList([...Array(k).keys()], 2);
const leftOutputs = tuplesFromMultipleLists(leftBlockers, twoFair).map(
(x) => [flattenTuples(x), -1],
);
const rightOutputs = tuplesFromMultipleLists(twoFair, rightBlockers).map(
(x) => [flattenTuples(x), 1],
);
const outputs = [...leftOutputs, ...rightOutputs];
const rankedOutputs = unrankPermutation(pn, outputs);
const rules = {};
for (let i = 0; i < inputs.length; i++) {
rules[JSON.stringify(inputs[i])] = rankedOutputs[i];
}
return rules;
}
export function maStep(rules, state, r) {
/**
* Apply one step of the mobile automaton rules
*
* Args:
* rules (object): Dictionary of rules where key is input tuple and value is [output_tuple, direction]
* state (array): [list, head] where list is current state and head is current position
* r (number): Radius of the neighborhood (window size = 2r + 1)
*
* Returns:
* array: [new_list, new_head] or [[], -1] if out of bounds
*/
const [currentList, head] = state;
// Check bounds
if (head - r <= 0 || head + r >= currentList.length) {
return [[], -1];
}
// Get the window of elements centered at head
const window = currentList.slice(head - r, head + r + 1);
// Apply rule
const ruleKey = JSON.stringify(window);
const [newWindow, direction] = rules[ruleKey];
// Create new list with replaced elements
const newList = [...currentList];
for (let i = 0; i < newWindow.length; i++) {
newList[head - r + i] = newWindow[i];
}
return [newList, head + direction];
}
export function ma(rules, initialState, t) {
/**
* Perform t steps of the mobile automaton
*
* Args:
* rules (object): Dictionary of rules
* initialState (array): Initial [list, head] state
* t (number): Number of steps to perform
*
* Returns:
* array: List of states at each time step
*/
// Calculate radius from first rule key length
const firstKey = Object.keys(rules)[0];
const r = JSON.parse(firstKey).length / 2;
const states = [initialState];
let currentState = initialState;
for (let i = 0; i < t; i++) {
currentState = maStep(rules, currentState, r);
states.push(currentState);
// Stop if we hit an invalid state
if (currentState[0].length === 0) {
break;
}
}
return states;
}
export function cyclicMaStep(rules, state, r) {
/**
* Cyclic version: indexing wraps around the array.
*/
const [currentList, head] = state;
const n = currentList.length;
// --- Cyclic window extraction ---
const window = [];
for (let i = -r; i <= r; i++) {
window.push(currentList[(head + i + n) % n]);
}
// Apply rule
const ruleKey = JSON.stringify(window);
const [newWindow, direction] = rules[ruleKey];
// --- Cyclic writeback ---
const newList = [...currentList];
for (let offset = 0; offset < newWindow.length; offset++) {
newList[(head - r + offset + n) % n] = newWindow[offset];
}
// Move head cyclically
const newHead = (head + direction + n) % n;
return [newList, newHead];
}
export function cyclicMa(rules, initialState, t) {
/**
* Perform t steps of the mobile automaton
*
* Args:
* rules (object): Dictionary of rules
* initialState (array): Initial [list, head] state
* t (number): Number of steps to perform
*
* Returns:
* array: List of states at each time step
*/
// Calculate radius from first rule key length
const firstKey = Object.keys(rules)[0];
const r = JSON.parse(firstKey).length / 2;
const states = [initialState];
let currentState = initialState;
for (let i = 0; i < t; i++) {
currentState = cyclicMaStep(rules, currentState, r);
states.push(currentState);
// Stop if we hit an invalid state
if (currentState[0].length === 0) {
break;
}
}
return states;
}
// export function renderToCanvas(canvas, width, height) {
// let states = Array.from({ length: height }, () =>
// Array.from({ length: width }, () => Math.round(Math.random() + 0.4)),
// );
// }
export function renderMaToCanvas(canvas, width, height, sn = 0, dn = 0) {
if (sn == 0) {
const min = 1500000;
const max = 2000000;
sn = Math.floor(Math.random() * (max - min + 1)) + min;
}
if (dn == 0) {
const min = 100;
const max = 200;
dn = Math.floor(Math.random() * (max - min + 1)) + min;
}
const r = 1;
const n = 2 * r + 1;
const rules = toMaRule(sn, dn, n, 2);
let states = Array.from({ length: height }, () =>
Array.from({ length: width }, () => Math.round(Math.random() + 0.4)),
);
let head = Math.floor(width / 2) % width;
let row_num = 0;
const ctx = canvas.getContext("2d");
const img = ctx.createImageData(width, height);
const data = img.data;
const colorOn = [10, 60, 130]; // dark blue (active cell)
const colorOff = [10, 70, 110]; // darker blue (inactive cell)
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const idx = (y * width + x) * 4;
const color = states[y][x] ? colorOn : colorOff;
data[idx] = color[0]; // R
data[idx + 1] = color[1]; // G
data[idx + 2] = color[2]; // B
data[idx + 3] = 255; // A
}
}
ctx.putImageData(img, 0, 0);
function step() {
// calculate new state
let [newState, newHead] = cyclicMaStep(rules, [states[row_num], head], r);
states[row_num] = newState;
// write changed cells to ImageData
for (let x = head - r; x <= head + r; x++) {
const idx = (row_num * width + x) * 4;
const val = newState[x] ? colorOn : colorOff;
data[idx] = val[0]; // R
data[idx + 1] = val[1]; // G
data[idx + 2] = val[2]; // B
data[idx + 3] = 255; // A
}
// update canvas (only this row)
ctx.putImageData(img, head - r, row_num, 0, 0, n, 1);
// advance row and head
row_num = (row_num + 1) % height;
head = newHead;
requestAnimationFrame(step);
}
requestAnimationFrame(step);
}

11
nginx/vue/src/main.js Normal file
View File

@@ -0,0 +1,11 @@
import "./assets/main.css";
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
const app = createApp(App);
app.use(router);
app.mount("#app");

View File

@@ -0,0 +1,23 @@
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: Home,
},
{
path: "/cv",
name: "cv",
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import("../views/CV.vue"),
},
],
});
export default router;

359
nginx/vue/src/views/CV.vue Normal file
View File

@@ -0,0 +1,359 @@
<template>
<div>
<RouterLink class="no-print" to="/">Home</RouterLink>
<div
class="no-print"
style="width: 100%; text-align: center; margin: 20px 0"
>
<h1>Page 1</h1>
</div>
<div class="a4page">
<div class="contact">
<h1>Adam French</h1>
<!-- <a href="covers.html"><img width=25 height=50 src="img/rune.png"></a> -->
<div class="contact-details">
<p>+447563266931</p>
<p>adam.a.french@outlook.com</p>
</div>
</div>
<h2>Profile</h2>
<p>
Passionate about developing robust, efficient software with a
strong focus on maintainability, scalability, and long-term
performance. I take pride in my ability to perform under
pressure, adapt quickly, and contribute effectively in
collaborative, fast-paced environments.
</p>
<p>
My ideal role involves designing and building scalable systems
that balance creativity with problem-solving. I aim to cultivate
meaningful professional connections and contribute to projects
that deliver a clear altruistic impact.
</p>
<h2>Education</h2>
<table>
<thead>
<tr>
<th>Location</th>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>The University of Leeds</td>
<td>
<!-- <div style="display: flex; flex-direction: column; align-items: center;"> -->
<!-- <span>2021</span> -->
<!-- <span>to</span> -->
<!-- <span>2025</span> -->
<!-- </div> -->
2021-2025
</td>
<td class="row-leftalign">
<strong
>BSc Computer Science with Mathematics
(International)</strong
><br />
<strong
>Average:
81.1%&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;&ensp;(First
Class Honours) </strong
><br />
<strong>Relevant Courses: </strong>
Procedural Programming, Object Oriented Programming,
Algorithms and Data Structures I & II, Databases,
Computer Processors, Compiler Design and
Construction, Formal Languages and Finite Automata,
Probability and Statistics I, Machine Learning,
Graph Algorithms & Complexity Theory
</td>
</tr>
<tr>
<td>The University of Waterloo</td>
<td>
<!-- <div style="display: flex; flex-direction: column; align-items: center;"> -->
<!-- <span>2023</span> -->
<!-- <span>to</span> -->
<!-- <span>2024</span> -->
<!-- </div> -->
2023-2024
</td>
<td class="row-leftalign">
<strong>Average: 74.5%</strong>
<br />
<strong>Relevant Courses:</strong>
Applied Cryptography, Introduction to Computer
Graphics, Introduction to Rings and Fields with
Applications<br /><br />
</td>
</tr>
</tbody>
</table>
<h2>Experience</h2>
<table>
<thead>
<tr>
<th>Role</th>
<th>Location</th>
<th>Date</th>
<th>Duties</th>
</tr>
</thead>
<tbody>
<tr>
<td>Student</td>
<td>Wolfram Summer School</td>
<td>2024</td>
<td class="row-leftalign">
Designed and completed a time-constrained research
project exploring Mobile Automata and conditions for
computational reversibility. Communicated findings
through visualizations and presentations.
</td>
</tr>
<tr>
<td>Bartender</td>
<td>Belgrave Music Hall</td>
<td>2022-2025</td>
<td class="row-leftalign">
Delivered heartfelt customer service in various
fast-paced, high-pressure hospitality environments.
</td>
</tr>
<tr>
<td>Cashier Assistant</td>
<td>To The Rise Bakery</td>
<td>Summer 2022</td>
<td class="row-leftalign">
Prepared coffee, served customers, presented goods,
cleaned bakery equipment, and made toasties.
</td>
</tr>
<tr>
<td>Waiter</td>
<td>BFI Bar and Kitchen</td>
<td>Summer 2020</td>
<td class="row-leftalign">
Managed bookings, allocated tables, handled
complaints, ran food and drinks, and maintained BOH
cleanliness.
</td>
</tr>
</tbody>
</table>
</div>
<div
class="no-print"
style="width: 100%; text-align: center; margin: 20px 0"
>
<h1>Page 2</h1>
</div>
<div class="a4page">
<h2>Personal Projects</h2>
<table>
<thead>
<tr>
<th>Project</th>
<th>Skills</th>
<th>Date</th>
<th>Description</th>
</tr>
</thead>
<tbody>
<tr>
<td>Mobile Automata</td>
<td>Mathematica, JS, Logic, Analysis</td>
<td>2024</td>
<td class="row-leftalign">
Designed experiments and analysis tools to identify
pattern similarities among automata. Investigated
computational properties by defining specific
phenomena and observing emergent behaviors through
custom simulations.
</td>
</tr>
<tr>
<td>Computer Graphics</td>
<td>Rust, Linear Algebra, Multi-threading</td>
<td>2023</td>
<td class="row-leftalign">
Developed a multi-threaded, recursive ray tracer as
part of a University of Waterloo project. Explored
advanced ray-surface intersection techniques,
including experimental rendering of
higher-dimensional geometries.
</td>
</tr>
<tr>
<td>Arduino Programming & Circuits</td>
<td>C++, Soldering, Embedded Systems</td>
<td>2022 - 2025</td>
<td class="row-leftalign">
Created room decorations using salvaged components
from discarded electronics.
</td>
</tr>
<tr>
<td>Memory Palace Website</td>
<td>TS, Rust, React, Redux, SQLite</td>
<td>2025</td>
<td class="row-leftalign">
Full-stack web application implementing the memory
palace memorization technique. Built with a
React/Redux frontend, Rust-based Actix backend, and
SQLite database.
</td>
</tr>
<tr>
<td>Personal Websites</td>
<td>HTML, JS, Design, UI/UX</td>
<td>Ongoing</td>
<td class="row-leftalign">
Continuously evolving my personal site and designing
other creative websites. Experimented with Svelte,
Vue, and React/Redux using libraries such as P5 and
Three.js.
</td>
</tr>
<tr>
<td>3D Printing</td>
<td>FreeCAD</td>
<td>Ongoing</td>
<td class="row-leftalign">
Designing and manufacturing household objects and
repairs, including replacement window handles, desk
organizers, and 3D scans.
</td>
</tr>
</tbody>
<tbody></tbody>
</table>
<h2>Commitments</h2>
<table>
<thead>
<tr>
<th>Activity</th>
<th>Date</th>
<th>Details</th>
</tr>
</thead>
<tbody>
<tr>
<td>Learning Mandarin</td>
<td>Ongoing</td>
<td class="row-leftalign">
Aiming to complete HSK 3 proficiency exam by
December 2026
</td>
</tr>
<!-- <tr> -->
<!-- <td>Cybersecurity Training</td> -->
<!-- <td>Ongoing</td> -->
<!-- <td class="row-leftalign"> -->
<!-- Using <em>pwn.college, tryhackme.com</em> to learn pentesting techniques.</td> -->
<!-- </tr> -->
<tr>
<td>Sports Activities</td>
<td>Ongoing</td>
<td class="row-leftalign">
Run weekly, active gym attendee, regularly go
hiking.
</td>
</tr>
<tr>
<td>Construction and Landscaping</td>
<td>Ongoing</td>
<td class="row-leftalign">
Involved in building a house in Bulgaria.
</td>
</tr>
<tr>
<td>University of Waterloo Film Club</td>
<td>2023-2024</td>
<td class="row-leftalign">
Worked on student films <em>Moon King</em> and
<em>HAM</em>, available online.
</td>
</tr>
<tr>
<td>Socratica</td>
<td>2023-2024</td>
<td class="row-leftalign">
Worked with like-minded individuals exploring
innovative tech.
</td>
</tr>
<tr>
<td>University of Leeds Hockey Club</td>
<td>2022-2023</td>
<td class="row-leftalign">
Played for the University of Leeds Hockey Club.
</td>
</tr>
<tr>
<td>Royal Air Force Air Cadets</td>
<td>2017-2020</td>
<td class="row-leftalign">
Achieved the role of Sergeant and Best Cadet"
award.
</td>
</tr>
</tbody>
</table>
<!-- <div class="interests"> -->
<!-- <table> -->
<!-- <tr><th>Personal qualities</th></tr> -->
<!-- <tr><td>Intuitive</td></tr> -->
<!-- <tr><td>Communicative</td></tr> -->
<!-- <tr><td>Adaptable</td></tr> -->
<!-- <tr><td>Versatile</td></tr> -->
<!-- <tr><td>Diligent</td></tr> -->
<!-- </table> -->
<!-- <table> -->
<!-- <tr><th>Interests</th></tr> -->
<!-- <tr><td>Neuroscience</td></tr> -->
<!-- <tr><td>Bouldering</td></tr> -->
<!-- <tr><td>Science Fiction</td></tr> -->
<!-- <tr><td>Mathematics</td></tr> -->
<!-- <tr><td>Hiking</td></tr> -->
<!-- </table> -->
<!-- <table> -->
<!-- <tr><th>Languages</th></tr> -->
<!-- <tr><td>Rust</td></tr> -->
<!-- <tr><td>HTML/JS</td></tr> -->
<!-- <tr><td>C/C++</td></tr> -->
<!-- <tr><td>React/Vue</td></tr> -->
<!-- <tr><td>Python</td></tr> -->
<!-- </table> -->
<!-- </div> -->
</div>
<div
class="no-print"
style="width: 100%; text-align: center; margin: 20px 0"
>
<h1>END</h1>
</div>
</div>
</template>
<style>
@import "/css/cv_styles.css";
@media print {
@page {
size: A4 portrait;
margin: 0;
}
}
</style>

View File

@@ -0,0 +1,47 @@
<script setup></script>
<template>
<main>
<h1>Welcome</h1>
<h2>whoami?</h2>
<p>Hi im Adam</p>
<h2>cv</h2>
<RouterLink to="/cv">CV</RouterLink>
<h2>bookmarks</h2>
<a href="/pages/bookmarks.html">bookmarks</a>
<h2>Listening to:</h2>
<div
x-data="spotifyPlayer()"
x-init="fetchNowPlaying(); setInterval(fetchNowPlaying, 60000)"
class="spotify-card"
>
<img :src="album_image" class="album-img" alt="" />
<div class="spotify-info">
<div x-text="song_name || 'No song playing'"></div>
<div x-text="artist_name"></div>
<div
x-text="playing ? 'Playing' : ''"
:class="{ playing: playing }"
></div>
<a :href="song_url"></a>
</div>
</div>
<!--<h2> </h2>
<p>
Sometimes there's this fire that sends shivers down my back.
It'll come when I'm lis
</p>
-->
<!--<h2>Shrines</h1>
<a href="/pages/shrines/evangelion.html">Evangelion</a>
<a href="/pages/shrines/skipskipbenben.html">Skip skip ben ben</a>
<a href="/pages/shrines/demoman.html">demoman</a>-->
<!--<a href="pages/shrines/gto.html">GTO</a>-->
</main>
</template>

18
nginx/vue/vite.config.js Normal file
View File

@@ -0,0 +1,18 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})