Compare commits

...

2 commits

13 changed files with 327 additions and 22 deletions

View file

@ -5,4 +5,5 @@ pass = ""
[databases] [databases]
vlw = "" vlw = ""
playground = ""
battlestation = "" battlestation = ""

View file

@ -0,0 +1,40 @@
<?php
use Reflect\Path;
use Reflect\Response;
use ReflectRules\Type;
use ReflectRules\Rules;
use ReflectRules\Ruleset;
use const VLW\API\RESP_DELETE_OK;
use VLW\API\Databases\VLWdb\{
VLWdb,
Databases
};
use VLW\API\Databases\VLWdb\Models\Playground\Coffee\CoffeeModel;
require_once Path::root("src/databases/VLWdb.php");
require_once Path::root("src/databases/models/Playground/Coffee/Coffee.php");
class DELETE_PlaygroundCoffee extends VLWdb {
protected Ruleset $ruleset;
public function __construct() {
parent::__construct();
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([
(new Rules(CoffeeModel::ID->value))
->required()
->type(Type::NUMBER)
]);
parent::__construct(Databases::PLAYGROUND, $this->ruleset);
}
public function main(): Response {
return $this->db->for(WorkActionsModel::TABLE)->delete($_POST) === true
? new Response(RESP_DELETE_OK)
: new Response("D:", 500);
}
}

View file

@ -0,0 +1,40 @@
<?php
use Reflect\Path;
use Reflect\Response;
use ReflectRules\Type;
use ReflectRules\Rules;
use ReflectRules\Ruleset;
use VLW\API\Databases\VLWdb\{
VLWdb,
Databases
};
use VLW\API\Databases\VLWdb\Models\Playground\Coffee\CoffeeModel;
require_once Path::root("src/databases/VLWdb.php");
require_once Path::root("src/databases/models/Playground/Coffee/Coffee.php");
class GET_PlaygroundCoffee extends VLWdb {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
parent::__construct(Databases::PLAYGROUND, $this->ruleset);
}
public function main(): Response {
$result = $this->db
->for(CoffeeModel::TABLE)
->order([CoffeeModel::ID->value => "DESC"])
->select([
CoffeeModel::ID->value
]);
return $result->num_rows > 0
// Return entries as sequential array of integer timestamps
? new Response(array_map(fn(array $value): int => $value[0], $result->fetch_all(MYSQLI_NUM)))
: new Response([], 404);
}
}

View file

@ -0,0 +1,38 @@
<?php
use Reflect\Path;
use Reflect\Response;
use ReflectRules\Type;
use ReflectRules\Rules;
use ReflectRules\Ruleset;
use VLW\API\Databases\VLWdb\{
VLWdb,
Databases
};
use VLW\API\Databases\VLWdb\Models\Playground\Coffee\CoffeeModel;
require_once Path::root("src/databases/VLWdb.php");
require_once Path::root("src/databases/models/Playground/Coffee/Coffee.php");
class POST_PlaygroundCoffee extends VLWdb {
protected Ruleset $ruleset;
public function __construct() {
$this->ruleset = new Ruleset(strict: true);
$this->ruleset->GET([
(new Rules(CoffeeModel::ID->value))
->type(Type::NUMBER)
->default(time())
]);
parent::__construct(Databases::PLAYGROUND, $this->ruleset);
}
public function main(): Response {
return $this->db->for(CoffeeModel::TABLE)->insert($_POST) === true
? new Response($_POST[CoffeeModel::ID->value], 201)
: new Response(":(", 500);
}
}

View file

@ -15,6 +15,8 @@
case WORK_TAGS = "/work/tags"; case WORK_TAGS = "/work/tags";
case WORK_ACTIONS = "/work/actions"; case WORK_ACTIONS = "/work/actions";
case PLAYGROUND_COFFEE = "/playground/coffee";
case BATTLESTATION = "/battlestation"; case BATTLESTATION = "/battlestation";
case BATTLESTATION_MB = "/battlestation/mb"; case BATTLESTATION_MB = "/battlestation/mb";
case BATTLESTATION_CPU = "/battlestation/cpu"; case BATTLESTATION_CPU = "/battlestation/cpu";

View file

@ -11,6 +11,7 @@
enum Databases: string { enum Databases: string {
case VLW = "vlw"; case VLW = "vlw";
case PLAYGROUND = "playground";
case BATTLESTATION = "battlestation"; case BATTLESTATION = "battlestation";
} }

View file

