This commit is contained in:
flash 2020-07-30 00:55:37 +00:00
commit 1405d65126
15 changed files with 1850 additions and 0 deletions

18
components/footer.php Normal file
View file

@ -0,0 +1,18 @@
<?php
$quotes = isset($quotes) && is_array($quotes) ? $quotes : FM_FEET;
$baseUrl = empty($external) ? '' : '//' . $_SERVER['HTTP_HOST'];
if(empty($is_index)):
?>
</div>
<?php endif; if(empty($hide)): ?>
<div class="footer">
<div class="footer-text">&copy; flashwave 2010-<?=date('Y');?> - <?=($quotes[array_rand($quotes)]);?></div>
</div>
<?php endif; ?>
<script src="<?=$baseUrl;?>/assets/2020v2.js" charset="utf-8" type="text/javascript"></script>
<?php if(isset($scripts) && is_array($scripts)) foreach($scripts as $script): ?>
<script src="<?=$script;?>" charset="utf-8" type="text/javascript"></script>
<?php endforeach;?>
</body>
</html>

52
components/header.php Normal file
View file

@ -0,0 +1,52 @@
<?php
$menu = isset($menu) && is_array($menu) ? $menu : FM_NAV;
$backgrounds = isset($backgrounds) && is_array($backgrounds) ? $backgrounds : FM_BGS;
$baseUrl = empty($external) ? '' : '//' . $_SERVER['HTTP_HOST'];
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title><?=($title ?? 'flash.moe');?></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<link href="<?=$baseUrl;?>/assets/2020v2.css" type="text/css" rel="stylesheet"/>
<link href="//flash.moe/css/electrolize/style.css" type="text/css" rel="stylesheet"/>
<?php if(isset($styles) && is_array($styles)) foreach($styles as $style): ?>
<link href="<?=$style;?>" type="text/css" rel="stylesheet"/>
<?php endforeach;?>
</head>
<body class="<?php if(isset($is_index)) { echo 'index';} if(isset($do_fullscreen_header)) { echo 'fullscreen-header';} ?>">
<div class="header">
<div class="header-background">
<img src="<?=($backgrounds[array_rand($backgrounds)]);?>" alt=""/>
</div>
<div class="header-foreground"<?php if(isset($offset) && $offset > 0) echo " style=\"padding-bottom: {$offset}px\""; ?>>
<a class="header-logo" href="/">
<div class="header-flash">flash</div>
<div class="header-wave">wave</div>
</a>
<?php if(!empty($is_index) || !empty($do_fullscreen_header)): ?>
<div class="header-now-playing header-now-playing-hidden">
<div class="header-now-playing-icon" title="Now listening to...">
<a href="/now-listening"><img src="https://mikoto.misaka.nl/i/1594148841-7yAS44p.png" alt=""/></a>
</div>
<div class="header-now-playing-cover">
<img src="//now.flash.moe/resources/no-cover.png" alt=""/>
</div>
<div class="header-now-playing-details">
<div class="header-now-playing-title"><a href="#" target="_blank" rel="noopener"></a></div>
<div class="header-now-playing-artist"><a href="#" target="_blank" rel="noopener"></a></div>
</div>
</div>
<?php else: ?>
<div class="header-menu">
<?php foreach($menu as $link): ?>
<a href="<?=$link['link'];?>"><?=$link['title'];?></a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php if(empty($is_index) && empty($do_fullscreen_header)): ?>
<div class="container">
<?php endif; ?>

28
lib/fmcolour.php Normal file
View file

@ -0,0 +1,28 @@
<?php
class FmColour {
private $raw = null;
public function __construct(?int $raw) {
$this->raw = $raw;
}
public function hasColour(): bool {
return $this->raw !== null;
}
public function getRaw(): int {
return $this->raw ?? 0;
}
public function getRed(): int { return ($this->getRaw() >> 16) & 0xFF; }
public function getGreen(): int { return ($this->getRaw() >> 8) & 0xFF; }
public function getBlue(): int { return $this->getRaw() & 0xFF; }
public function getHex(): string { return str_pad(dechex($this->getRaw()), 6, '0'); }
public function getCSS(): string {
if(!$this->hasColour())
return 'inherit';
return '#' . $this->getHex();
}
}

14
lib/fmlanguage.php Normal file
View file

@ -0,0 +1,14 @@
<?php
class FmLanguage {
private $language_id = -1;
private $language_name = '';
private $language_colour = null;
public static function fromStdClass(stdClass $lang) {
$langRaw = (array)$lang;
$lang = new static;
foreach($langRaw as $prop => $val)
$lang->{$prop} = $val;
return $lang;
}
}

170
lib/fmproject.php Normal file
View file

