changing frameworks
38
nginx/vue/README.md
Normal 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
@@ -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
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
2557
nginx/vue/package-lock.json
generated
Normal file
23
nginx/vue/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
272
nginx/vue/public/css/cv_styles.css
Normal 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;
|
||||
}
|
||||
56
nginx/vue/public/css/styles.css
Normal 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;
|
||||
}
|
||||
BIN
nginx/vue/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
nginx/vue/public/fonts/AldotheApache.ttf
Normal file
BIN
nginx/vue/public/fonts/CreatoDisplay-Bold.otf
Normal file
BIN
nginx/vue/public/fonts/CreatoDisplay-Regular.otf
Normal file
BIN
nginx/vue/public/fonts/Robot_Font.otf
Normal file
BIN
nginx/vue/public/fonts/big_noodle_titling.ttf
Normal file
BIN
nginx/vue/public/fonts/m12.ttf
Normal file
BIN
nginx/vue/public/img/Untitled.png
Normal file
|
After Width: | Height: | Size: 194 KiB |
BIN
nginx/vue/public/img/background.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
nginx/vue/public/img/evangelion/1751722658545025.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1752167296446761.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1752573650791232.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1754090738900322.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1754315171900320.mp4
Normal file
BIN
nginx/vue/public/img/evangelion/1754416300987968.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1755791134999098.mp4
Normal file
BIN
nginx/vue/public/img/evangelion/1755791932803463.mp4
Normal file
BIN
nginx/vue/public/img/evangelion/1756950404768114.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1759271450919264.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1760157883887673.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1760765839977417.webm
Normal file
BIN
nginx/vue/public/img/evangelion/1760766316027911.mp4
Normal file
BIN
nginx/vue/public/img/evangelion/1760865119827753.mp4
Normal file
BIN
nginx/vue/public/img/evangelion/1761433469781419.webm
Normal file
BIN
nginx/vue/public/img/favicon.ico
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
nginx/vue/public/img/img.png
Normal file
|
After Width: | Height: | Size: 61 KiB |
BIN
nginx/vue/public/img/memes/1755430627827545.webm
Normal file
BIN
nginx/vue/public/img/memes/1761047246286262.webm
Normal file
BIN
nginx/vue/public/img/memes/1761201115308614.webm
Normal file
BIN
nginx/vue/public/img/memes/1761540684738196.webm
Normal file
BIN
nginx/vue/public/img/memes/1761830320173621.webm
Normal file
BIN
nginx/vue/public/img/memes/epic.jpeg
Normal file
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 1.3 MiB |
BIN
nginx/vue/public/img/memes/lol.jpg
Normal file
|
After Width: | Height: | Size: 246 KiB |
BIN
nginx/vue/public/img/rune.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
nginx/vue/public/img/tf2/1760582395316219.webm
Normal file
BIN
nginx/vue/public/img/tf2/1761052136609718.webm
Normal file
BIN
nginx/vue/public/img/tf2/1761088452011210.mp4
Normal file
BIN
nginx/vue/public/img/tf2/1761570214170465.webm
Normal file
BIN
nginx/vue/public/img/tf2/1761828457509465.webm
Normal file
BIN
nginx/vue/public/img/tmpen31z3pe.PNG
Normal file
|
After Width: | Height: | Size: 13 KiB |
113
nginx/vue/public/index.html
Normal 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
@@ -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>
|
||||
347
nginx/vue/src/js/mobile-automata.mjs
Normal 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
@@ -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");
|
||||
23
nginx/vue/src/router/index.js
Normal 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
@@ -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%           (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>
|
||||
47
nginx/vue/src/views/Home.vue
Normal 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
@@ -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))
|
||||
},
|
||||
},
|
||||
})
|
||||