chore: add support for Vegvisir 3 (#1)

This PR adds support for the latest major release of the [Vegvisir framework](https://vegvisir.vlw.se)

Reviewed-on: https://codeberg.org/vlw/vlw.se/pulls/1
Co-authored-by: vlw <victor@vlw.se>
Co-committed-by: vlw <victor@vlw.se>
This commit is contained in:
Victor Westerlund 2024-09-27 09:32:52 +00:00 committed by Victor Westerlund
parent 43ddf1fdf6
commit ae1e992c5f
34 changed files with 276 additions and 256 deletions

3
.gitignore vendored
View file

@ -1,8 +1,9 @@
assets/media/content
assets/js/modules/npm
# Bootstrapping #
#################
vendor
node_modules
.env.ini
# OS generated files #

View file

@ -1,32 +1,38 @@
# vlw.se
This is the source code behind [vlw.se](https://vlw.se) which has been written from the ground up by me. This website is built on top of my [Vegvisir web framework](https://github.com/victorwesterlund/vegvisir) and my [Reflect API framework](https://github.com/victorwesterlund/reflect).
This is the source code behind [vlw.se](https://vlw.se) which has been written from the ground up by me. This website is built on top of my [Vegvisir web framework](https://vegvisir.vlw.se) and my [Reflect API framework](https://reflect.vlw.se).
# Installation
If you for whatever reason want to get this website up and running for yourself this is how that is done.
This website is built for PHP 8.0+ and MariaDB 14+ (for the API database).
## This website requires the following prerequisites
- [PHP 8.0+](https://www.php.net/)
- [MariaDB 14+](https://mariadb.org/)
- [The NPM package manager](https://www.npmjs.com/)
- [The Reflect API framework](https://reflect.vlw.se)
- [The Vegvisir web framework](https://vegvisir.vlw.se)
- [The composer package manager](https://getcomposer.org/)
**Confimed supported framework versions:**
Vegvisir|Reflect
--|--
✅ [`2.5.0`](https://github.com/VictorWesterlund/vegvisir/releases/tag/2.5.0)|✅ [`2.7.2`](https://github.com/VictorWesterlund/reflect/releases/tag/2.7.2)
✅ [`3.0.1`](https://codeberg.org/vegvisir/vegvisir/releases/tag/3.0.1)|✅ [`2.7.2`](https://codeberg.org/reflect/reflect/releases/tag/2.7.2)
## Website (Vegvisir)
1. **Download this repo**
Git clone or download this repo to any local folder
```
git clone https://github.com/VictorWesterlund/vlw.se
git clone https://codeberg.org/vlw/vlw.se
```
2. **Download and install Vegvisir**
Follow the installation instructions for [Vegvisir](https://github.com/victorwesterlund/vegvisir) and point the `site_path` variable to the local vlw.se folder.
Follow the installation instructions for [Vegvisir](https://vegvisir.vlw.se/docs/installation) and point the `root_path` variable to your local vlw.se folder.
3. **Install dependencies**
3. **Run the install script**
Install dependencies with composer.
This bash script will install dependencies and make npm modules public.
```
composer install --optimize-autoloader
./install.sh
```
Et voila! You probably want to install the API-side too but the website itself should now be accessible from your configured Vegvisir host.
@ -40,16 +46,16 @@ The API (and database) is where most content is stored and served from on this w
Otherwise... Git clone or download this repo to any local folder
```
git clone https://github.com/VictorWesterlund/vlw.se
git clone https://codeberg.org/vlw/vlw.se
```
2. **Download and install Reflect**
Follow the installation instructions for [Reflect](https://github.com/victorwesterlund/vegvisir) and point the `endpoints` variable to the `/api` subdirectory in the local vlw.se folder.
Follow the installation instructions for [Reflect](https://reflect.vlw.se/docs/installation) and point the `endpoints` variable to the `/api` subdirectory in the local vlw.se folder.
3. **Install dependencies**
Install dependencies with composer.
`cd` into the api folder and install dependencies with composer.
```
composer install --optimize-autoloader
```
@ -62,9 +68,6 @@ The API (and database) is where most content is stored and served from on this w
Make a copy of `/api/.env.example.ini` and change the `[vlwdb]` variables with your MariaDB credentials.
You also have to generate a [GitHub access token](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) if you wish to use the `releases` endpoint.
[Read more about this endpoint here](#)
6. **Set environment variables for website**
It's reasonable to assume if you've installed the website from this repo that you'd also want to use the API with it. Start my making a copy of `/.env.example.ini` (root directory) and change the `[api]` variables to point to your API hostname.

2
api/install.sh Normal file
View file

@ -0,0 +1,2 @@
# Install dependencies
composer install --optimize-autoloader

View file

@ -5,7 +5,7 @@
--color-accent: rgb(var(--primer-color-accent));
}
main {
vv-shell {
display: flex;
flex-direction: column;
gap: var(--padding);
@ -15,7 +15,7 @@ main {
/* ## Divider */
main > hr {
vv-shell > hr {
border-color: rgba(255, 255, 255, .1);
}

View file

@ -5,7 +5,7 @@
--color-accent: rgb(var(--primer-color-accent));
}
main {
vv-shell {
display: flex;
flex-direction: column;
gap: var(--padding);

View file

@ -5,7 +5,7 @@
--color-accent: rgb(var(--primer-color-accent));
}
main {
vv-shell {
display: flex;
flex-direction: column;
gap: var(--padding);

View file

@ -5,7 +5,7 @@
--color-accent: rgb(var(--primer-color-accent));
}
main {
vv-shell {
display: flex;
flex-direction: column;
align-items: center;
@ -14,7 +14,7 @@ main {
/* # Sections */
main > svg {
vv-shell > svg {
margin: var(--padding) 0;
}

View file

@ -6,7 +6,7 @@ header {
backdrop-filter: unset;
}
main {
vv-shell {
max-width: unset;
display: grid;
justify-items: center;

View file

@ -4,18 +4,18 @@ body[vv-top-page="/"]::before {
opacity: 0;
}
/* # Main styles */
/* # vv-shell styles */
/* ## Picture */
main {
vv-shell {
display: flex;
align-items: center;
justify-content: space-between;
flex-direction: column-reverse;
}
main img {
vv-shell img {
margin: auto;
width: 25vh;
pointer-events: none;
@ -171,14 +171,14 @@ splash::after {
/* # Size quries */
@media (min-width: 900px) {
main {
vv-shell {
display: grid;
grid-template-columns: repeat(2, 1fr);
justify-items: center;
align-items: center;
}
main img {
vv-shell img {
width: 35vh;
}
}

View file

@ -21,7 +21,7 @@ section.search {
margin-bottom: calc(var(--padding) * 2);
}
main[vv-page="/search"] > section.search {
vv-shell[vv-page="/search"] > section.search {
display: flex;
}

View file

@ -5,7 +5,7 @@
--color-accent: rgb(var(--primer-color-accent));
}
main {
vv-shell {
display: flex;
flex-direction: column;
gap: var(--padding);

View file

@ -260,24 +260,15 @@ header.searchboxActive searchbox {
transform: rotateX(0);
}
/* ## Main */
/* ## vv-shell */
main {
vv-shell {
position: relative;
padding: calc(var(--padding) * 1.5);
width: 100%;
max-width: 1000px;
}
main > * {
transition: 100ms opacity;
opacity: 1;
}
main.loading > * {
opacity: 0;
}
/* ## Search results */
search-results {

View file

@ -1,5 +1,3 @@
new vv.Interactions("about");
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min);
}

View file

@ -1 +0,0 @@
new vv.Interactions("battlestation-retired");

View file

@ -1,19 +1,19 @@
new vv.Interactions("battlestation", {
toggleGroup: (event) => {
// Collapse self if already active and current target
if (event.target.classList.contains("active")) {
return event.target.classList.remove("active");
}
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
// Collapse all and open current target
[...event.target.closest(".specs").querySelectorAll(".group")].forEach(element => element.classList.remove("active"));
event.target.classList.add("active");
},
setSpecActive: (event) => {
event.target.classList.add("active");
event.target.addEventListener("mouseleave", () => event.target.classList.remove("active"));
new Elevent("click", document.querySelectorAll(".group"), (event) => {
// Collapse self if already active and current target
if (event.target.classList.contains("active")) {
return event.target.classList.remove("active");
}
// Collapse all and open current target
[...event.target.closest(".specs").querySelectorAll(".group")].forEach(element => element.classList.remove("active"));
event.target.classList.add("active");
});
new Elevent("click", document.querySelectorAll(".spec"), (event) => {
event.target.classList.add("active");
event.target.addEventListener("mouseleave", () => event.target.classList.remove("active"));
});
// Bind hover listeners for components in the SVGs

View file

@ -10,8 +10,6 @@ class ContactForm {
[...document.querySelectorAll("form :is(input, textarea)")].forEach(element => {
element.addEventListener("keyup", () => this.saveMessage());
});
}
// Get saved message as JSON from SessionStorage
@ -36,6 +34,7 @@ class ContactForm {
return ContactForm.removeSavedMessage();
}
// Set value of each input field in DOM by name attribute
for (const [name, value] of Object.entries(message)) {
this.form.querySelector(`[name="${name}"]`).value = value;
}

View file

@ -1,108 +1,108 @@
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
// Run email copied splash animation
const emailCopiedAnimation = () => {
const CONFETTI_COUNT = 40;
const CONFETTI_SCALE_PIXELS = 300;
// Click to copy email button
{
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
// Run email copied splash animation
const emailCopiedAnimation = () => {
const CONFETTI_COUNT = 40;
const CONFETTI_SCALE_PIXELS = 300;
const randomIntFromInterval = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
}
// Create new splash element
const splashElement = document.createElement("splash");
splashElement.innerText = "copied!";
// Set inline display to none to hide this element on pages where the splash element has no override styles defined.
splashElement.style.display = "none";
// Array of box-shadow strings as "confetti"
const confetti = [];
// Generate random confetti
for (let i = 0; i < CONFETTI_COUNT; i++) {
// Random confetti position
const x = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
const y = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
// Random confetti RGB color
const rgb = [
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255)
];
// Interpolate random values and append to outer confetti array
confetti.push(`${x}px ${y}px 0 rgb(${rgb.join(",")})`);
}
// Set CSS variable on splash element that in turn will be used by pseudo-element
splashElement.style.setProperty("--confetti", confetti.join(","));
// Start animation by appending the created element to the document body
document.body.appendChild(splashElement);
// Run hide animation
setTimeout(() => {
splashElement.classList.add("hide");
// Selfdestruct element when hide animation finishes
setTimeout(() => splashElement.remove(), 400);
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
}
// Create new splash element
const splashElement = document.createElement("splash");
splashElement.innerText = "copied!";
// Set inline display to none to hide this element on pages where the splash element has no override styles defined.
splashElement.style.display = "none";
// Array of box-shadow strings as "confetti"
const confetti = [];
// Generate random confetti
for (let i = 0; i < CONFETTI_COUNT; i++) {
// Random confetti position
const x = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
const y = randomIntFromInterval(CONFETTI_SCALE_PIXELS * -1, CONFETTI_SCALE_PIXELS);
// Random confetti RGB color
const rgb = [
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255),
randomIntFromInterval(0, 255)
];
// Interpolate random values and append to outer confetti array
confetti.push(`${x}px ${y}px 0 rgb(${rgb.join(",")})`);
}
// Set CSS variable on splash element that in turn will be used by pseudo-element
splashElement.style.setProperty("--confetti", confetti.join(","));
// Start animation by appending the created element to the document body
document.body.appendChild(splashElement);
// Run hide animation
setTimeout(() => {
splashElement.classList.add("hide");
// Selfdestruct element when hide animation finishes
setTimeout(() => splashElement.remove(), 400);
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
}
new vv.Interactions("index", {
// Copy email address to clipboard
copyEmail: async () => {
new Elevent("click", document.querySelector(".email"), async () => {
try {
await navigator.clipboard.writeText("victor@vlw.se");
// Run "email copied" animation!
emailCopiedAnimation();
// NOTE: I don't know, spamming the button is kinda fun
// Prevent interactions with the copy email elements while the animation is running
/*[...document.querySelectorAll("[vv-call='copyEmail']")].forEach(element => {
//element.classList.add("lock");
setTimeout(() => element.classList.remove("lock"), EMAIL_CPY_ANIM_DUR_MSECONDS);
});*/
} catch (error) {
console.error(error.message);
}
},
// Open the fullscreen menu
openMenu: () => document.querySelector("menu").classList.add("active"),
// Close the fullscreen menu
closeMenu: () => document.querySelector("menu").classList.remove("active")
});
});
}
// Change site accent color on hover of menu items
if (window.matchMedia("(hover: hover)")) {
// Update root CSS variables
const updateColor = (rgb = null, hue = 0) => {
if (!rgb) {
document.documentElement.style.removeProperty("--hue-accent");
document.documentElement.style.removeProperty("--primer-color-accent");
document.documentElement.style.removeProperty("--color-accent");
{
if (window.matchMedia("(hover: hover)")) {
// Update root CSS variables
const updateColor = (rgb = null, hue = 0) => {
if (!rgb) {
document.documentElement.style.removeProperty("--hue-accent");
document.documentElement.style.removeProperty("--primer-color-accent");
document.documentElement.style.removeProperty("--color-accent");
return;
}
return;
}
document.documentElement.style.setProperty("--hue-accent", `${hue}deg`);
document.documentElement.style.setProperty("--hue-accent", `${hue}deg`);
document.documentElement.style.setProperty("--primer-color-accent", `${rgb}`);
// Compiled color variable must to be updated to receive the new RGB values
document.documentElement.style.setProperty("--color-accent", "rgb(var(--primer-color-accent)");
};
document.documentElement.style.setProperty("--primer-color-accent", `${rgb}`);
// Compiled color variable must to be updated to receive the new RGB values
document.documentElement.style.setProperty("--color-accent", "rgb(var(--primer-color-accent)");
};
[...document.querySelectorAll("menu li")].forEach(element => {
// Change site accent color to RGB and HUE rotation defined in element dataset
element.addEventListener("mouseenter", (event) => updateColor(event.target.dataset.rgb, event.target.dataset.hue));
// Reset initial accent color and hues
element.addEventListener("mouseleave", () => updateColor());
});
[...document.querySelectorAll("menu li")].forEach(element => {
// Change site accent color to RGB and HUE rotation defined in element dataset
element.addEventListener("mouseenter", (event) => updateColor(event.target.dataset.rgb, event.target.dataset.hue));
// Reset initial accent color and hues
element.addEventListener("mouseleave", () => updateColor());
});
// Reset color on navigation
document.querySelector(vv._env.MAIN).addEventListener(vv.Navigation.events.LOADED, () => updateColor(), { once: true });
}
// Reset color on navigation
vv.Navigation.rootShellElement.addEventListener(vv.Navigation.EVENTS.STARTED, () => updateColor(), { once: true });
}
}

View file

@ -1 +0,0 @@
new vv.Interactions("search");

View file

@ -1 +0,0 @@
new vv.Interactions("work");

47
assets/js/document.js → assets/js/shells/document.js Executable file → Normal file
View file

@ -1,38 +1,37 @@
new vv.Interactions("document", {
navigateHome: () => new vv.Navigation("/").navigate(),
closeSearchbox: () => {
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
const CLASSNAME_SEARCHBOX_ACTIVE = "searchboxActive";
// Handle search box open/close buttons
{
// Open search box
new Elevent("click", document.querySelector(".searchbox-open"), () => {
document.querySelector("header").classList.add(CLASSNAME_SEARCHBOX_ACTIVE);
// Select searchbox inner input element
document.querySelector("searchbox input").focus();
});
// Close searchbox
new Elevent("click", document.querySelector(".searchbox-close"), () => {
// Disable search button interaction while animation is running
// This is required to prevent conflicts with the :hover "peak" transformation
const searchButtonElement = document.querySelector("header button.search");
const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
searchButtonElement.style.setProperty("pointer-events", "none");
document.querySelector("header").classList.remove("searchboxActive");
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
// Wait for the transform animation to finish
setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration);
},
openSearchbox: () => {
document.querySelector("header").classList.add("searchboxActive");
// Select searchbox inner input element
document.querySelector("searchbox input").focus();
}
});
// Crossfade pages on navigation
{
const mainElement = document.querySelector(vv._env.MAIN);
mainElement.addEventListener(vv.Navigation.events.LOADING, () => {
mainElement.classList.add("loading");
});
}
mainElement.addEventListener(vv.Navigation.events.LOADED, () => {
// Close searchbox on main page navigation
document.querySelector("header").classList.remove("searchboxActive");
// Wait 200ms for the page fade-in animation to finish
setTimeout(() => mainElement.classList.remove("loading"), 200);
// Root shell navigation event handlers
{
// On all top shell navigations
new Elevent(vv.Navigation.EVENTS.STARTED, vv.Navigation.rootShellElement, () => {
// Close searchbox on top shell navigations
document.querySelector("header").classList.remove(CLASSNAME_SEARCHBOX_ACTIVE);
});
}

10
install.sh Normal file
View file

@ -0,0 +1,10 @@
# Install dependencies
composer install --optimize-autoloader
npm install
# (Re)create public NPM modules folder
rm -r assets/js/modules/npm
mkdir assets/js/modules/npm
# Create link to Elevent MJS from public JS modules folder
ln -sr node_modules/elevent/src/Elevent.mjs assets/js/modules/npm/Elevent.mjs

17
package-lock.json generated Normal file
View file

@ -0,0 +1,17 @@
{
"name": "vlw.se",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"elevent": "^1.0.2"
}
},
"node_modules/elevent": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/elevent/-/elevent-1.0.2.tgz",
"integrity": "sha512-ks5LBUBTg4Bpfmj99OcFAzuDGzBRDEZhTyxmq/Y3RbsdBQ4JCaIUYB0M15OBvBWgIn1BnCo4WCSmw0/YbCJliw=="
}
}
}

5
package.json Normal file
View file

@ -0,0 +1,5 @@
{
"dependencies": {
"elevent": "^1.0.2"
}
}

View file

@ -1,6 +0,0 @@
<style><?= VV::css("pages/error") ?></style>
<canvas></canvas>
<section class="error">
<h1 glitch-text><span>4</span><span>0</span><span>4</span></h1>
</section>
<script type="module"><?= VV::js("pages/error") ?></script>

6
pages/about.php → public/about.php Executable file → Normal file
View file

@ -1,4 +1,4 @@
<style><?= VV::css("pages/about") ?></style>
<style><?= VV::css("assets/css/pages/about") ?></style>
<section class="intro">
<h2 aria-hidden="true">Hi, I'm</h2>
<h1>Victor Westerlund</h1>
@ -31,7 +31,7 @@
</section>
<hr>
<section class="version">
<p>website version: <?= VV::include("pages/about/version") ?></p>
<p>website version: <?= VV::include("public/about/version") ?></p>
</section>
<div class="interests" aria-hidden="true">
@ -49,4 +49,4 @@
<p>photography</p>
<p>videography</p>
</div>
<script><?= VV::js("pages/about") ?></script>
<script type="module"><?= VV::js("assets/js/pages/about") ?></script>

View file

@ -1,4 +1,4 @@
<style><?= VV::css("pages/about/battlestation-retired") ?></style>
<style><?= VV::css("assets/css/pages/about/battlestation-retired") ?></style>
<section class="title">
<h1>Retired components</h1>
<p>I'd be happy to send you any component that you find here for "free". The only thing I ask in return is that you pay for shipping.</p>
@ -33,4 +33,4 @@
<button class="inline solid">Contact me</button>
</a>
</section>
<script><?= VV::js("pages/about/battlestation-retired") ?></script>
<script><?= VV::js("assets/js/pages/about/battlestation-retired") ?></script>

View file

@ -25,26 +25,26 @@
MbStorageSlotFormfactorEnum
};
require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
require_once VV::root("src/client/API.php");
require_once VV::root("api/src/Endpoints.php");
// Load hardware database models
require_once Path::root("api/src/databases/models/Battlestation/Mb.php");
require_once Path::root("api/src/databases/models/Battlestation/Cpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Gpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Psu.php");
require_once Path::root("api/src/databases/models/Battlestation/Dram.php");
require_once Path::root("api/src/databases/models/Battlestation/Storage.php");
require_once Path::root("api/src/databases/models/Battlestation/Chassis.php");
require_once VV::root("api/src/databases/models/Battlestation/Mb.php");
require_once VV::root("api/src/databases/models/Battlestation/Cpu.php");
require_once VV::root("api/src/databases/models/Battlestation/Gpu.php");
require_once VV::root("api/src/databases/models/Battlestation/Psu.php");
require_once VV::root("api/src/databases/models/Battlestation/Dram.php");
require_once VV::root("api/src/databases/models/Battlestation/Storage.php");
require_once VV::root("api/src/databases/models/Battlestation/Chassis.php");
// Load hardware config database models
require_once Path::root("api/src/databases/models/Battlestation/Config/MbPsu.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbGpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbDram.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/Config.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbStorage.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/ChassisMb.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbCpuCooler.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/MbPsu.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/MbGpu.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/MbDram.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/Config.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/MbStorage.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/ChassisMb.php");
require_once VV::root("api/src/databases/models/Battlestation/Config/MbCpuCooler.php");
const GIGA = 0x3B9ACA00;
const MEGA = 0xF4240;
@ -55,7 +55,7 @@
$config = $api->call(Endpoints::BATTLESTATION->value)->get();
?>
<style><?= VV::css("pages/about/battlestation") ?></style>
<style><?= VV::css("assets/css/pages/about/battlestation") ?></style>
<?php if ($config->ok): ?>
<section class="title">
<h1>Battle&shy;stations</h1>
@ -93,12 +93,12 @@
data-drives-twodotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageModel::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::TWODOTFIVE->value)) ?>"
data-drives-threedotfive="<?= count(array_keys(array_column($motherboard["storage"], MbStorageModel::SLOT_FORMFACTOR->value), MbStorageSlotFormfactorEnum::THREEDOTFIVE->value)) ?>"
>
<?= VV::media("battlestation.svg") ?>
<?= VV::embed("assets/media/battlestation.svg") ?>
<div class="specs">
<?php // Show motherboard details ?>
<?php if ($motherboard): ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="mb" class="spec">
<div data-target="mb" class="spec">
<p>Motherboard</p>
<h3><?= $motherboard[MbModel::VENDOR_NAME->value] ?> <span><?= $motherboard[MbModel::VENDOR_MODEL->value] ?></span></h3>
<div>
@ -149,7 +149,7 @@
ChassisModel::ID->value => $mb_chassis[ChassisMbModel::REF_CHASSIS_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="case" class="spec">
<div data-target="case" class="spec">
<p>Case</p>
<h3><?= $case[ChassisModel::VENDOR_NAME->value] ?> <span><?= $case[ChassisModel::VENDOR_MODEL->value] ?></span></h3>
<div>
@ -192,7 +192,7 @@
CpuModel::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="cpu" class="spec">
<div data-target="cpu" class="spec">
<p>CPU</p>
<h3><?= $cpu[CpuModel::VENDOR_NAME->value] ?> <span><?= $cpu[CpuModel::VENDOR_MODEL->value] ?></span></h3>
<div>
@ -257,7 +257,7 @@
GpuModel::ID->value => $mb_gpu[MbGpuModel::REF_GPU_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="gpu" class="spec">
<div data-target="gpu" class="spec">
<p>GPU</p>
<h3><?= $gpu[GpuModel::VENDOR_NAME->value] ?> <span><?= $gpu[GpuModel::VENDOR_CHIP_MODEL->value] ?></span></h3>
<div>
@ -304,7 +304,7 @@
PsuModel::ID->value => $mb_psu[MbPsuModel::REF_PSU_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="psu" class="spec">
<div data-target="psu" class="spec">
<p>PSU</p>
<h3><?= $psu[PsuModel::VENDOR_NAME->value] ?> <span><?= $psu[PsuModel::VENDOR_MODEL->value] ?></span> <span><?= $psu[PsuModel::POWER->value] ?>W</span></h3>
<div>
@ -343,9 +343,9 @@
</div>
<?php endforeach; ?>
<div vv="battlestation" vv-call="toggleGroup" class="group">
<div class="group">
<p>DRAM</p>
<?= VV::media("icons/chevron.svg") ?>
<?= VV::embed("assets/media/icons/chevron.svg") ?>
</div>
<div class="collection">
@ -357,7 +357,7 @@
DramModel::ID->value => $mb_dram[MbDramModel::REF_DRAM_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="dram" class="spec">
<div data-target="dram" class="spec">
<p>DRAM - <?= $dram[DramModel::TECHNOLOGY->value] ?></p>
<h3><?= $dram[DramModel::VENDOR_NAME->value] ?>
<span><?= $dram[DramModel::CAPACITY->value] / GIGA ?>GB</span>
@ -422,9 +422,9 @@
<?php endforeach; ?>
</div>
<div vv="battlestation" vv-call="toggleGroup" class="group">
<div class="group">
<p>Storage</p>
<?= VV::media("icons/chevron.svg") ?>
<?= VV::embed("assets/media/icons/chevron.svg") ?>
</div>
<div class="collection">
@ -436,7 +436,7 @@
StorageModel::ID->value => $mb_storage[MbStorageModel::REF_STORAGE_ID->value]
])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="drive" class="spec">
<div data-target="drive" class="spec">
<p><?= $storage[StorageModel::DISK_FORMFACTOR->value] ?> <?= $storage[StorageModel::DISK_TYPE->value] ?></p>
<h3>
<?= $storage[StorageModel::VENDOR_NAME->value] ?>
@ -493,4 +493,4 @@
</section>
<?php endforeach; ?>
<?php endif; ?>
<script><?= VV::js("pages/about/battlestation") ?></script>
<script type="module"><?= VV::js("assets/js/pages/about/battlestation") ?></script>

4
pages/about/version.php → public/about/version.php Executable file → Normal file
View file

@ -6,11 +6,9 @@
of this website should always track the master branch and pull the latest HEAD
without any exceptions.
*/
use Vegvisir\Path;
// Get tags from local git folder
$dir = scandir(Path::root(".git/refs/tags"));
$dir = scandir(VV::root(".git/refs/tags"));
// Get current version number from latest tag
$version = end($dir);

22
pages/contact.php → public/contact.php Executable file → Normal file
View file

@ -7,37 +7,37 @@
use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel;
require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
require_once VV::root("src/client/API.php");
require_once VV::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Messages/Messages.php");
require_once VV::root("api/src/databases/models/Messages/Messages.php");
// Connect to VLW API
$api = new API();
?>
<style><?= VV::css("pages/contact") ?></style>
<style><?= VV::css("assets/css/pages/contact") ?></style>
<section>
<h1>Let's chat</h1>
<p>The best way to get in touch is by email, or with the form on this page. I will try to reply as quickly as possible, probably within a few hours. The time is <i><?= (new DateTime("now", new DateTimeZone($_ENV["time"]["date_time_zone"])))->format("h:i a") ?></i> in Sweden right now.</p>
</section>
<section class="social">
<a href="mailto:victor@vlw.se"><social>
<?= VV::media("icons/email.svg") ?>
<?= VV::embed("assets/media/icons/email.svg") ?>
<p>e-mail</p>
</social></a>
<a href="https://mastodon.social/@vlwone"><social>
<?= VV::media("icons/mastodon.svg") ?>
<?= VV::embed("assets/media/icons/mastodon.svg") ?>
<p>mastodon</p>
</social></a>
<a href="https://web.libera.chat/#vlw.se"><social>
<?= VV::media("icons/libera.svg") ?>
<?= VV::embed("assets/media/icons/libera.svg") ?>
<p>libera.chat</p>
</social></a>
</section>
<?= VV::media("line.svg") ?>
<?= VV::embed("assets/media/line.svg") ?>
<section class="pgp">
<?= VV::media("icons/pin.svg") ?>
<?= VV::embed("assets/media/icons/pin.svg") ?>
<h3>encrypt your message with my OpenPGP key.</h3>
<p>my key is also listed on the <a href="https://keys.openpgp.org/search?q=victor%40vlw.se" target="_blank" rel="noopener noreferer">openPGP key server</a> for victor@vlw.se so your e-mail client can automatically retreive it if supported.</p>
<div class="buttons">
@ -45,7 +45,7 @@
<a href="https://emailselfdefense.fsf.org/en/" target="_blank" rel="noopener noreferer"><button class="inline">more info</button></a>
</div>
</section>
<?= VV::media("line.svg") ?>
<?= VV::embed("assets/media/line.svg") ?>
<?php // Send message on POST request ?>
<?php if ($_SERVER["REQUEST_METHOD"] === "POST"): ?>
@ -88,4 +88,4 @@
</form>
</section>
<script><?= VV::js("pages/contact") ?></script>
<script><?= VV::js("assets/js/pages/contact") ?></script>

6
public/error.php Normal file
View file

@ -0,0 +1,6 @@
<style><?= VV::css("assets/css/pages/error") ?></style>
<canvas></canvas>
<section class="error">
<h1 glitch-text><span>4</span><span>0</span><span>4</span></h1>
</section>
<script type="module"><?= VV::js("assets/js/pages/error") ?></script>

10
pages/index.php → public/index.php Executable file → Normal file
View file

@ -7,20 +7,20 @@
}
?>
<style><?= VV::css("pages/index") ?></style>
<style><?= VV::css("assets/css/pages/index") ?></style>
<div class="menu">
<?= VV::media("line.svg") ?>
<?= VV::embed("assets/media/line.svg") ?>
<menu>
<a href="/work" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::WORK->value ?>" data-hue="90">work</li></a>
<a href="/about" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::ABOUT->value ?>" data-hue="390">about</li></a>
<a href="/contact" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::CONTACT->value ?>" data-hue="200">contact</li></a>
</menu>
<?= VV::media("line.svg") ?>
<button class="email" vv="index" vv-call="copyEmail">
<?= VV::embed("assets/media/line.svg") ?>
<button class="email">
<p>victor@vlw.se</p>
<p class="cta">to copy</p>
</button>
</div>
<img src="/assets/media/gazing.jpg" alt="A portrait of Victor with a pair of cartoon glasses drawn in the shape of two V's over his eyes"/>
<script><?= VV::js("pages/index") ?></script>
<script type="module"><?= VV::js("assets/js/pages/index") ?></script>

16
pages/search.php → public/search.php Executable file → Normal file
View file

@ -10,11 +10,11 @@
WorkActionsModel
};
require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
require_once VV::root("src/client/API.php");
require_once VV::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php");
require_once Path::root("api/src/databases/models/Work/WorkActions.php");
require_once VV::root("api/src/databases/models/Work/Work.php");
require_once VV::root("api/src/databases/models/Work/WorkActions.php");
// Search endpoint query paramter
const SEARCH_PARAM = "q";
@ -26,7 +26,7 @@
$response = $api->call(Endpoints::SEARCH->value)->params([SEARCH_PARAM => $_GET[SEARCH_PARAM]])->get();
?>
<style><?= VV::css("pages/search") ?></style>
<style><?= VV::css("assets/css/pages/search") ?></style>
<section class="search">
<form method="GET">
<search>
@ -34,7 +34,7 @@
</search>
<button type="submit" class="inline solid">Search</button>
</form>
<?= VV::media("line.svg") ?>
<?= VV::embed("assets/media/line.svg") ?>
<button class="inline">advanced search options</button>
</section>
@ -89,11 +89,11 @@
</section>
<?php else: ?>
<section class="info empty">
<?= VV::media("icons/search.svg") ?>
<?= VV::embed("assets/media/icons/search.svg") ?>
<p>Start typing to search</p>
</section>
<?php endif; ?>
<?php endif; ?>
<script><?= VV::js("pages/search") ?></script>
<script><?= VV::js("assets/js/pages/search") ?></script>

16
pages/work.php → public/work.php Executable file → Normal file
View file

@ -11,12 +11,12 @@
WorkActionsModel
};
require_once Path::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php");
require_once VV::root("src/client/API.php");
require_once VV::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php");
require_once Path::root("api/src/databases/models/Work/WorkTags.php");
require_once Path::root("api/src/databases/models/Work/WorkActions.php");
require_once VV::root("api/src/databases/models/Work/Work.php");
require_once VV::root("api/src/databases/models/Work/WorkTags.php");
require_once VV::root("api/src/databases/models/Work/WorkActions.php");
// Connect to VLW API
$api = new API();
@ -31,10 +31,10 @@
}
?>
<style><?= VV::css("pages/work") ?></style>
<style><?= VV::css("assets/css/pages/work") ?></style>
<section class="git">
<?= VV::media("icons/github.svg") ?>
<?= VV::embed("assets/media/icons/github.svg") ?>
<p>Most of my free open-source software is available on GitHub and it's also mirrored on my server</p>
<div class="buttons">
<a href="https://github.com/victorwesterlund"><button class="inline solid">open GitHub</button></a>
@ -178,4 +178,4 @@
<p>Something went wrong!</p>
<?php endif; ?>
<script><?= VV::js("pages/work") ?></script>
<script><?= VV::js("assets/js/pages/work") ?></script>

20
pages/document.php → shells/document.php Executable file → Normal file
View file

@ -38,8 +38,8 @@
</script>
<?php // Bootstrapping ?>
<style><?= VV::css("fonts") ?></style>
<style><?= VV::css("document") ?></style>
<style><?= VV::css("assets/css/fonts") ?></style>
<style><?= VV::css("assets/css/shells/document") ?></style>
<title>Victor L. Westerlund</title>
<link rel="icon" href="/assets/media/vw.svg"/>
@ -49,28 +49,28 @@
<nav>
<p><a href="/" vv="document" vv-call="navigate">victor westerlund</a></p>
</nav>
<button class="search" vv="document" vv-call="openSearchbox">
<?= VV::media("icons/search.svg") ?>
<button class="search searchbox-open">
<?= VV::embed("assets/media/icons/search.svg") ?>
<p>search vlw.se...</p>
</button>
<button class="logo" vv="document" vv-call="navigateHome"><?= VV::media("vw.svg") ?></button>
<button class="logo" vv="/"><?= VV::embed("assets/media/vw.svg") ?></button>
<searchbox>
<input type="search" autocomplete="off" placeholder="search vlw.se...">
<button class="close" vv="document" vv-call="closeSearchbox"><?= VV::media("icons/close.svg") ?></button>
<button class="close searchbox-close"><?= VV::embed("assets/media/icons/close.svg") ?></button>
</searchbox>
</header>
<main></main>
<vv-shell></vv-shell>
<search-results>
<div class="info empty">
<?= VV::media("icons/search.svg") ?>
<?= VV::embed("assets/media/icons/search.svg") ?>
<p>start typing to search</p>
</div>
</search-results>
<?php // Bootstrapping ?>
<script><?= VV::init() ?></script>
<script><?= VV::js("document") ?></script>
<?= VV::init() ?>
<script type="module"><?= VV::js("assets/js/shells/document") ?></script>
</body>
</html>