@ -0,0 +1,170 @@
<?php
class FmProject {
public const TYPE_PROJECT = 'Project';
public const TYPE_TOOL = 'Tool';
private $project_id = -1;
private $project_name = '';
private $project_name_clean = null;
private $project_summary = null;
private $project_description = null;
private $project_order = 0;
private $project_type = self::TYPE_PROJECT;
private $project_featured = 0;
private $project_colour = null;
private $project_homepage = null;
private $project_repository = null;
private $project_forum = null;
private $project_created = null;
private $project_deleted = null;
private $project_archived = null;
private $languages = null;
public function getId(): int {
return $this->project_id < 1 ? -1 : $this->project_id;
}
public function getName(): string {
return $this->project_name;
}
public function getCleanName(): string {
return $this->project_name_clean ?? str_replace(' ', '-', trim(strtolower($this->getName())));
}
public function getSummary(): string {
return $this->project_summary ?? '';
}
public function getDescription(): string {
return $this->project_description ?? '';
}
public function getDescriptionLines(): array {
$linesRaw = array_reverse(explode("\n", $this->getDescription()));
$lines = [];
while(($line = array_pop($linesRaw)) !== false) {
$line = trim($line);
if(!empty($line))
$lines[] = $line;
}
return $lines;
}
public function getOrder(): int {
return $this->project_order;
}
public function getType(): string {
return $this->project_type;
}
public function isProject(): bool {
return $this->getType() === self::TYPE_PROJECT;
}
public function isTool(): bool {
return $this->getType() === self::TYPE_TOOL;
}
public function isFeatured(): bool {
return boolval($this->project_featured);
}
// TODO: INHERIT LANGUAGE COLOUR
public function hasColour(): bool {
return $this->project_colour !== null;
}
public function getColour(): FmColour {
return new FmColour($this->project_colour);
}
public function hasHomepage(): bool {
return !empty($this->project_homepage);
}
public function getHomepage(): string {
return $this->project_homepage ?? '';
}
public function hasRepository(): bool {
return !empty($this->project_repository);
}
public function getRepository(): string {
return $this->project_repository ?? '';
}
public function hasForum(): bool {
return !empty($this->project_forum);
}
public function getForum(): string {
return $this->project_forum ?? '';
}
public function getCreatedTime(): DateTime {
return new DateTime('@' . ($this->project_created ?? 0));
}
public function isDeleted(): bool {
return $this->project_deleted !== null;
}
public function getDeletedTime(): DateTime {
return new DateTime('@' . ($this->project_deleted ?? 0));
}
public function isArchived(): bool {
return $this->project_archived !== null;
}
public function getArchivedTime(): DateTime {
return new DateTime('@' . ($this->project_archived ?? 0));
}
public function getLinks(): array {
$links = [];
if($this->hasHomepage())
$links[] = FmProjectLink::create('homepage', 'Homepage', $this->getHomepage());
if($this->hasRepository())
$links[] = FmProjectLink::create('repository', 'Source', $this->getRepository());
if($this->hasForum())
$links[] = FmProjectLink::create('forum', 'Discussion', $this->getForum());
return $links;
}
public function getLanguages(): array {
return $this->languages ?? [];
}
private function transformLanguages(): void {
if(empty($this->languages))
return;
foreach($this->languages as &$lang)
if($lang instanceof stdClass)
$lang = FmLanguage::fromStdClass($lang);
}
public static function byFeatured(): array {
return cache_output('projects-featured-noconvert', 300, function() {
$projects = json_decode(file_get_contents('https://flash.moe/2020/projects.php?dump_that_shit&noconvert&rf'));
foreach($projects as &$project) {
$projRaw = (array)$project;
$project = new static;
foreach($projRaw as $prop => $val)
$project->{$prop} = $val;
$project->transformLanguages();
}
return $projects;
});
}
public static function all(): array {
return cache_output('projects-noconvert', 300, function() {
$projects = json_decode(file_get_contents('https://flash.moe/2020/projects.php?dump_that_shit&noconvert'));
foreach($projects as &$project) {
$projRaw = (array)$project;
$project = new static;
foreach($projRaw as $prop => $val)
$project->{$prop} = $val;
$project->transformLanguages();
}
return $projects;
});
}
}

24
lib/fmprojectlink.php Normal file
View file

@ -0,0 +1,24 @@
<?php
class FmProjectLink {
private $className;
private $text;
private $url;
public function getClass(): string {
return $this->className;
}
public function getText(): string {
return $this->text;
}
public function getUrl(): string {
return $this->url;
}
public static function create(string $class, string $text, string $url): self {
$link = new static;
$link->className = $class;
$this->text = $text;
$this->url = $url;
return $link;
}
}

42
pages/blog.php Normal file
View file

@ -0,0 +1,42 @@
<?php
if(preg_match('#^/blog/([0-9]+)$#', $reqPath, $matches) || $reqPath === '/blog.php' || $reqPath === '/blog-post.php') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: //flash.moe/2020/blog.php?p=' . ($matches[1] ?? filter_input(INPUT_GET, 'p', FILTER_SANITIZE_NUMBER_INT) ?? ''));
return FM_HIT | 302;
}
if($reqPath === '/blog-elements.json') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Content-Type: application/json; charset=utf-8');
$title = filter_input(INPUT_GET, 'title', FILTER_SANITIZE_STRING);
ob_start();
fm_component('header', [
'title' => $title ?? '%title%',
'external' => true,
'styles' => [
'//blog.flash.moe/assets/blog.css',
],
]);
$header = ob_get_contents();
ob_clean();
fm_component('footer', [
'external' => true,
'scripts' => [
'//blog.flash.moe/assets/blog.js',
],
]);
$footer = ob_get_contents();
ob_end_clean();
echo json_encode([
'header' => $header,
'footer' => $footer,
]);
return FM_HIT;
}

193
pages/contact.php Normal file
View file

