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 # # Bootstrapping #
################# #################
vendor vendor
node_modules
.env.ini .env.ini
# OS generated files # # OS generated files #

View file

@ -1,32 +1,38 @@
# vlw.se # 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 # Installation
If you for whatever reason want to get this website up and running for yourself this is how that is done. 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:** **Confimed supported framework versions:**
Vegvisir|Reflect 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) ## Website (Vegvisir)
1. **Download this repo** 1. **Download this repo**
Git clone or download this repo to any local folder 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** 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. 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 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** 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** 3. **Install dependencies**
Install dependencies with composer. `cd` into the api folder and install dependencies with composer.
``` ```
composer install --optimize-autoloader 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. 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** 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. 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)); --color-accent: rgb(var(--primer-color-accent));
} }
main { vv-shell {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: var(--padding); gap: var(--padding);
@ -15,7 +15,7 @@ main {
/* ## Divider */ /* ## Divider */
main > hr { vv-shell > hr {
border-color: rgba(255, 255, 255, .1); border-color: rgba(255, 255, 255, .1);
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,7 +1,11 @@
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000; import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
// Run email copied splash animation // Click to copy email button
const emailCopiedAnimation = () => { {
const EMAIL_CPY_ANIM_DUR_MSECONDS = 1000;
// Run email copied splash animation
const emailCopiedAnimation = () => {
const CONFETTI_COUNT = 40; const CONFETTI_COUNT = 40;
const CONFETTI_SCALE_PIXELS = 300; const CONFETTI_SCALE_PIXELS = 300;
@ -49,11 +53,9 @@ const emailCopiedAnimation = () => {
// Selfdestruct element when hide animation finishes // Selfdestruct element when hide animation finishes
setTimeout(() => splashElement.remove(), 400); setTimeout(() => splashElement.remove(), 400);
}, EMAIL_CPY_ANIM_DUR_MSECONDS + 100); }, EMAIL_CPY_ANIM_DUR_MSECONDS + 100);
} }
new vv.Interactions("index", { new Elevent("click", document.querySelector(".email"), async () => {
// Copy email address to clipboard
copyEmail: async () => {
try { try {
await navigator.clipboard.writeText("victor@vlw.se"); await navigator.clipboard.writeText("victor@vlw.se");
@ -70,15 +72,12 @@ new vv.Interactions("index", {
} catch (error) { } catch (error) {
console.error(error.message); 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 // Change site accent color on hover of menu items
if (window.matchMedia("(hover: hover)")) { {
if (window.matchMedia("(hover: hover)")) {
// Update root CSS variables // Update root CSS variables
const updateColor = (rgb = null, hue = 0) => { const updateColor = (rgb = null, hue = 0) => {
if (!rgb) { if (!rgb) {
@ -104,5 +103,6 @@ if (window.matchMedia("(hover: hover)")) {
}); });
// Reset color on navigation // Reset color on navigation
document.querySelector(vv._env.MAIN).addEventListener(vv.Navigation.events.LOADED, () => updateColor(), { once: true }); 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", { import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
navigateHome: () => new vv.Navigation("/").navigate(),
closeSearchbox: () => { 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 // Disable search button interaction while animation is running
// This is required to prevent conflicts with the :hover "peak" transformation // This is required to prevent conflicts with the :hover "peak" transformation
const searchButtonElement = document.querySelector("header button.search"); const searchButtonElement = document.querySelector("header button.search");
const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration")); const transformDuration = parseInt(window.getComputedStyle(searchButtonElement).getPropertyValue("--transform-duration"));
searchButtonElement.style.setProperty("pointer-events", "none"); 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 // Wait for the transform animation to finish
setTimeout(() => searchButtonElement.style.removeProperty("pointer-events"), transformDuration); 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, () => { // Root shell navigation event handlers
// Close searchbox on main page navigation {
document.querySelector("header").classList.remove("searchboxActive"); // On all top shell navigations
new Elevent(vv.Navigation.EVENTS.STARTED, vv.Navigation.rootShellElement, () => {
// Wait 200ms for the page fade-in animation to finish // Close searchbox on top shell navigations
setTimeout(() => mainElement.classList.remove("loading"), 200); 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"> <section class="intro">
<h2 aria-hidden="true">Hi, I'm</h2> <h2 aria-hidden="true">Hi, I'm</h2>
<h1>Victor Westerlund</h1> <h1>Victor Westerlund</h1>
@ -31,7 +31,7 @@
</section> </section>
<hr> <hr>
<section class="version"> <section class="version">
<p>website version: <?= VV::include("pages/about/version") ?></p> <p>website version: <?= VV::include("public/about/version") ?></p>
</section> </section>
<div class="interests" aria-hidden="true"> <div class="interests" aria-hidden="true">
@ -49,4 +49,4 @@
<p>photography</p> <p>photography</p>
<p>videography</p> <p>videography</p>
</div> </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"> <section class="title">
<h1>Retired components</h1> <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> <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> <button class="inline solid">Contact me</button>
</a> </a>
</section> </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 MbStorageSlotFormfactorEnum
}; };
require_once Path::root("src/client/API.php"); require_once VV::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php"); require_once VV::root("api/src/Endpoints.php");
// Load hardware database models // Load hardware database models
require_once Path::root("api/src/databases/models/Battlestation/Mb.php"); require_once VV::root("api/src/databases/models/Battlestation/Mb.php");
require_once Path::root("api/src/databases/models/Battlestation/Cpu.php"); require_once VV::root("api/src/databases/models/Battlestation/Cpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Gpu.php"); require_once VV::root("api/src/databases/models/Battlestation/Gpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Psu.php"); require_once VV::root("api/src/databases/models/Battlestation/Psu.php");
require_once Path::root("api/src/databases/models/Battlestation/Dram.php"); require_once VV::root("api/src/databases/models/Battlestation/Dram.php");
require_once Path::root("api/src/databases/models/Battlestation/Storage.php"); require_once VV::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/Chassis.php");
// Load hardware config database models // Load hardware config database models
require_once Path::root("api/src/databases/models/Battlestation/Config/MbPsu.php"); require_once VV::root("api/src/databases/models/Battlestation/Config/MbPsu.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbGpu.php"); require_once VV::root("api/src/databases/models/Battlestation/Config/MbGpu.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbDram.php"); require_once VV::root("api/src/databases/models/Battlestation/Config/MbDram.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/Config.php"); require_once VV::root("api/src/databases/models/Battlestation/Config/Config.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/MbStorage.php"); require_once VV::root("api/src/databases/models/Battlestation/Config/MbStorage.php");
require_once Path::root("api/src/databases/models/Battlestation/Config/ChassisMb.php"); require_once VV::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/MbCpuCooler.php");
const GIGA = 0x3B9ACA00; const GIGA = 0x3B9ACA00;
const MEGA = 0xF4240; const MEGA = 0xF4240;
@ -55,7 +55,7 @@
$config = $api->call(Endpoints::BATTLESTATION->value)->get(); $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): ?> <?php if ($config->ok): ?>
<section class="title"> <section class="title">
<h1>Battle&shy;stations</h1> <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-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)) ?>" 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"> <div class="specs">
<?php // Show motherboard details ?> <?php // Show motherboard details ?>
<?php if ($motherboard): ?> <?php if ($motherboard): ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="mb" class="spec"> <div data-target="mb" class="spec">
<p>Motherboard</p> <p>Motherboard</p>
<h3><?= $motherboard[MbModel::VENDOR_NAME->value] ?> <span><?= $motherboard[MbModel::VENDOR_MODEL->value] ?></span></h3> <h3><?= $motherboard[MbModel::VENDOR_NAME->value] ?> <span><?= $motherboard[MbModel::VENDOR_MODEL->value] ?></span></h3>
<div> <div>
@ -149,7 +149,7 @@
ChassisModel::ID->value => $mb_chassis[ChassisMbModel::REF_CHASSIS_ID->value] ChassisModel::ID->value => $mb_chassis[ChassisMbModel::REF_CHASSIS_ID->value]
])->get()->json()[0]; ?> ])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="case" class="spec"> <div data-target="case" class="spec">
<p>Case</p> <p>Case</p>
<h3><?= $case[ChassisModel::VENDOR_NAME->value] ?> <span><?= $case[ChassisModel::VENDOR_MODEL->value] ?></span></h3> <h3><?= $case[ChassisModel::VENDOR_NAME->value] ?> <span><?= $case[ChassisModel::VENDOR_MODEL->value] ?></span></h3>
<div> <div>
@ -192,7 +192,7 @@
CpuModel::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value] CpuModel::ID->value => $mb_cpu[MbCpuCoolerModel::REF_CPU_ID->value]
])->get()->json()[0]; ?> ])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="cpu" class="spec"> <div data-target="cpu" class="spec">
<p>CPU</p> <p>CPU</p>
<h3><?= $cpu[CpuModel::VENDOR_NAME->value] ?> <span><?= $cpu[CpuModel::VENDOR_MODEL->value] ?></span></h3> <h3><?= $cpu[CpuModel::VENDOR_NAME->value] ?> <span><?= $cpu[CpuModel::VENDOR_MODEL->value] ?></span></h3>
<div> <div>
@ -257,7 +257,7 @@
GpuModel::ID->value => $mb_gpu[MbGpuModel::REF_GPU_ID->value] GpuModel::ID->value => $mb_gpu[MbGpuModel::REF_GPU_ID->value]
])->get()->json()[0]; ?> ])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="gpu" class="spec"> <div data-target="gpu" class="spec">
<p>GPU</p> <p>GPU</p>
<h3><?= $gpu[GpuModel::VENDOR_NAME->value] ?> <span><?= $gpu[GpuModel::VENDOR_CHIP_MODEL->value] ?></span></h3> <h3><?= $gpu[GpuModel::VENDOR_NAME->value] ?> <span><?= $gpu[GpuModel::VENDOR_CHIP_MODEL->value] ?></span></h3>
<div> <div>
@ -304,7 +304,7 @@
PsuModel::ID->value => $mb_psu[MbPsuModel::REF_PSU_ID->value] PsuModel::ID->value => $mb_psu[MbPsuModel::REF_PSU_ID->value]
])->get()->json()[0]; ?> ])->get()->json()[0]; ?>
<div vv="battlestation" vv-call="setSpecActive" data-target="psu" class="spec"> <div data-target="psu" class="spec">
<p>PSU</p> <p>PSU</p>
<h3><?= $psu[PsuModel::VENDOR_NAME->value] ?> <span><?= $psu[PsuModel::VENDOR_MODEL->value] ?></span> <span><?= $psu[PsuModel::POWER->value] ?>W</span></h3> <h3><?= $psu[PsuModel::VENDOR_NAME->value] ?> <span><?= $psu[PsuModel::VENDOR_MODEL->value] ?></span> <span><?= $psu[PsuModel::POWER->value] ?>W</span></h3>
<div> <div>
@ -343,9 +343,9 @@
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<div vv="battlestation" vv-call="toggleGroup" class="group"> <div class="group">
<p>DRAM</p> <p>DRAM</p>
<?= VV::media("icons/chevron.svg") ?> <?= VV::embed("assets/media/icons/chevron.svg") ?>
</div> </div>
<div class="collection"> <div class="collection">
@ -357,7 +357,7 @@
DramModel::ID->value => $mb_dram[MbDramModel::REF_DRAM_ID->value] DramModel::ID->value => $mb_dram[MbDramModel::REF_DRAM_ID->value]
])->get()->json()[0]; ?> ])->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> <p>DRAM - <?= $dram[DramModel::TECHNOLOGY->value] ?></p>
<h3><?= $dram[DramModel::VENDOR_NAME->value] ?> <h3><?= $dram[DramModel::VENDOR_NAME->value] ?>
<span><?= $dram[DramModel::CAPACITY->value] / GIGA ?>GB</span> <span><?= $dram[DramModel::CAPACITY->value] / GIGA ?>GB</span>
@ -422,9 +422,9 @@
<?php endforeach; ?> <?php endforeach; ?>
</div> </div>
<div vv="battlestation" vv-call="toggleGroup" class="group"> <div class="group">
<p>Storage</p> <p>Storage</p>
<?= VV::media("icons/chevron.svg") ?> <?= VV::embed("assets/media/icons/chevron.svg") ?>
</div> </div>
<div class="collection"> <div class="collection">
@ -436,7 +436,7 @@
StorageModel::ID->value => $mb_storage[MbStorageModel::REF_STORAGE_ID->value] StorageModel::ID->value => $mb_storage[MbStorageModel::REF_STORAGE_ID->value]
])->get()->json()[0]; ?> ])->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> <p><?= $storage[StorageModel::DISK_FORMFACTOR->value] ?> <?= $storage[StorageModel::DISK_TYPE->value] ?></p>
<h3> <h3>
<?= $storage[StorageModel::VENDOR_NAME->value] ?> <?= $storage[StorageModel::VENDOR_NAME->value] ?>
@ -493,4 +493,4 @@
</section> </section>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?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

