wip(22w6b): add content and more glitching

This commit is contained in:
Victor Westerlund 2022-02-12 16:42:33 +01:00
parent 405fbe0f28
commit a610af6ce6
17 changed files with 170 additions and 306 deletions

View file

@ -1,9 +1,12 @@
:root { :root {
--color-base: 0, 0, 0; --color-base: 0, 0, 0;
--color-: 33, 33, 33; --color-contrast: 256, 256, 256;
--color-contrast: 255, 255, 225;
--padding: 30px; --padding: 40px;
--font-min: 30px;
--font-tar: 10vw;
--font-max: 4vh;
} }
/* -- Cornerstones -- */ /* -- Cornerstones -- */
@ -16,16 +19,7 @@
*::selection { *::selection {
background-color: rgb(var(--color-contrast)); background-color: rgb(var(--color-contrast));
color: rgb(var(--color-background)); color: rgb(var(--color-base));
}
a {
display: inline-block;
padding: 15px 30px;
border-radius: 100px;
border: solid 4px rgba(var(--color-contrast), .3);
text-decoration: none;
margin: var(--padding) 0;
} }
html, html,
@ -33,53 +27,120 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow-x: hidden; overflow-x: hidden;
background-color: rgba(var(--color-base), .8); background-color: rgba(var(--color-base), .7);
background-size: cover; background-size: cover;
background-blend-mode: overlay; background-blend-mode: overlay;
background-position: fixed; background-position: fixed;
} }
:is(p, h1) {
font-size: var(--font-min);
user-select: none;
}
a {
display: inline-block;
text-decoration: none;
text-align: center;
font-size: 20px;
}
h1 {
font-size: clamp(var(--font-min), var(--font-tar), var(--font-max));
}
p {
font-size: clamp(calc(var(--font-min) / 2), calc(var(--font-tar) / 2), calc(var(--font-max) / 2));
}
/* -- Components -- */ /* -- Components -- */
body { body {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center;
justify-items: center;
gap: var(--padding, 30px); gap: var(--padding, 30px);
} }
body > div { body > div {
padding: var(--padding); padding: var(--padding);
justify-items: center;
} }
body > div:first-child { /* --- */
--font-min: 20px;
#intro {
--font-tar: 13vw; --font-tar: 13vw;
--font-max: 6vh; --font-max: 6vh;
} }
body > div:first-child :is(a, p, h1) { #intro a {
font-size: var(--font-min); padding: 15px 30px;
user-select: none; border-radius: 100px;
border: solid 4px rgba(var(--color-contrast), .3);
margin: var(--padding) 0;
width: calc(100% - (var(--padding) * 2));
} }
body > div:first-child p { #intro p {
font-size: clamp(calc(var(--font-min) / 2), calc(var(--font-tar) / 2), calc(var(--font-max) / 2)); margin: 10px 0;
} }
body > div:first-child h1 { /* --- */
font-size: clamp(var(--font-min), var(--font-tar), var(--font-max));
#card,
#card > div {
display: flex;
flex-direction: column;
align-items: center;
gap: calc(var(--padding) / 2);
}
#card {
--portrait-size: 128px;
gap: var(--padding);
position: relative;
max-width: 500px;
background-color: rgb(var(--color-base));
padding: var(--padding);
padding-top: calc(var(--portrait-size) - (var(--padding) / 2));
border-radius: 18px;
}
#card > img {
width: var(--portrait-size);
height: var(--portrait-size);
position: absolute;
border-radius: 100%;
top: calc((var(--portrait-size) / 2) * -1);
box-shadow: 0 0 0 15px rgb(var(--color-base));
}
#card a {
width: 100%;
padding: 15px 0;
border-radius: 9px;
background-color: rgba(var(--color-contrast), .13);
} }
/* -- Media Queries -- */ /* -- Media Queries -- */
@media (pointer: fine) { @media (pointer: fine) {
a:hover { #intro a:hover {
background: rgba(var(--color-contrast), .1); background-color: rgba(var(--color-contrast), .1);
}
#card a:hover {
background-color: rgba(var(--color-contrast), .2);
} }
} }
@media (min-aspect-ratio: 14/9) and (min-height: 500px) { @media (min-aspect-ratio: 14/9) and (min-height: 450px) {
#intro a {
width: unset;
}
body { body {
display: grid; display: grid;
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, 1fr);

View file

@ -0,0 +1,45 @@
export default class Background {
constructor(target) {
this.images = {
dir: "assets/media/b64/",
count: 2
}
this.image = null;
this.target = target ? target : document.body;
this.updateBg = setInterval(() => this.randBg(), 2000);
}
// Update the target CSS background
setBg(image = this.image) {
this.target.style.setProperty("background-image", `url(${image})`);
}
// Genrate random int in range
randInt(min, max) {
return Math.round(Math.random() * (max - min) + min);
}
// Fetch a base64 encoded background image
async fetchBg(id) {
const url = new URL(window.location);
url.pathname += this.images.dir;
url.pathname += id + ".txt";
const image = await fetch(url);
if(!image.ok) throw new Error("Failed to fetch background image");
return image.text();
}
// Load a random background from the image set
async randBg() {
const id = this.randInt(1, this.images.count);
const image = await this.fetchBg(id);
this.image = image;
this.setBg(image);
}
}

View file

@ -1,7 +1,8 @@
export default class Glitch { import { default as Background } from "./Background.mjs";
constructor(image, target) {
this.image = image; export default class Glitch extends Background {
this.target = target ? target : document.body; constructor(target) {
super(target);
this.interval = { this.interval = {
_this: this, _this: this,
@ -13,17 +14,12 @@ export default class Glitch {
} }
} }
this.interval.next = 2000; this.interval.next = 500;
this.setBackground(); this.randBg();
}
// Update the target CSS background
setBackground(image = this.image) {
this.target.style.setProperty("background-image", `url(${image})`);
} }
// Generate random string of length from charset // Generate random string of length from charset
randomString(length = 2) { randStr(length = 2) {
const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let output = ""; let output = "";
for(let i = 0; i < length; i++) { for(let i = 0; i < length; i++) {
@ -32,16 +28,10 @@ export default class Glitch {
return output; return output;
} }
// Genrate random int in range
randomInt(min, max) {
return Math.random() * (max - min) + min;
}
// Create a glitchy image // Create a glitchy image
glitch() { glitch() {
const glitched = this.image.replaceAll(this.randomString(), this.randomString()); const image = this.image.replaceAll(this.randStr(), this.randStr());
this.setBackground(glitched); this.setBg(image);
this.interval.next = this.randInt(100, 3500);
this.interval.next = this.randomInt(100, 1500);
} }
} }

1
public/assets/media/b64/1.txt Executable file

File diff suppressed because one or more lines are too long

View file

View file

@ -8,30 +8,21 @@
<link rel="icon" href="/assets/media/favicon-dark.png" media="(prefers-color-scheme:no-preference)"> <link rel="icon" href="/assets/media/favicon-dark.png" media="(prefers-color-scheme:no-preference)">
<link rel="icon" href="/assets/media/favicon-dark.png" media="(prefers-color-scheme:light)"> <link rel="icon" href="/assets/media/favicon-dark.png" media="(prefers-color-scheme:light)">
<link rel="icon" href="/assets/media/favicon-light.png" media="(prefers-color-scheme:dark)"> <link rel="icon" href="/assets/media/favicon-light.png" media="(prefers-color-scheme:dark)">
<link rel="stylesheet" href="/assets/css/style.css">
<link rel="stylesheet" href="/assets/css/components.css">
<style> <style>
main { align-items: center; gap: unset; } html, body { margin: 30px; font-family: monospace; color: black; }
h1 { font-size: clamp(25px,40px,3vw); } span { background: black; color: white; }
.button { margin-top: calc(var(--padding) * 2); } @media (prefers-color-scheme: dark) {
footer { background-color: transparent; } html, body, a { background: black; color: white; }
span { background: white; color: black; }
}
</style> </style>
</head> </head>
<body> <body>
<header> <h1>there is nothing here</h1>
<p><a href="/">victor westerlund</a></p> <p>and that is all I know</p>
</header> <a href="/"><h2>take me home</h2></a>
<main>
<h1>there is nothing here</h1>
<p>and that is all I know</p>
<a href="/">
<div class="button">
<p>take me home</p>
</div>
</a>
</main>
<footer> <footer>
<p>404 not found</p> <span>victorwesterlund.com</span>
</footer> </footer>
</body> </body>
</html> </html>

View file

@ -12,7 +12,7 @@
</head> </head>
<body> <body>
<div> <div>
<div> <div id="intro">
<p>hello, my name is</p> <p>hello, my name is</p>
<h1>victor</h1> <h1>victor</h1>
<p>I'm a</p> <p>I'm a</p>
@ -23,11 +23,23 @@
</div> </div>
</div> </div>
<div> <div>
<div id="card">
<img src="https://lh3.googleusercontent.com/a-/AOh14Ggkm-Fr7rjHKeJHKHNOZoM72lARq25kIJS73Wo0SU4=s128-c-rg-br100" alt="portrait of victor"/>
<div>
<p>I create things with code. When I'm not creating things with code, I enjoy skiing, watching movies and some occasional gaming</p>
<p>Beyond computer science, I'm also a armchair rabbit-holer for engineering, astronomy and physics</p>
</div>
<div>
<h1></h1>
<p>...and coffee, full-time</p>
</div>
<a href="#">stalk me 😬</a>
</div>
</div> </div>
<script type="module"> <script type="module">
import { default as Glitch } from "./assets/js/modules/Glitch.mjs"; import { default as Glitch } from "./assets/js/modules/Glitch.mjs";
fetch("assets/media/bg64.txt").then((resp) => resp.text().then(bg64 => new Glitch(bg64, document.body))); //fetch("assets/media/bg64.txt").then((resp) => resp.text().then(bg64 => new Glitch(bg64, document.body)));
new Glitch(document.body)
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,50 +0,0 @@
<?php
class APIRouter {
public function __construct($path) {
// List of implemented API services
$this->services = [
"search" => function() {
require_once "/search/Search.php";
new Search();
}
];
$this->url = parse_url($path);
$this->run();
}
// Find the requested service by looking at the next URI breadcrumb after "api"
private function get_service() {
$path = explode("/",$this->url["path"]);
$service = array_search("api",$path) + 1; // Next array value
$service = $path[$service];
return $service;
}
private function error($message,$code = 500) {
$output = [
"ok" => false,
"code" => strval($code),
"message" => $message
];
header("Content-Type: application/json");
http_response_code($code);
echo json_encode($output);
}
// Run the requested service if it exists in services list
private function run() {
$service = $this->get_service();
if(!array_key_exists($service,$this->services)) {
$this->error("Inavlid API");
return false;
}
// Import and run requested service
$this->services[$service]();
}
}
new APIRouter($_SERVER["REQUEST_URI"]);

View file

@ -1,16 +0,0 @@
<?php
class Import {
// Import assets from disk
public static function file($file) {
$content = file_get_contents($file);
return $content;
}
// Import JSON to PHP list
public static function json($file) {
$contents = Import::file($file);
$json = json_decode($contents);
return $json;
}
}

View file

@ -1,47 +0,0 @@
<?php
include_once dirname(__DIR__,1)."/core/Import.php";
class Database extends mysqli {
public function __construct($table) {
// Load config file from this directory
$config_path = dirname(__FILE__,1)."/config.json";
$config = Import::json($config_path);
parent::__construct();
//$this->ssl_set();
// Attempt to connect to MySQL servers in order (moving to the next on failure)
foreach($config->servers as $server) {
$db = $this->real_connect($server->host,$server->user,$server->pass,$server->db);
if($db) {
return true;
}
}
}
// Exit with error code
private function error($message) {
http_response_code(500);
header("Content-Type: application/json");
$output = json_encode([
"error" => $message
]);
die($output);
}
// Return affected rows as an array of arrays
protected function get_rows($sql) {
if(!$this->ping()) {
$this->error("No database connected");
}
$query = $this->query($sql);
$rows = [];
while($row = $query->fetch_row()) {
$rows[] = $row;
}
return $rows;
}
}

View file

@ -1,10 +0,0 @@
{
"servers": [
{
"host": "",
"user": "",
"pass": "",
"db": ""
}
]
}

View file

@ -1,87 +0,0 @@
<?php
require_once dirname(__DIR__,1)."/core/Import.php";
require_once dirname(__DIR__,1)."/database/Database.php";
class Search extends Database {
public function __construct() {
parent::__construct("search");
$this->query = $this->real_escape_string($_GET["q"]); // Escape the user-provided query
// Determine response type from request header or search param
$mime_type = $_SERVER["HTTP_CONTENT_TYPE"] ? $_SERVER["HTTP_CONTENT_TYPE"] : $_GET["f"];
switch($mime_type) {
case "html":
case "text/html":
$this->get_html();
break;
default:
case "json":
case "application/json":
$this->get_json();
break;
}
}
// Perform a seach on the given query and return the results as an array
private function get_results() {
$sql = "SELECT template,title,content,href FROM `search` WHERE `title` LIKE '%{$this->query}%' OR `content` LIKE '%{$this->query}%'";
$rows = $this->get_rows($sql);
return $rows;
}
// Load HTML template from disk
private function get_html_template($name) {
$path = dirname(__FILE__,1)."/templates/${name}.html";
if(!is_file($path)) {
return $this->get_html_template("card_error_display");
}
$html = Import::file($path);
return $html;
}
// Return query as HTML from templates
private function get_html() {
$results = $this->get_results();
if(count($results) < 1) {
$results[] = ["message","info","no results 😞"];
}
// Load HTML and format each response from template
$results = array_map(function($result) {
// Use first row as template name
$template = $this->get_html_template($result[0]);
// Use remaining rows as format arguments
$format = array_shift($result);
return sprintf($template,...$result);
},$results);
header("Content-Type: text/html");
echo implode("",$results);
}
// Return query as JSON
private function get_json() {
$results = $this->get_results();
$data = [
"results" => []
];
// Assign custom keys to each value (not db columns)
foreach($results as $result) {
$data["results"][] = [
"html_template" => $result[0],
"title" => $result[1],
"content" => $result[2],
"href" => $result[3]
];
}
$json = json_encode($data);
header("Content-Type: application/json");
echo $json;
}
}

View file

@ -1,8 +0,0 @@
<div class="card">
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M3 2.75A2.75 2.75 0 0 1 5.75 0h14.5a.75.75 0 0 1 .75.75v20.5a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h5.25v-4H6A1.5 1.5 0 0 0 4.5 18v.75c0 .716.43 1.334 1.05 1.605a.75.75 0 0 1-.6 1.374A3.25 3.25 0 0 1 3 18.75v-16zM19.5 1.5V15H6c-.546 0-1.059.146-1.5.401V2.75c0-.69.56-1.25 1.25-1.25H19.5z" fill="currentColor"/><path d="M7 18.25a.25.25 0 0 1 .25-.25h5a.25.25 0 0 1 .25.25v5.01a.25.25 0 0 1-.397.201l-2.206-1.604a.25.25 0 0 0-.294 0L7.397 23.46a.25.25 0 0 1-.397-.2v-5.01z" fill="currentColor"/></svg>
<p>%s</p>
</div>
<p>%s</p>
<p href="%s" class="button">read more</p>
</div>

View file

@ -1,4 +0,0 @@
<div class="card error">
<p><strong>There was a problem displaying this result</strong></p>
<p>This is a problem on my side, sorry about that</p>
</div>

View file

@ -1,8 +0,0 @@
<div class="card">
<div>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M3 2.75A2.75 2.75 0 0 1 5.75 0h14.5a.75.75 0 0 1 .75.75v20.5a.75.75 0 0 1-.75.75h-6a.75.75 0 0 1 0-1.5h5.25v-4H6A1.5 1.5 0 0 0 4.5 18v.75c0 .716.43 1.334 1.05 1.605a.75.75 0 0 1-.6 1.374A3.25 3.25 0 0 1 3 18.75v-16zM19.5 1.5V15H6c-.546 0-1.059.146-1.5.401V2.75c0-.69.56-1.25 1.25-1.25H19.5z" fill="currentColor"/><path d="M7 18.25a.25.25 0 0 1 .25-.25h5a.25.25 0 0 1 .25.25v5.01a.25.25 0 0 1-.397.201l-2.206-1.604a.25.25 0 0 0-.294 0L7.397 23.46a.25.25 0 0 1-.397-.2v-5.01z" fill="currentColor"/></svg>
<p>%s</p>
</div>
<p>%s</p>
<p href="%s" class="button">read more</p>
</div>

View file

@ -1 +0,0 @@
<p class="%s">%s</p>

View file

@ -1,5 +0,0 @@
<div class="resultsFooter">
<svg id="previous"><polygon points="40,10 0,20 40,30"/></svg>
<p>showing %s/%s results<span> (query took %s seconds)</span></p>
<svg id="next"><polygon points="0,10 40,20 0,30"/></svg>
</div>