@ -0,0 +1,9 @@
<?php
namespace VLW\API\Databases\VLWdb\Models\Playground\Coffee;
enum CoffeeModel: string {
const TABLE = "coffee";
case ID = "id";
}

View file

@ -20,6 +20,8 @@ header {
} }
img { img {
user-select: none;
pointer-events: none;
image-rendering: pixelated; image-rendering: pixelated;
} }
@ -28,20 +30,20 @@ img {
/* ## Title */ /* ## Title */
section.title { section.title {
gap: var(--padding);
display: grid; display: grid;
align-items: center; align-items: center;
justify-items: center; justify-items: center;
grid-template-columns: repeat(10, 1fr); min-height: 50vw;
min-height: 30vh; grid-template-columns: repeat(5, 1fr);
padding-bottom: var(--padding);
} }
section.title img { section.title img {
--outline-width: 5px; --outline-width: 5px;
--outline-color: black; --outline-color: black;
width: 100%; width: 10vw;
transform: translateY(-10px); transform: translateY(-1vw);
animation: bouncy 1s infinite alternate ease-in-out; animation: bouncy 1s infinite alternate ease-in-out;
filter: filter:
drop-shadow(0 10px 0 blue) drop-shadow(0 10px 0 blue)
@ -53,17 +55,47 @@ section.title img {
} }
@keyframes bouncy { @keyframes bouncy {
25% { transform: translateY(10px); } 25% { transform: translateY(0); }
100% { transform: translateY(10px); } 100% { transform: translateY(0); }
}
/* ### Super Secret Settings */
section.sss {
border-radius: 6px;
padding: var(--padding);
margin: var(--padding) 0;
background-color: rgba(255, 255, 255, .1);
}
section.sss button {
margin-top: var(--padding);
} }
/* ## Coffee */ /* ## Coffee */
section.coffee { section.coffee {
display: grid; display: grid;
gap: var(--padding);
grid-template-columns: 100px 1fr; grid-template-columns: 100px 1fr;
} }
section.coffee img { section.coffee img {
height: 100%; width: 100%;
}
/* # Size queries */
@media (min-width: 700px) {
section.title {
gap: var(--padding);
margin: 50px 0;
min-height: unset;
grid-template-columns: repeat(10, 1fr);
}
section.title img {
width: 100%;
transform: translateY(-10px);
}
} }

View file

@ -1,9 +1,11 @@
/* # Sections */ /* # Sections */
super-secret-settings { super-secret-settings {
--border-width: 5px;
position: fixed; position: fixed;
top: 5px; top: var(--border-width);
left: 5px; left: var(--border-width);
width: 400px; width: 400px;
fill: white; fill: white;
color: white; color: white;
@ -11,8 +13,8 @@ super-secret-settings {
border: solid 1px white; border: solid 1px white;
background-color: rgb(0, 102, 204); background-color: rgb(0, 102, 204);
box-shadow: box-shadow:
0 0 0 5px black, 0 0 0 var(--border-width) black,
0 0 40px 10px rgba(0, 0, 0, .4); 0 0 40px calc(var(--border-width) * 2) rgba(0, 0, 0, .4);
} }
/* ## Header */ /* ## Header */
@ -20,12 +22,15 @@ super-secret-settings {
super-secret-settings section.header { super-secret-settings section.header {
display: grid; display: grid;
align-items: center; align-items: center;
border-bottom: solid 1px white;
grid-template-columns: 1fr repeat(2, var(--running-size)); grid-template-columns: 1fr repeat(2, var(--running-size));
height: var(--running-size); height: var(--running-size);
cursor: grab; cursor: grab;
} }
super-secret-settings:not(.collapsed) section.header {
border-bottom: solid 1px white;
}
super-secret-settings section.header:active { super-secret-settings section.header:active {
cursor: grabbing; cursor: grabbing;
} }
@ -42,7 +47,11 @@ super-secret-settings section.header svg {
super-secret-settings section.header button { super-secret-settings section.header button {
height: 100%; height: 100%;
border-left: solid 1px var(--border-color); border-left: solid 1px white;
}
super-secret-settings.collapsed section.header button.collapse svg {
transform: rotate(-90deg);
} }
/* ## Body */ /* ## Body */
@ -50,3 +59,18 @@ super-secret-settings section.header button {
super-secret-settings section.body { super-secret-settings section.body {
padding: var(--padding); padding: var(--padding);
} }
super-secret-settings.collapsed section.body {
display: none;
}
/* # Size queries */
@media not (hover: hover), (max-width: 900px) {
super-secret-settings {
top: unset;
bottom: 5px;
width: calc(100% - (var(--border-width) * 2));
transform: translate(0, 0) !important;
}
}

View file

@ -1,6 +1,6 @@
import { Elevent } from "/assets/js/modules/npm/Elevent.mjs"; import { Elevent } from "/assets/js/modules/npm/Elevent.mjs";
new Elevent("click", document.querySelector("section.sss button"), () => { new Elevent("click", document.querySelector("section.sss button"), (event) => {
const SSS_TAG_NAME = "super-secret-settings"; const SSS_TAG_NAME = "super-secret-settings";
let sssElement = document.querySelector(SSS_TAG_NAME); let sssElement = document.querySelector(SSS_TAG_NAME);
@ -11,6 +11,13 @@ new Elevent("click", document.querySelector("section.sss button"), () => {
document.body.appendChild(sssElement); document.body.appendChild(sssElement);
} }
const size = event.target.getBoundingClientRect();
// Set initial window position above button
const y = size.top;
const x = size.left + size.width;
sssElement.style.transform = `translate(${x}px, ${y}px)`
// Navigate SSS element to playground page // Navigate SSS element to playground page
new vegvisir.Navigation("/playground/super-secret-settings").navigate(sssElement); new vegvisir.Navigation("/playground/super-secret-settings").navigate(sssElement);
}); });

View file

@ -8,6 +8,7 @@ const clampRight = (window.innerWidth - sssElement._size.width) - (borderWidth *
const clampBottom = (window.innerHeight - sssElement._size.height) - (borderWidth * 3); const clampBottom = (window.innerHeight - sssElement._size.height) - (borderWidth * 3);
const moveWindow = new Elevent("mousemove", null, (event) => { const moveWindow = new Elevent("mousemove", null, (event) => {
// Clamp window to viewport
const y = Math.max(borderWidth, Math.min(clampBottom, (event.clientY - 40))); const y = Math.max(borderWidth, Math.min(clampBottom, (event.clientY - 40)));
const x = Math.max(borderWidth, Math.min(clampRight, event.clientX - (sssElement._size.width / 2))); const x = Math.max(borderWidth, Math.min(clampRight, event.clientX - (sssElement._size.width / 2)));
@ -34,4 +35,5 @@ new Elevent("mousedown", sssElement.querySelector(".header"), (event) => {
}); });
}); });
new Elevent("click", sssElement.querySelector(".header button"), () => sssElement.remove()); new Elevent("click", sssElement.querySelector(".header button.close"), () => sssElement.remove());
new Elevent("click", sssElement.querySelector(".header button.collapse"), () => sssElement.classList.toggle("collapsed"));

View file

@ -1,20 +1,129 @@
<?php
use Reflect\Response;
use VLW\Client\API;
use VLW\API\Endpoints;
use VLW\API\Databases\VLWdb\Models\Work\WorkModel;
require_once VV::root("src/client/API.php");
require_once VV::root("api/src/Endpoints.php");
function to_plural(int $value): string {
return $value !== 1 ? "s" : "";
}
$time = new class extends DateTimeImmutable {
public function __construct() {
parent::__construct("now", new DateTimeZone($_ENV["time"]["date_time_zone"]));
}
public function hours(): int {
return (int) $this->format("G");
}
public function minutes(): int {
return (int) $this->format("i");
}
public function since_midnight_str(): string {
$hours = $this->hours();
$minutes = $this->minutes();
// Show minutes if we're not into the first hour yet
if ($hours < 1) {
return $minutes . " minute" . to_plural($minutes);
}
// Round up to the nearest hour
return ($minutes < 45 ? $hours : $hours + 1) . " hour" . to_plural($hours);
}
};
$coffee = new class extends API {
const THRESHOLDS = [
[-10, "a lot less than"],
[-5, "a bit less than"],
[0, "average"],
[5, "a bit more than"],
[10, "a lot more than"]
];
private readonly int $now;
private readonly Response $resp;
public function __construct() {
parent::__construct();
$this->now = time();
$this->resp = $this->call(Endpoints::PLAYGROUND_COFFEE->value)->get();
}
private function get_until_timestamp(int $offset = 0): array {
return array_filter($this->resp->json(), fn(int $timestamp): int => $timestamp > $offset);
}
private function get_average_month(): int {
$days = [0];
foreach ($this->get_until_timestamp(2592000) as $timestamp) {
// Append new value for next 24 hours
if ($timestamp % 86400 === 0) {
$days[] = 0;
}
$days[count($days) - 1]++;
}
// Count the average for each day
return array_sum($days) / count($days);
}
// Return the number of cups within the last 24 hours
public function get_today(): int {
return $this->resp->ok ? count($this->get_until_timestamp(86400)) : 0;
}
public function get_average_month_str(): string {
$diff = $this->get_today() - $this->get_average_month();
// Check diff of today against monthyl average thresholds
foreach (self::THRESHOLDS as $threshold) {
[$limit, $msg] = $threshold;
// Bail out if diff is less than threshold limit
if ($diff < $limit) {
return $msg;
}
}
// Return last entry if diff is more than last threshold limit
return self::THRESHOLDS[count(self::THRESHOLDS) - 1][1];
}
};
?>
<style><?= VV::css("public/assets/css/pages/playground/index") ?></style> <style><?= VV::css("public/assets/css/pages/playground/index") ?></style>
<section class="title"> <section class="title">
<?php // I was listening to https://www.youtube.com/watch?v=DsUb4Lq6DBE when I made the dancing letters ?> <!-- I was listening to https://www.youtube.com/watch?v=DsUb4Lq6DBE when I made the dancing letters -->
<?php foreach (str_split("playground") as $idx => $char): ?> <?php foreach (str_split("playground") as $idx => $char): ?>
<img src="data:image/gif;base64,<?= base64_encode(VV::embed("public/assets/media/playground/{$char}.gif")) ?>" style="animation-delay:<?= ($idx % rand(2, 4)) * 500 ?>ms"> <img src="data:image/gif;base64,<?= base64_encode(VV::embed("public/assets/media/playground/{$char}.gif")) ?>" style="animation-delay:<?= ($idx % rand(2, 4)) * 500 ?>ms">
<?php endforeach; ?> <?php endforeach; ?>
</section> </section>
<section class="sss"> <section class="sss">
<button class="inline">Super Secret Settings</button> <h1>I'm so bored.</h1>
<p>Don't like the way my website looks? These oficially very Super Secret Settings<sup><a href="https://minecraft-archive.fandom.com/wiki/Super_Secret_Settings">&#8482;</a></sup> will give you something else to look at!</p>
<button class="inline">Try it</button>
</section> </section>
<section class="coffee"> <section class="coffee">
<img src="data:image/gif;base64,<?= base64_encode(VV::embed("public/assets/media/playground/coffee.gif")) ?>"> <img src="data:image/gif;base64,<?= base64_encode(VV::embed("public/assets/media/playground/coffee.gif")) ?>">
<div> <div>
<h1>How much coffee is enough coffee?</h1> <h1>What else.. I've had <?= $coffee->get_today() ?> cup<?= to_plural($coffee->get_today()) ?> of coffee today!</h1>
<p>Woah, I 've had 4 cups of coffee today (counting from and to midnight). That's about the average amount for me (from the last 30 days)</p> <p>That's <?= $coffee->get_average_month_str() ?> the daily average for me (last 30 days). Here I'm counting how many ~300ml cups of coffee I've had since midnight for my timezone <?= $_ENV["time"]["date_time_zone"] ?>, that was <?= $time->since_midnight_str() ?> ago.</p>
<p>How much coffee is enough coffee? <a href="<?= $_ENV["api"]["base_url"] . Endpoints::PLAYGROUND_COFFEE->value ?>">Click here to see my answer!!</a> (NOT CLICKBAIT). Each array value is a UNIX timestamp that represents a single cup of coffee.</p>
</div> </div>
</section> </section>
<script type="module"><?= VV::js("public/assets/js/pages/playground/index") ?></script> <script type="module"><?= VV::js("public/assets/js/pages/playground/index") ?></script>

View file

@ -1,8 +1,8 @@
<style><?= VV::css("public/assets/css/pages/playground/super-secret-settings") ?></style> <style><?= VV::css("public/assets/css/pages/playground/super-secret-settings") ?></style>
<section class="header"> <section class="header">
<p>Super Secret Settings</p> <p>Super Secret Settings</p>
<button><?= VV::embed("public/assets/media/icons/chevron.svg") ?></button> <button class="collapse"><?= VV::embed("public/assets/media/icons/chevron.svg") ?></button>
<button><?= VV::embed("public/assets/media/icons/close.svg") ?></button> <button class="close"><?= VV::embed("public/assets/media/icons/close.svg") ?></button>
</section> </section>
<section class="body"> <section class="body">
<p>Do you have what it takes to click this button?</p> <p>Do you have what it takes to click this button?</p>