@ -7,10 +7,8 @@
without any exceptions. without any exceptions.
*/ */
use Vegvisir\Path;
// Get tags from local git folder // 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 // Get current version number from latest tag
$version = end($dir); $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; use VLW\API\Databases\VLWdb\Models\Messages\MessagesModel;
require_once Path::root("src/client/API.php"); require_once VV::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.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 // Connect to VLW API
$api = new API(); $api = new API();
?> ?>
<style><?= VV::css("pages/contact") ?></style> <style><?= VV::css("assets/css/pages/contact") ?></style>
<section> <section>
<h1>Let's chat</h1> <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> <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>
<section class="social"> <section class="social">
<a href="mailto:victor@vlw.se"><social> <a href="mailto:victor@vlw.se"><social>
<?= VV::media("icons/email.svg") ?> <?= VV::embed("assets/media/icons/email.svg") ?>
<p>e-mail</p> <p>e-mail</p>
</social></a> </social></a>
<a href="https://mastodon.social/@vlwone"><social> <a href="https://mastodon.social/@vlwone"><social>
<?= VV::media("icons/mastodon.svg") ?> <?= VV::embed("assets/media/icons/mastodon.svg") ?>
<p>mastodon</p> <p>mastodon</p>
</social></a> </social></a>
<a href="https://web.libera.chat/#vlw.se"><social> <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> <p>libera.chat</p>
</social></a> </social></a>
</section> </section>
<?= VV::media("line.svg") ?> <?= VV::embed("assets/media/line.svg") ?>
<section class="pgp"> <section class="pgp">
<?= VV::media("icons/pin.svg") ?> <?= VV::embed("assets/media/icons/pin.svg") ?>
<h3>encrypt your message with my OpenPGP key.</h3> <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> <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"> <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> <a href="https://emailselfdefense.fsf.org/en/" target="_blank" rel="noopener noreferer"><button class="inline">more info</button></a>
</div> </div>
</section> </section>
<?= VV::media("line.svg") ?> <?= VV::embed("assets/media/line.svg") ?>
<?php // Send message on POST request ?> <?php // Send message on POST request ?>
<?php if ($_SERVER["REQUEST_METHOD"] === "POST"): ?> <?php if ($_SERVER["REQUEST_METHOD"] === "POST"): ?>
@ -88,4 +88,4 @@
</form> </form>
</section> </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"> <div class="menu">
<?= VV::media("line.svg") ?> <?= VV::embed("assets/media/line.svg") ?>
<menu> <menu>
<a href="/work" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::WORK->value ?>" data-hue="90">work</li></a> <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="/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> <a href="/contact" vv="index" vv-call="navigate"><li data-rgb="<?= RGB::CONTACT->value ?>" data-hue="200">contact</li></a>
</menu> </menu>
<?= VV::media("line.svg") ?> <?= VV::embed("assets/media/line.svg") ?>
<button class="email" vv="index" vv-call="copyEmail"> <button class="email">
<p>victor@vlw.se</p> <p>victor@vlw.se</p>
<p class="cta">to copy</p> <p class="cta">to copy</p>
</button> </button>
</div> </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"/> <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 WorkActionsModel
}; };
require_once Path::root("src/client/API.php"); require_once VV::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php"); require_once VV::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php"); require_once VV::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/WorkActions.php");
// Search endpoint query paramter // Search endpoint query paramter
const SEARCH_PARAM = "q"; const SEARCH_PARAM = "q";
@ -26,7 +26,7 @@
$response = $api->call(Endpoints::SEARCH->value)->params([SEARCH_PARAM => $_GET[SEARCH_PARAM]])->get(); $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"> <section class="search">
<form method="GET"> <form method="GET">
<search> <search>
@ -34,7 +34,7 @@
</search> </search>
<button type="submit" class="inline solid">Search</button> <button type="submit" class="inline solid">Search</button>
</form> </form>
<?= VV::media("line.svg") ?> <?= VV::embed("assets/media/line.svg") ?>
<button class="inline">advanced search options</button> <button class="inline">advanced search options</button>
</section> </section>
@ -89,11 +89,11 @@
</section> </section>
<?php else: ?> <?php else: ?>
<section class="info empty"> <section class="info empty">
<?= VV::media("icons/search.svg") ?> <?= VV::embed("assets/media/icons/search.svg") ?>
<p>Start typing to search</p> <p>Start typing to search</p>
</section> </section>
<?php endif; ?> <?php endif; ?>
<?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 WorkActionsModel
}; };
require_once Path::root("src/client/API.php"); require_once VV::root("src/client/API.php");
require_once Path::root("api/src/Endpoints.php"); require_once VV::root("api/src/Endpoints.php");
require_once Path::root("api/src/databases/models/Work/Work.php"); require_once VV::root("api/src/databases/models/Work/Work.php");
require_once Path::root("api/src/databases/models/Work/WorkTags.php"); require_once VV::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/WorkActions.php");
// Connect to VLW API // Connect to VLW API
$api = new 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"> <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> <p>Most of my free open-source software is available on GitHub and it's also mirrored on my server</p>
<div class="buttons"> <div class="buttons">
<a href="https://github.com/victorwesterlund"><button class="inline solid">open GitHub</button></a> <a href="https://github.com/victorwesterlund"><button class="inline solid">open GitHub</button></a>
@ -178,4 +178,4 @@
<p>Something went wrong!</p> <p>Something went wrong!</p>
<?php endif; ?> <?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> </script>
<?php // Bootstrapping ?> <?php // Bootstrapping ?>
<style><?= VV::css("fonts") ?></style> <style><?= VV::css("assets/css/fonts") ?></style>
<style><?= VV::css("document") ?></style> <style><?= VV::css("assets/css/shells/document") ?></style>
<title>Victor L. Westerlund</title> <title>Victor L. Westerlund</title>
<link rel="icon" href="/assets/media/vw.svg"/> <link rel="icon" href="/assets/media/vw.svg"/>
@ -49,28 +49,28 @@
<nav> <nav>
<p><a href="/" vv="document" vv-call="navigate">victor westerlund</a></p> <p><a href="/" vv="document" vv-call="navigate">victor westerlund</a></p>
</nav> </nav>
<button class="search" vv="document" vv-call="openSearchbox"> <button class="search searchbox-open">
<?= VV::media("icons/search.svg") ?> <?= VV::embed("assets/media/icons/search.svg") ?>
<p>search vlw.se...</p> <p>search vlw.se...</p>
</button> </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> <searchbox>
<input type="search" autocomplete="off" placeholder="search vlw.se..."> <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> </searchbox>
</header> </header>
<main></main> <vv-shell></vv-shell>
<search-results> <search-results>
<div class="info empty"> <div class="info empty">
<?= VV::media("icons/search.svg") ?> <?= VV::embed("assets/media/icons/search.svg") ?>
<p>start typing to search</p> <p>start typing to search</p>
</div> </div>
</search-results> </search-results>
<?php // Bootstrapping ?> <?php // Bootstrapping ?>
<script><?= VV::init() ?></script> <?= VV::init() ?>
<script><?= VV::js("document") ?></script> <script type="module"><?= VV::js("assets/js/shells/document") ?></script>
</body> </body>
</html> </html>