@ -0,0 +1,193 @@
<?php
if($reqPath === '/contact.php' || $reqPath === '/contact.html') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /contact');
return FM_HIT | 302;
}
if($reqPath === '/nintendo') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /contact#gaming');
return FM_HIT | 302;
}
if($reqPath === '/now-listening.json') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Content-Type: application/json; charset=utf-8');
$lfmInfo = cache_output('lastfm', 10, function() {
return json_decode(file_get_contents('https://now.flash.moe/get.php?u=flashwave_'));
});
if(empty($lfmInfo[0]->name))
echo '[]';
else {
$lfmInfo = $lfmInfo[0];
echo json_encode([
'name' => strval($lfmInfo->name),
'now_playing' => !empty($lfmInfo->nowplaying),
'url' => strval($lfmInfo->url),
'cover' => !empty($lfmInfo->images->large) ? strval($lfmInfo->images->large) : '',
'artist' => [
'name' => !empty($lfmInfo->artist->name) ? strval($lfmInfo->artist->name) : '',
'url' => explode('/_/', strval($lfmInfo->url))[0],
],
]);
}
return FM_HIT;
}
if($reqPath === '/contact') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
$contact = [
[
'id' => 'contact',
'title' => 'Direct Contact',
'items' => [
[
'id' => 'email',
'name' => 'E-mail',
'display' => 'me@flash.moe',
'link' => 'mailto:me+contact@flash.moe',
],
],
],
[
'id' => 'communities',
'title' => 'Communities & Social Media',
'items' => [
[
'id' => 'flashii',
'name' => 'Flashii',
'display' => 'flash',
'link' => '//flashii.net/profile.php?u=1',
],
[
'id' => 'twitter',
'name' => 'Twitter',
'display' => '@smugwave',
'link' => '//twitter.com/smugwave',
],
[
'id' => 'youtube',
'name' => 'YouTube',
'display' => 'flashwave',
'link' => '//youtube.com/c/flashwave',
],
[
'id' => 'github',
'name' => 'Github',
'display' => 'flashwave',
'link' => '//github.com/flashwave',
],
[
'id' => 'twitch',
'name' => 'Twitch.tv',
'display' => 'flashwave0',
'link' => '//twitch.tv/flashwave0',
],
[
'id' => 'lastfm',
'name' => 'Last.fm',
'display' => 'flashwave_',
'link' => '//last.fm/user/flashwave_',
],
],
],
[
'id' => 'gaming',
'title' => 'Gaming',
'items' => [
[
'id' => 'steam',
'name' => 'Steam',
'display' => 'flashwave_',
'link' => '//steamcommunity.com/id/flashwave_',
],
[
'id' => 'nin-sw',
'name' => 'Nintendo Switch',
'display' => 'SW-7446-8163-4902',
],
[
'id' => 'nin-3ds',
'name' => 'Nintendo 3DS',
'display' => '4013-0352-0648',
],
[
'id' => 'nin-wiiu',
'name' => 'Wii U / NNID',
'display' => 'flashwave0',
],
[
'id' => 'osu',
'name' => 'osu!',
'display' => 'flash',
'link' => '//osu.ppy.sh/u/flash',
],
],
],
[
'id' => 'support',
'title' => 'Support me',
'items' => [
[
'id' => 'paypal',
'name' => 'Paypal Donation',
'display' => 'flashwave',
'link' => '//paypal.me/flashwave',
],
[
'id' => 'patreon',
'name' => 'Patreon',
'display' => 'flashwave',
'link' => '//patreon.com/flashwave',
],
],
],
];
fm_component('header', [
'title' => 'flash.moe / contact',
]);
foreach($contact as $section) {
?>
<div class="section" id="section-<?=$section;?>">
<div class="section-content">
<div class="section-background"></div>
<h1><?=$section['title'];?></h1>
</div>
</div>
<div class="socials">
<?php foreach($section['items'] as $social): ?>
<div class="social social-<?=$social['id'];?>">
<?php if(isset($social['link'])): ?>
<a href="<?=$social['link'];?>" class="social-background" target="_blank" rel="noopener"></a>
<?php else: ?>
<div class="social-background" onclick="fm.selectTextInElement(this.parentNode.querySelector('.social-handle')); fm.copySelectedText();"></div>
<?php endif; ?>
<div class="social-icon"></div>
<div class="social-content">
<div class="social-name"><?=$social['name'];?></div>
<div class="social-handle"><?=$social['display'];?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
}
fm_component('footer');
return FM_HIT;
}

59
pages/etcetera.php Normal file
View file

@ -0,0 +1,59 @@
<?php
if($reqPath === '/etc.php' || $reqPath === 'etc.html'
|| $reqPath === '/etcetera' || $reqPath === '/etcetera.html' || $reqPath === '/etcetera.php'
|| $reqPath === '/misc' || $reqPath === '/misc.html' || $reqPath === '/misc.php') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /etc');
return FM_HIT | 302;
}
if($reqPath === '/etc') {
$links = [
[
'title' => 'ASCII Table',
'desc' => 'ASCII character set table with minor filtering functionality.',
'link' => '//flash.moe/ascii.php',
],
[
'title' => 'MIDI Collection',
'desc' => 'My collection of MIDI and SF2 files.',
'link' => '//flash.moe/midis',
],
[
'title' => 'Randomised Keys',
'desc' => 'Generates a number of random keys at various lengths using a secure random number generator.',
'link' => '//flash.moe/key.php',
],
[
'title' => 'The Abyss',
'desc' => 'Accumulated collection of historical trash. Beware that the things here are unaltered products of their time.',
'link' => '//abyss.flash.moe',
],
];
fm_component('header', [
'title' => 'flash.moe / etcetera',
]);
?>
<div class="section">
<div class="section-content">
<div class="section-background"></div>
<h1>Etcetera</h1>
</div>
</div>
<?php foreach($links as $link): ?>
<div class="etcetera-item">
<div class="etcetera-item-content">
<a href="<?=$link['link'];?>" class="etcetera-item-link" rel="noopener" target="_blank"></a>
<h2><?=$link['title'];?></h2>
<p><?=$link['desc'];?></p>
</div>
</div>
<?php
endforeach;
fm_component('footer');
return FM_HIT;
}

199
pages/index.php Normal file
View file

@ -0,0 +1,199 @@
<?php
if($reqPath === '/about' || $reqPath === '/about.html' || $reqPath === '/about.php'
|| $reqPath === '/index.php' || $reqPath === '/index.html') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /');
return FM_HIT | 302;
}
if($reqPath === '/now-listening') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
$offset = (int)filter_input(INPUT_GET, 'offset', FILTER_SANITIZE_NUMBER_INT);
fm_component('header', [
'title' => 'flash.moe / now listening',
'do_fullscreen_header' => true,
'offset' => $offset,
]);
?>
<script>window.addEventListener('load', function() { window.fm.initIndex(10); });</script>
<?php
fm_component('footer', [
'hide' => true,
]);
return FM_HIT;
}
if($reqPath === '/test') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Content-Type: text/plain');
var_dump(FmProject::byFeatured());
return FM_HIT;
}
if($reqPath === '/') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
$legacyPage = filter_input(INPUT_GET, 'p', FILTER_SANITIZE_STRING);
if(!empty($legacyPage)) {
$legacyPages = [
'projects' => '/projects',
'contact' => '/contact',
'about' => '/',
'etc' => '/etc',
'hosted' => '/etc',
'friends' => '/related',
];
if(isset($legacyPages[$legacyPage])) {
header('Location: ' . $legacyPages[$legacyPage]);
return FM_HIT | 302;
}
}
$blogInfo = cache_output('blog', 600, function() {
return json_decode(file_get_contents('https://flash.moe/2020/?blog_dump'));
});
$projInfo = cache_output('projects-featured', 300, function() {
return json_decode(file_get_contents('https://flash.moe/2020/projects.php?dump_that_shit&rf'));
});
shuffle($projInfo);
$contact = [
[
'id' => 'email',
'name' => 'E-mail',
'display' => 'me@flash.moe',
'link' => 'mailto:me+contact@flash.moe',
],
[
'id' => 'flashii',
'name' => 'Flashii',
'display' => 'flash',
'link' => '//flashii.net/profile.php?u=1',
],
[
'id' => 'twitter',
'name' => 'Twitter',
'display' => '@smugwave',
'link' => '//twitter.com/smugwave',
],
[
'id' => 'github',
'name' => 'Github',
'display' => 'flashwave',
'link' => '//github.com/flashwave',
],
];
fm_component('header', [
'title' => 'flash.moe',
'is_index' => true,
]);
?>
<div class="index-menu">
<?php for($i = 1; $i < count(FM_NAV); ++$i): ?>
<a href="<?=(FM_NAV[$i]['link']);?>"><?=(FM_NAV[$i]['title']);?></a>
<?php endfor; ?>
</div>
<div class="index-featured">
<div class="index-feature">
<div class="index-feature-header">
<a href="//blog.flash.moe" class="index-feature-header-link"></a>
<div class="index-feature-header-title">Blog</div>
<div class="index-feature-header-more">More</div>
</div>
<div class="index-blog">
<?php $blogCount = 0; foreach($blogInfo as $blogPost): if($blogCount++ >= 5) break; ?>
<div class="index-blog-post">
<a href="//flash.moe/blog/<?=$blogPost->post_id;?>" class="index-blog-post-link"></a>
<div class="index-blog-post-header">
<div class="index-blog-post-title"><?=$blogPost->post_title;?></div>
<div class="index-blog-post-published"><time datetime="<?=date('c', $blogPost->post_published);?>" title="<?=date('r', $blogPost->post_published);?>"><?=time_elapsed($blogPost->post_published);?></time></div>
</div>
<div class="index-blog-post-content">
<?=first_paragraph($blogPost->post_text);?>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="index-feature">
<div class="index-feature-header">
<a href="/projects" class="index-feature-header-link"></a>
<div class="index-feature-header-title">Projects</div>
<div class="index-feature-header-more">More</div>
</div>
<?php
$projCount = 0;
foreach($projInfo as $proj):
if($projCount++ >= 3)
break;
$links = [];
if(isset($proj->homepage))
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $proj->homepage];
if(isset($proj->repository))
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $proj->repository];
if(isset($proj->forum))
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $proj->forum];
?>
<div class="index-project" style="background-color: #<?=str_pad(dechex($proj->colour), 6, '0', STR_PAD_LEFT);?>;">
<a href="/projects#<?=$proj->name_clean;?>" class="index-project-anchor"></a>
<div class="index-project-content">
<div class="index-project-name"><?=$proj->name;?></div>
<div class="index-project-summary"><?=$proj->summary;?></div>
</div>
<?php if(!empty($links)): ?>
<div class="index-project-links">
<?php foreach($links as $link): ?>
<a class="index-project-link index-project-link-<?=$link['class'];?>" href="<?=$link['url'];?>" rel="noopener" target="_blank">
<?=$link['text'];?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
<?php endforeach; ?>
</div>
<div class="index-feature">
<div class="index-feature-header">
<a href="/contact" class="index-feature-header-link"></a>
<div class="index-feature-header-title">Contact</div>
<div class="index-feature-header-more">More</div>
</div>
<div class="index-contact">
<?php foreach($contact as $social): ?>
<div class="social social-<?=$social['id'];?>">
<?php if(isset($social['link'])): ?>
<a href="<?=$social['link'];?>" class="social-background" target="_blank" rel="noopener"></a>
<?php else: ?>
<div class="social-background" onclick="fm.selectTextInElement(this.parentNode.querySelector('.social-handle')); fm.copySelectedText();"></div>
<?php endif; ?>
<div class="social-icon"></div>
<div class="social-content">
<div class="social-name"><?=$social['name'];?></div>
<div class="social-handle"><?=$social['display'];?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<script>window.addEventListener('load', function() { window.fm.initIndex(); });</script>
<?php
fm_component('footer', [
'is_index' => true,
]);
return FM_HIT;
}

91
pages/projects.php Normal file
View file

@ -0,0 +1,91 @@
<?php
if($reqPath === '/projects.php' || $reqPath === '/projects.html') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /projects');
return FM_HIT | 302;
}
if($reqPath === '/projects') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
$projInfo = cache_output('projects', 300, function() {
return json_decode(file_get_contents('https://flash.moe/2020/projects.php?dump_that_shit'));
});
fm_component('header', [
'title' => 'flash.moe / projects',
]);
$sectionInfos = [
'project' => [
'title' => 'Active Projects',
'desc' => 'Projects that I work on on a fairly regular basis.',
],
'tool' => [
'title' => 'Tools',
'desc' => 'Personal quality of life tools that I update when I need something new from them.',
],
'archive' => [
'title' => 'Archived Projects',
'desc' => 'Past projects that I no longer work on.',
],
];
foreach($projInfo as $section => $projects):
$sectionInfo = $sectionInfos[$section];
?>
<div class="section" id="section-<?=$section;?>">
<div class="section-content">
<div class="section-background"></div>
<h1><?=$sectionInfo['title'];?></h1>
<p><?=$sectionInfo['desc'];?></p>
</div>
</div>
<?php
foreach($projects as $proj):
$links = [];
if(isset($proj->homepage))
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $proj->homepage];
if(isset($proj->repository))
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $proj->repository];
if(isset($proj->forum))
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $proj->forum];
$descLines = explode("\n", trim($proj->description ?? ''));
?>
<div class="project project-type-<?=$section;?>" id="<?=$proj->name_clean;?>" style="--project-colour: #<?=str_pad(dechex($proj->colour), 6, '0', STR_PAD_LEFT);?>;">
<div class="project-content">
<div class="project-details">
<h2><?=$proj->name;?><div class="project-languages">
<?php foreach($proj->languages as $lang): ?>
<div class="project-language" style="--language-colour: #<?=str_pad(dechex($lang->colour), 6, '0', STR_PAD_LEFT);?>;">
<?=$lang->name;?>
</div>
<?php endforeach; ?>
</div></h2>
<p class="project-details-summary"><?=$proj->summary;?></p>
<?php foreach($descLines as $line): if(empty($line)) continue; ?>
<p><?=trim($line);?></p>
<?php endforeach; ?>
</div>
<?php if(!empty($links)): ?>
<div class="project-links">
<?php foreach($links as $link): ?>
<a class="project-link project-link-<?=$link['class'];?>" href="<?=$link['url'];?>" rel="noopener" target="_blank">
<?=$link['text'];?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php
endforeach;
endforeach;
fm_component('footer');
return FM_HIT;
}

44
pages/related.php Normal file
View file

@ -0,0 +1,44 @@
<?php
if($reqPath === '/related.php' || $reqPath === '/related.html'
|| $reqPath === '/friends' || $reqPath === '/friends.html' || $reqPath === '/friends.php') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
header('Location: /related');
return FM_HIT | 302;
}
if($reqPath === '/related') {
if($reqMethod !== 'GET')
return FM_ERR | 405;
fm_component('header', [
'title' => 'flash.moe / related',
]);
?>
<div class="section">
<div class="section-content">
<div class="section-background"></div>
<h1>Related Sites</h1>
</div>
</div>
<div class="related-sites">
<div class="related-site">
<a href="https://wart.host/" class="related-site-link"></a>
<div class="related-site-image">
<img src="//wart.host/Vintage/assets/cat.gif" alt=""/>
</div>
<div class="related-site-content">
<div class="related-site-title">
wart host
</div>
<div class="related-site-desc">
screenshot palace of my momther
</div>
</div>
</div>
</div>
<?php
fm_component('footer');
return FM_HIT;
}

676
public/assets/2020v2.css Normal file
View file

@ -0,0 +1,676 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
position: relative;
outline-style: none;
user-select: none;
}
html, body {
width: 100%;
height: 100%;
}
html {
scrollbar-color: #4a3650 #111;
}
/* an attempt to replicate scrollbar-color for chromosome */
::-webkit-scrollbar {
width: 6px;
background-color: #111;
}
::-webkit-scrollbar-thumb {
background-color: #4a3650;
}
::-webkit-scrollbar-thumb:hover {
background-color: #5b4761;
}
::-webkit-scrollbar-thumb:active {
background-color: #6c5872;
}
body {
background-color: #111;
color: #fff;
font: 12px/20px Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
display: flex;
flex-direction: column;
}
.header, .footer { flex: 0 0 auto; }
.container { flex: 1 1 auto; }
.header {
width: 100%;
height: 200px;
overflow: hidden;
}
@media (max-width: 700px) {
.header { height: auto; }
}
.index .header {
height: 50vh;
}
.fullscreen-header .header {
height: 100%;
}
.header-background {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
overflow: hidden;
}
.header-background img {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
filter: blur(20px) brightness(80%);
transform: scale(1.2);
opacity: 1;
transition: opacity .5s;
}
.header-foreground {
width: 100%;
height: 100%;
display: flex;
max-width: 1200px;
margin: 0 auto;
justify-content: space-between;
align-items: flex-end;
padding: 10px;
}
@media (max-width: 700px) {
.header-foreground {
justify-content: center;
align-items: center;
flex-direction: column;
}
}
.index .header-foreground {
align-items: center;
justify-content: flex-end;
flex-direction: column;
}
.header-logo {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 3em;
line-height: 1.2em;
filter: drop-shadow(0 1px 5px #000);
color: transparent;
text-decoration: none;
display: flex;
flex: 0 0 auto;
}
.index .header-logo {
font-size: 5em;
}
.header-flash {
background-image: linear-gradient(180deg, #eee 0%, #ddd 50%, #ccc 50%, #aaa 100%);
background-clip: text;
-webkit-background-clip: text;
}
.header-wave {
background-image: linear-gradient(0deg, #281430 0%, #392540 50%, #4a3650 50%, #6c5871 100%);
-webkit-background-clip: text;
}
.header-menu {
display: flex;
justify-content: center;
flex: 0 0 auto;
}
.header-menu a {
display: block;
color: #fff;
text-decoration: none;
font-size: 1.5em;
line-height: 1.2em;
margin: 0 2px;
padding: 5px 10px;
border-radius: 5px;
text-shadow: 0 1px 5px #000;
transition: background-color .1s;
}
.header-menu a:hover,
.header-menu a:focus {
background-color: rgba(255, 255, 255, .1);
}
.header-menu a:active {
background-color: rgba(127, 127, 127, .1);
}
@media (max-width: 800px) {
.header-menu {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
}
.header-menu a {
text-align: center;
min-width: 150px;
margin: 2px;
padding: 10px 0;
}
.header-menu :first-child {
display: none;
}
}
@media (max-width: 480px) {
.header-menu { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 320px) {
.header-menu { grid-template-columns: 1fr; }
}
.footer {}
.footer-text {
opacity: .2;
font-size: .9em;
text-align: center;
max-width: 1200px;
margin: 5px auto;
}
.index {
display: flex;
flex-direction: column;
height: 100%;
max-height: 100vh;
overflow: hidden;
}
@media(max-width: 900px) { .index {
max-height: initial;
overflow: auto;
} }
.index-menu {
width: 100%;
display: flex;
justify-content: center;
background-image: linear-gradient(0deg, #111 0%, #222 50%, #333 50%, #555 100%);
flex: 0 0 auto;
box-shadow: 0 0 1em rgba(0, 0, 0, .8);
}
.index-menu a {
display: block;
color: #fff;
text-decoration: none;
font-size: 1.5em;
line-height: 1.2em;
padding: 5px 10px;
transition: background-color .1s;
}
.index-menu a:hover,
.index-menu a:focus {
background-color: rgba(255, 255, 255, .1);
}
.index-menu a:active {
background-color: rgba(127, 127, 127, .1);
}
@media (max-width: 600px) {
.index-menu {
flex-wrap: wrap;
}
.index-menu a {
min-width: 200px;
margin: 2px;
padding: 10px;
text-align: center;
}
}
.socials {
max-width: 1100px;
width: 100%;
margin: 10px auto;
padding: 0 15px;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
justify-content: space-evenly;
column-gap: 10px;
}
@media(max-width: 980px) { .socials { grid-template-columns: 1fr 1fr; } }
@media(max-width: 640px) { .socials { grid-template-columns: 1fr; } }
.social {
width: 100%;
margin: 5px 0;
filter: drop-shadow(0 1px 5px #000);
cursor: pointer;
display: flex;
align-items: center;
padding: 5px;
}
.index-contact .social {
margin: 10px 0;
}
.social-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(0deg, #1118 0%, #2228 50%, #3338 50%, #5558 100%);
transform: skew(-15deg);
}
.social:active .social-background {
background-image: linear-gradient(0deg, #1118 0%, #2228 50%, #3338 50%, #3338 100%);
}
.social-icon {
width: 25px;
height: 25px;
background-size: 25px 25px;
margin: 3px 4px 2px 8px;
pointer-events: none;
}
.social-content {
margin: 2px 5px;
pointer-events: none;
}
.social-name {
font-size: .9em;
line-height: 1.5em;
}
.social-handle {
font-size: 1.5em;
line-height: 1.3em;
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
user-select: text;
}
.social-twitter .social-icon { background-image: url('//flash.moe/assets/icons/twitter.png'); }
.social-twitter .social-background { background-color: #1da1f2; }
.social-youtube .social-icon { background-image: url('//flash.moe/assets/icons/youtube.png'); }
.social-youtube .social-background { background-color: #ff0000; }
.social-flashii .social-icon { background-image: url('//flash.moe/assets/icons/flashii.png'); }
.social-flashii .social-background { background-color: #8559a5; }
.social-github .social-icon { background-image: url('//flash.moe/assets/icons/github.png'); }
.social-github .social-background { background-color: #222222; }
.social-twitch .social-icon { background-image: url('//flash.moe/assets/icons/twitch.png'); }
.social-twitch .social-background { background-color: #6441a4; }
.social-steam .social-icon { background-image: url('//flash.moe/assets/icons/steam.png'); }
.social-steam .social-background { background-color: #2c2e35; }
.social-osu .social-icon { background-image: url('//flash.moe/assets/icons/steam.png'); }
.social-osu .social-background { background-color: #ff66aa; }
.social-email .social-icon { background-image: url('//flash.moe/assets/icons/email.png'); }
.social-email .social-background { background-color: #4a3650; }
.social-lastfm .social-icon { background-image: url('//flash.moe/assets/icons/lastfm.png'); }
.social-lastfm .social-background { background-color: #ba0000; }
.social-nin-sw .social-icon { background-image: url('//flash.moe/assets/icons/ninswitch.png'); }
.social-nin-sw .social-background { background-color: #e60012; }
.social-nin-3ds .social-icon { background-image: url('//flash.moe/assets/icons/ninswitch.png'); }
.social-nin-3ds .social-background { background-color: #ce171f; }
.social-nin-wiiu .social-icon { background-image: url('//flash.moe/assets/icons/ninswitch.png'); }
.social-nin-wiiu .social-background { background-color: #00acca; }
.social-paypal .social-icon { background-image: url('//flash.moe/assets/icons/paypal.png'); }
.social-paypal .social-background { background-color: #009cde; }
.social-patreon .social-icon { background-image: url('//flash.moe/assets/icons/paypal.png'); }
.social-patreon .social-background { background-color: #f86754; }
.index-featured {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
flex: 1 1 auto;
overflow: hidden;
margin: 5px auto;
padding: 0 10px;
margin-bottom: 0;
column-gap: 10px;
row-gap: 10px;
width: 100%;
max-width: 1500px;
}
@media(max-width: 900px) { .index-featured { grid-template-columns: 1fr; overflow: visible; } }
.index-feature {
overflow: auto;
scrollbar-width: thin;
}
.index-feature-header {
border-bottom: 1px solid #333;
display: flex;
align-items: center;
}
.index-feature-header-link {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.index-feature-header-title {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 2em;
line-height: 1.5em;
pointer-events: none;
flex: 1 1 auto;
}
.index-feature-header-more {
pointer-events: none;
flex: 0 0 auto;
margin: 0 5px;
padding: 2px 8px;
border-radius: 2em;
font-size: .9em;
line-height: 1.5em;
background-color: #1a1a1a;
transition: background-color .2s;
}
.index-feature-header-more::after {
content: " »";
}
.index-feature-header:focus-within .index-feature-header-more,
.index-feature-header:hover .index-feature-header-more,
.index-feature-header-more:focus {
background-color: #2a2a2a;
}
.index-feature-header:active .index-feature-header-more,
.index-feature-header-more:active {
background-color: #222;
}
.index-contact {
padding: 5px 15px;
overflow: auto;
}
.index-blog {}
.index-blog-post {
margin: 5px;
padding: 2px 5px;
background: #222;
box-shadow: 0 2px 5px #000;
}
.index-blog-post-link {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.index-blog-post-header {
display: flex;
align-items: center;
pointer-events: none;
border-bottom: 1px solid #333;
}
.index-blog-post-title {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
flex: 1 1 auto;
font-size: 1.5em;
line-height: 1.5em;
}
.index-blog-post-published {
flex: 0 1 auto;
font-size: .9em;
line-height: 1.5em;
opacity: .5;
padding-bottom: 2px;
}
.index-blog-post-content {
pointer-events: none;
max-height: 70px;
overflow: hidden;
}
.index-blog-post-content a {
pointer-events: initial;
color: inherit;
text-decoration: underline;
}
.header-now-playing {
max-height: 60px;
min-width: 300px;
max-width: 500px;
height: 100%;
background-image: linear-gradient(0deg, #111c 0%, #222c 50%, #333c 50%, #555c 100%);
box-shadow: 0 2px 1em #000;
border-radius: 5px;
overflow: hidden;
align-items: center;
bottom: 0;
padding: 5px;
display: grid;
grid-template-columns: 25px 50px 1fr;
column-gap: 5px;
transition: bottom .5s, width .2s, max-height .5s, padding .2s;
}
.header-now-playing-hidden {
bottom: -80px;
max-height: 0;
padding: 0;
}
.header-now-playing-icon img {
vertical-align: middle;
}
.header-now-playing-cover {
width: 50px;
height: 50px;
}
.header-now-playing-cover img {
width: 100%;
height: 100%;
vertical-align: middle;
object-fit: cover;
}
.header-now-playing-details {
overflow: hidden;
white-space: nowrap;
}
.header-now-playing-details a {
color: #fff;
text-decoration: none;
transition: width .2s;
}
.header-now-playing-details a:hover,
.header-now-playing-details a:focus {
text-decoration: underline;
}
.header-now-playing-title {
font-size: 1.2em;
}
.header-now-playing-title,
.header-now-playing-artist {
overflow: hidden;
text-overflow: ellipsis;
}
.index-project {
margin: 5px;
background-image: linear-gradient(180deg, #555c 0, #333c 32px, #222c 32px, #111c 100%);
box-shadow: 0 2px 5px #000;
border-radius: 5px;
overflow: hidden;
}
.index-project-anchor {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.index-project-content {
margin: 5px 8px;
margin-bottom: 0;
pointer-events: none;
}
.index-project-name {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
flex: 1 1 auto;
font-size: 1.5em;
line-height: 1.5em;
}
.index-project-summary {
margin-top: 2px;
}
.index-project-links {
display: flex;
pointer-events: none;
margin: 0 3px;
}
.index-project-link {
pointer-events: initial;
margin: 4px 1px;
color: #fff;
text-decoration: none;
min-width: 100px;
padding: 2px 8px;
border-radius: 4px;
transition: background-color .2s;
}
.index-project-link:hover,
.index-project-link:focus {
background-color: #fff2;
}
.index-project-link:active {
background-color: #fff1;
}
.section {
padding: 0 15px;
}
.section:not(:first-child) {
margin-top: 30px;
}
.section-content {
max-width: 1100px;
margin: 10px auto;
padding: 10px 20px;
filter: drop-shadow(0 1px 5px #000);
}
.section-background {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(0deg, #111 0%, #222 50%, #333 50%, #555 100%);
transform: skew(-15deg);
}
.section-content h1 {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 2em;
line-height: 1em;
font-weight: 400;
}
.project {
padding: 0 10px;
/*background-color: var(--project-colour);
background-image: linear-gradient(#111e, #111e);
overflow: hidden;*/
}
.project-content {
max-width: 1100px;
width: 100%;
margin: 1em auto;
overflow: hidden;
background-color: var(--project-colour);
background-image: linear-gradient(180deg, #555c 0, #333c 38px, #222c 38px, #111c 100%);
box-shadow: 0 2px 1em #000;
border-radius: 5px;
}
.project-languages {
font-size: 0;
line-height: 0;
display: inline-block;
font-family: Tahoma, Geneva, 'Dejavu Sans', Arial, Helvetica, sans-serif;
margin-left: 4px;
}
.project-language {
font-size: 11px;
line-height: 18px;
display: inline-block;
border-left: 4px solid var(--language-colour);
background-color: var(--language-colour);
background-image: linear-gradient(90deg, #1118, #111a);
border-radius: 4px;
overflow: hidden;
padding: 0 4px;
margin: 0 4px;
box-shadow: 0 0 1px var(--language-colour);
}
.project-details {
margin: 10px;
margin-bottom: 0;
}
.project-details h2 {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 2em;
line-height: 1em;
font-weight: 400;
margin-bottom: 5px;
}
.project-details p {
font-size: .9em;
line-height: 1.8em;
}
.project-details .project-details-summary {
font-size: 1.2em;
line-height: 1.5em;
}
.project-links {
display: flex;
margin: 0 3px;
}
.project-link {
margin: 4px 1px;
color: #fff;
text-decoration: none;
min-width: 100px;
padding: 2px 8px;
border-radius: 4px;
transition: background-color .2s;
}
.project-link:hover,
.project-link:active {
background-color: #fff2;
}
.project-link:focus {
background-color: #fff1;
}
.etcetera-item {
max-width: 1100px;
margin: 10px auto;
padding: 0 15px;
filter: drop-shadow(0 1px 5px #000);
}
.etcetera-item-link {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #393939;
background-image: linear-gradient(0deg, #1118 0%, #2228 50%, #3338 50%, #5558 100%);
border-radius: 5px;
overflow: hidden;
transition: background-color .2s;
}
.etcetera-item-link:hover,
.etcetera-item-link:focus {
background-color: #555;
}
.etcetera-item-link:active {
background-color: #222;
}
.etcetera-item-content {
max-width: 1100px;
margin: 0 auto;
padding: 10px 12px;
}
.etcetera-item-content h2 {
font-family: 'Electrolize', Verdana, 'Dejavu Sans', Arial, Helvetica, sans-serif;
font-size: 1.5em;
line-height: 1.2em;
font-weight: 400;
pointer-events: none;
}
.etcetera-item-content p {
font-size: .9em;
line-height: 1.4em;
pointer-events: none;
}

113
public/assets/2020v2.js Normal file
View file

@ -0,0 +1,113 @@
window.fm = (function() {
this.headerBackground = null;
this.originalHeaderBackground = null;
this.defaultCoverImage = 'https://lastfm.freetls.fastly.net/i/u/174s/2a96cbd8b46e442fc41c2b86b821562f.png';
this.indexPlayingContainer = null;
this.indexPlayingCover = null;
this.indexPlayingTitle = null;
this.indexPlayingArtist = null;
this.selectTextInElement = function(elem) {
// MSIE
if(document.body.createTextRange) {
var range = document.body.createTextRange();
range.moveToElementText(elem);
range.select();
return;
}
// Mozilla
if(window.getSelection) {
var select = window.getSelection(),
range = document.createRange();
range.selectNodeContents(elem);
select.removeAllRanges();
select.addRange(range);
return;
}
console.warn('Unable to select text.');
};
this.copySelectedText = function() {
if(document.execCommand) {
document.execCommand('copy');
return;
}
console.warn('Unable to copy text.');
};
this.getNowListening = function(callback) {
if(!callback)
return;
var xhr = new XMLHttpRequest;
xhr.onreadystatechange = function() {
if(xhr.readyState !== 4 || xhr.status !== 200)
return;
callback.call(this, JSON.parse(xhr.responseText));
}.bind(this);
xhr.open('GET', '/now-listening.json');
xhr.send();
};
this.updateIndexNowListening = function() {
window.fm.getNowListening(function(info) {
this.indexPlayingContainer.classList[info.now_playing ? 'remove' : 'add']('header-now-playing-hidden');
this.indexPlayingCover.alt = this.indexPlayingCover.src = (info.cover !== this.defaultCoverImage ? info.cover : '//now.flash.moe/resources/no-cover.png');
this.indexPlayingTitle.textContent = this.indexPlayingTitle.title = info.name;
this.indexPlayingTitle.href = info.url;
this.indexPlayingArtist.textContent = this.indexPlayingArtist.title = info.artist.name;
this.indexPlayingArtist.href = info.artist.url;
this.switchHeaderBackground(
info.now_playing && info.cover !== this.defaultCoverImage
? this.indexPlayingCover.src
: this.originalHeaderBackground
);
});
};
this.getCurrentHeaderBackground = function() {
return this.headerBackground.querySelector('img').src;
};
this.switchHeaderBackground = function(url) {
if(this.getCurrentHeaderBackground() === url)
return;
var newImg = document.createElement('img'),
oldImg = this.headerBackground.querySelector('img');
newImg.alt = newImg.src = url;
newImg.style.opacity = '0';
oldImg.style.zIndex = '-1';
newImg.style.zIndex = '0';
this.headerBackground.appendChild(newImg);
newImg.onload = function() {
setTimeout(function() {
newImg.style.opacity = null;
setTimeout(function() {
newImg.style.zIndex = null;
this.headerBackground.removeChild(oldImg);
}.bind(this), 500);
}.bind(this), 50);
}.bind(this);
newImg.onerror = function() {
this.switchHeaderBackground(this.originalHeaderBackground);
}.bind(this);
};
this.initIndex = function(npInterval) {
this.headerBackground = document.querySelector('.header-background');
this.originalHeaderBackground = this.headerBackground.querySelector('img').src;
this.indexPlayingContainer = document.querySelector('.header-now-playing');
this.indexPlayingCover = window.fm.indexPlayingContainer.querySelector('.header-now-playing-cover img');
this.indexPlayingCover.onerror = function() {
this.indexPlayingCover.src = '//now.flash.moe/resources/no-cover.png';
}.bind(this);
this.indexPlayingTitle = window.fm.indexPlayingContainer.querySelector('.header-now-playing-title a');
this.indexPlayingArtist = window.fm.indexPlayingContainer.querySelector('.header-now-playing-artist a');
this.updateIndexNowListening();
setInterval(this.updateIndexNowListening, (npInterval || 30) * 1000);
};
return this;
}).call(window.fm || {});

127
public/index.php Normal file
View file

@ -0,0 +1,127 @@
<?php
// TODO FOR LAUNCH
// - Make a new list of headers, make sure they're small in file size
// - Make icons
// TODO deferred
// - Make related page
// Todo ultra-deferred
// - Make blog script
define('FM_HIT', 0x01000000);
define('FM_ERR', 0x02000000);
define('FM_NAV', [
['title' => 'Home', 'link' => '/'],
//['title' => 'Blog', 'link' => '//blog.flash.moe'],
['title' => 'Blog', 'link' => '//flash.moe/2020/?blog=1'],
['title' => 'Projects', 'link' => '/projects'],
['title' => 'Contact', 'link' => '/contact'],
//['title' => 'Related', 'link' => '/related'],
['title' => 'Etcetera', 'link' => '/etc'],
['title' => 'Forum', 'link' => '//forum.flash.moe'],
]);
define('FM_BGS', [
'//mikoto.misaka.nl/i/Konachan.com%20-%20164484%20blush%20brown_eyes%20brown_hair%20drink%20misaka_mikoto%20seifuku%20short_hair%20skirt%20socks%20to_aru_kagaku_no_railgun%20to_aru_majutsu_no_index.png',
'//mikoto.misaka.nl/i/misaka%202.png',
'//mikoto.misaka.nl/i/misaka.jpg',
'//mikoto.misaka.nl/i/Drl8DEPWwAAPniH.jpg',
'//mikoto.misaka.nl/i/65776322_p2_master1200.jpg',
'//mikoto.misaka.nl/i/52666925_p0.jpg',
'//mikoto.misaka.nl/i/44161767_p0.jpg',
'//mikoto.misaka.nl/i/32809110_p0.jpg',
'//mikoto.misaka.nl/i/1551886307-ZEgXIk2.jpg',
'//mikoto.misaka.nl/i/1551886971-MgnvSNG.jpg',
'//mikoto.misaka.nl/i/1551881563-wR2Dcx8.jpg',
'//mikoto.misaka.nl/i/1552084409-BsTLlKg.jpg',
]);
define('FM_FEET', [
'if it ain\'t broke, i\'ll break it',
]);
define('FM_ERRS' , [
403 => ['code' => 403, 'title' => 'Access Denied'],
404 => ['code' => 404, 'title' => 'Not Found'],
405 => ['code' => 405, 'title' => 'Method Not Supported'],
]);
function fm_component(string $name, array $vars = []) {
extract($vars);
require __DIR__ . '/../components/' . $name . '.php';
}
function first_paragraph(string $text, string $delimiter = "\n"): string {
$index = mb_strpos($text, $delimiter);
return $index === false ? $text : mb_substr($text, 0, $index);
}
function time_elapsed(int $since, bool $full = false): string {
$now = new DateTime;
$since = new DateTime(date('c', $since));
$diff = $now->diff($since);
$diff->w = floor($diff->d / 7);
$diff->d -= $diff->w * 7;
$string = [
'y' => 'year',
'm' => 'month',
'w' => 'week',
'd' => 'day',
'h' => 'hour',
'i' => 'minute',
's' => 'second',
];
foreach($string as $key => &$value) {
if($diff->{$key})
$value = $diff->{$key} . ' ' . $value . ($diff->{$key} > 1 ? 's' : '');
else
unset($string[$key]);
}
if(!$full)
$string = array_slice($string, 0, 1);
return $string ? implode(', ', $string) . ' ago' : 'just now';
}
function cache_output(string $name, int $lifetime, callable $callable) {
$path = sys_get_temp_dir() . '/fm-' . $name . '.cache';
if(!is_file($path) || (filemtime($path) + $lifetime) < time())
file_put_contents($path, serialize($callable()));
return unserialize(file_get_contents($path));
}
set_include_path(realpath(__DIR__ . '/../lib/') . PATH_SEPARATOR . get_include_path());
spl_autoload_extensions('.php');
spl_autoload_register();
ob_start();
$reqMethod = filter_input(INPUT_SERVER, 'REQUEST_METHOD', FILTER_SANITIZE_STRING);
$reqPath = '/' . trim(parse_url(filter_input(INPUT_SERVER, 'REQUEST_URI', FILTER_SANITIZE_STRING), PHP_URL_PATH), '/');
if(substr($reqPath, 0, 7) === '/error/') {
$statusCode = intval(substr($reqPath, 8, 3));
} else {
foreach(glob(__DIR__ . '/../pages/*.php') as $page) {
$result = include_once $page;
$statusCode = $result & 0xFFF;
if(($result & FM_HIT) === FM_HIT) {
if($statusCode >= 100 && $statusCode <= 999)
http_response_code($statusCode);
return;
}
if(($result & FM_ERR) === FM_ERR)
break;
}
}
$errorInfo = FM_ERRS[$statusCode ?? 404] ?? FM_ERRS[404];
http_response_code($errorInfo['code']);
fm_component('header', ['title' => $errorInfo['title']]);
printf(" <h2>%s</h2>\r\n", $errorInfo['title']);
fm_component('footer');