Massive templating system and router overhaul.

This commit is contained in:
flash 2022-09-26 23:06:12 +00:00
parent 9781827778
commit 09399a411b
24 changed files with 854 additions and 792 deletions

View file

@ -1,19 +0,0 @@
<?php
$quotes = isset($quotes) && is_array($quotes) ? $quotes : FM_FEET;
$baseUrl = empty($external) ? '' : '//' . $_SERVER['HTTP_HOST'];
?>
</div>
<div class="footer">
<div class="footer-text">&copy; flashwave 2010-<?=date('Y');?> - <?=($quotes[array_rand($quotes)]);?></div>
</div>
<?php if(isset($onload) && is_array($onload)): ?>
<script type="text/javascript">
window.fm = { onload: <?=json_encode($onload);?> };
</script>
<?php endif; ?>
<script src="<?=$baseUrl;?>/assets/2021.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>

View file

@ -18,6 +18,7 @@ define('MKI_DIR_LIB', MKI_ROOT . '/lib');
define('MKI_DIR_PUB', MKI_ROOT . '/public');
define('MKI_DIR_PAGES', MKI_ROOT . '/pages');
define('MKI_DIR_CONFIG', MKI_ROOT . '/config');
define('MKI_DIR_TEMPLATES', MKI_ROOT . '/tpl');
require_once MKI_DIR_LIB . '/index-new/index.php';
@ -47,3 +48,5 @@ if(!empty($dbConfig))
if(empty($db))
$db = (new NullDbBackend)->createConnection(new NullDbConnectionInfo);
$ctx = new MakaiContext($db);

View file

@ -1,50 +0,0 @@
<?php
namespace Makai;
$router->get('/contact.php', mkiRedirect('/contact'));
$router->get('/contact.html', mkiRedirect('/contact'));
$router->get('/nintendo', mkiRedirect('/contact'));
$router->get('/nintendo.php', mkiRedirect('/contact'));
$router->get('/contact', function() use ($db) {
$contacts = (new Contacts($db))->getAll();
$body = fm_component('header', [
'title' => 'flash.moe / contact',
]);
$body .= <<<HTML
<div class="section">
<div class="section-content">
<div class="section-background"></div>
<h1>Contacts</h1>
</div>
</div>
<div class="socials">
HTML;
foreach($contacts as $contact) {
$body .= '<div class="social social-' . $contact->getName() . '" style="--social-colour: ' . $contact->getColourHex() . '">';
if($contact->hasLink()) {
$body .= '<a href="' . $contact->getLink() . '" class="social-background" target="_blank" rel="noopener"></a>';
} else {
$body .= '<div class="social-background" onclick="fm.selectTextInElement(this.parentNode.querySelector(\'.social-handle\')); fm.copySelectedText();"></div>';
}
$body .= <<<HTML
<div class="social-icon {$contact->getIcon()}"></div>
<div class="social-content">
<div class="social-name">{$contact->getName()}</div>
<div class="social-handle">{$contact->getDisplay()}</div>
</div>
</div>
HTML;
}
$body .= '</div>';
$body .= fm_component('footer');
return $body;
});

View file

@ -1,270 +0,0 @@
<?php
namespace Makai;
$router->get('/about', mkiRedirect('/'));
$router->get('/about.html', mkiRedirect('/'));
$router->get('/about.php', mkiRedirect('/'));
$router->get('/index.php', mkiRedirect('/'));
$router->get('/index.html', mkiRedirect('/'));
$router->get('/related.php', mkiRedirect('/'));
$router->get('/related.html', mkiRedirect('/'));
$router->get('/friends', mkiRedirect('/'));
$router->get('/friends.php', mkiRedirect('/'));
$router->get('/friends.html', mkiRedirect('/'));
$router->get('/related', mkiRedirect('/'));
$router->get('/etc.php', mkiRedirect('/'));
$router->get('/etc.html', mkiRedirect('/'));
$router->get('/etcetera', mkiRedirect('/'));
$router->get('/etcetera.html', mkiRedirect('/'));
$router->get('/etcetera.php', mkiRedirect('/'));
$router->get('/misc', mkiRedirect('/'));
$router->get('/misc.html', mkiRedirect('/'));
$router->get('/misc.php', mkiRedirect('/'));
$router->get('/etc', mkiRedirect('/'));
$router->get('/365', mkiRedirect('/'));
$router->get('/donate', mkiRedirect('/'));
$router->get('/blog.php', mkiRedirect('/'));
$router->get('/blog-post.php', mkiRedirect('/'));
$router->get('/blog/:id', mkiRedirect('/'));
$router->get('/old-blog', mkiRedirect('/'));
$router->get('/old-blog/:id', mkiRedirect('/'));
$router->get('/header-bgs.json', function() {
return json_encode(FM_BGS);
});
$router->get('/now-listening', function() {
$offset = (int)filter_input(INPUT_GET, 'offset', FILTER_SANITIZE_NUMBER_INT);
$body = fm_component('header', [
'title' => 'flash.moe / now listening',
'do_fullscreen_header' => true,
'is_now_playing' => true,
'offset' => $offset,
]);
$body .= fm_component('footer', [
'hide' => true,
'onload' => [
['fm.initIndex', 10],
],
]);
return $body;
});
$router->get('/now-listening.json', function() {
$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))
return [];
$lfmInfo = $lfmInfo[0];
return [
'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],
],
];
});
$router->get('/np.php', function() {
header('Content-Type: text/xml');
return cache_output('lastfm-xml', 10, function() {
return file_get_contents('https://now.flash.moe/get.php?u=flashwave_&f=xml');
});
});
$router->get('/home', function() {
$body = fm_component('header', [
'title' => 'flash.moe / homepage',
'do_fullscreen_header' => true,
]);
$body .= <<<HTML
<div class="php">
<div class="php-time">
<div class="php-time-analog">
<div class="clock">
<div class="clock-background"></div>
<div class="clock-center"></div>
<div class="clock-hand clock-hand-hours"><div class="clock-hand-display"></div></div>
<div class="clock-hand clock-hand-minutes"><div class="clock-hand-display"></div></div>
<div class="clock-hand clock-hand-seconds"><div class="clock-hand-display"></div></div>
<div class="clock-number clock-number-3"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-6"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-9"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-12"><div class="clock-number-display"></div></div>
</div>
</div>
<div class="php-time-alter">
<div class="php-time-digital">
<div class="php-time-digital-hours">--</div>
<div class="php-time-digital-separator">:</div>
<div class="php-time-digital-minutes">--</div>
</div>
<div class="php-time-date">
<span class="php-date-label">Week</span>
<span class="php-date-week">--</span>
<span class="php-date-label">&nbsp;&mdash;&nbsp;</span>
<span class="php-date-year">----</span><span class="php-date-label">-</span><span class="php-date-month">---</span><span class="php-date-label">-</span><span class="php-date-day">--</span>
</div>
</div>
</div>
<form class="php-search" method="get" action="https://duckduckgo.com/">
<div class="php-search-input">
<input type="search" name="q" placeholder="Search using DuckDuckGo..."/>
</div>
</form>
</div>
HTML;
$body .= fm_component('footer', [
'hide' => true,
'skip_analytics' => true,
'onload' => [
['fm.initClock'],
['fm.initIndex', 10],
],
]);
return $body;
});
$router->get('/', function() use ($db) {
$legacyPage = (string)filter_input(INPUT_GET, 'p');
if(!empty($legacyPage)) {
$legacyPages = [
'projects' => '/projects',
'contact' => '/contact',
'about' => '/',
'etc' => '/etc',
'hosted' => '/etc',
'friends' => '/related',
];
if(isset($legacyPages[$legacyPage])) {
header('Location: ' . $legacyPages[$legacyPage]);
return 302;
}
}
$projects = (new Projects($db))->getFeatured();
$languages = new Languages($db);
$contacts = (new Contacts($db))->getHomePage();
$body = fm_component('header', [
'title' => 'flash.moe',
'is_index' => true,
]);
$body .= '<div class="index-menu">';
for($i = 1; $i < count(FM_NAV); ++$i) {
$link = FM_NAV[$i];
$body .= "<a href=\"{$link['link']}\"";
if($link['link'][0] === '/' && substr($link['link'], 0, 2) !== '//')
$body .= '';
$body .= ">{$link['title']}</a>";
}
$body .= <<<HTML
</div>
<div class="index-featured">
HTML;
$body .= <<<HTML
<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>
HTML;
foreach($projects as $project) {
$links = [];
if($project->hasHomePageUrl())
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()];
if($project->hasSourceUrl())
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()];
if($project->hasDiscussionUrl())
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()];
$colour = $project->hasColour() ? $project->getColour() : $languages->getProjectColour($project);
$colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT);
$body .= <<<HTML
<div class="index-project" style="background-color: #{$colour};">
<a href="/projects#{$project->getCleanName()}" class="index-project-anchor"></a>
<div class="index-project-content">
<div class="index-project-name">{$project->getName()}</div>
HTML;
if($project->hasSummary())
$body .= "<div class=\"index-project-summary\">{$project->getSummary()}</div>";
$body .= '</div>';
if(!empty($links)) {
$body .= '<div class="index-project-links">';
foreach($links as $link)
$body .= "<a class=\"index-project-link index-project-link-{$link['class']}\" href=\"{$link['url']}\" rel=\"noopener\" target=\"_blank\">{$link['text']}</a>";
$body .= '</div>';
}
$body .= '</div>';
}
$body .= <<<HTML
</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">
HTML;
foreach($contacts as $contact) {
$body .= "<div class=\"social social-{$contact->getName()}\" style=\"--social-colour: {$contact->getColourHex()}\">";
if($contact->hasLink()) {
$body .= "<a href=\"{$contact->getLink()}\" class=\"social-background\" target=\"_blank\" rel=\"noopener\"></a>";
} else {
$body .= "<div class=\"social-background\" onclick=\"fm.selectTextInElement(this.parentNode.querySelector('.social-handle')); fm.copySelectedText();\"></div>";
}
$body .= <<<HTML
<div class="social-icon {$contact->getIcon()}"></div>
<div class="social-content">
<div class="social-name">{$contact->getTitle()}</div>
<div class="social-handle">{$contact->getDisplay()}</div>
</div>
</div>
HTML;
}
$body .= '</div></div></div>';
$body .= fm_component('footer', [
'is_index' => true,
'onload' => [
['fm.initIndex'],
],
]);
return $body;
});

View file

@ -1,126 +0,0 @@
<?php
namespace Makai;
$router->get('/projects.php', mkiRedirect('/projects'));
$router->get('/projects.html', mkiRedirect('/projects'));
$router->get('/utilities', mkiRedirect('/projects'));
$router->get('/utilities.php', mkiRedirect('/projects'));
$router->get('/utilities.html', mkiRedirect('/projects'));
$router->get('/projects', function() use ($db) {
$projects = (new Projects($db))->getAll();
$languages = new Languages($db);
$sections = [
'projects' => [
'title' => 'Active Projects',
'desc' => 'Projects that I work on on a fairly regular basis.',
'items' => [],
],
'tools' => [
'title' => 'Tools',
'desc' => 'Personal quality of life tools that I update when I need something new from them.',
'items' => [],
],
'archives' => [
'title' => 'Archived Projects',
'desc' => 'Past projects that I no longer work on.',
'items' => [],
],
];
foreach($projects as $project) {
if($project->isArchived())
$sections['archives']['items'][] = $project;
else {
if($project->isTool())
$sections['tools']['items'][] = $project;
else
$sections['projects']['items'][] = $project;
}
}
$body = fm_component('header', [
'title' => 'flash.moe / projects',
]);
foreach($sections as $sectionId => $section) {
$body .= <<<HTML
<div class="section" id="section-{$sectionId}">
<div class="section-content">
<div class="section-background"></div>
<h1>{$section['title']}</h1>
<p>{$section['desc']}</p>
</div>
</div>
HTML;
foreach($section['items'] as $project) {
$links = [];
if($project->hasHomePageUrl())
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()];
if($project->hasSourceUrl())
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()];
if($project->hasDiscussionUrl())
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()];
$descLines = $project->hasDescription() ? $project->getDescription()->trim()->split("\n") : [];
$langs = $languages->getByProject($project);
if($project->hasColour())
$colour = $project->getColour();
else
foreach($langs as $lang)
if($lang->hasColour()) {
$colour = $lang->getColour();
break;
}
$colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT);
$body .= <<<HTML
<div class="project project-type-{$sectionId}" id="{$project->getCleanName()}" style="--project-colour: #{$colour};">
<div class="project-content">
<div class="project-details">
<h2>{$project->getName()}<div class="project-languages">
HTML;
foreach($langs as $lang) {
$langColour = str_pad(dechex($lang->getColour() ?? 0), 6, '0', STR_PAD_LEFT);
$body .= "<div class=\"project-language\" style=\"--language-colour: #{$langColour};\">{$lang->getName()}</div>";
}
$body .= '</div></h2>';
if($project->hasSummary())
$body .= "<p class=\"project-details-summary\">{$project->getSummary()}</p>";
foreach($descLines as $line) {
$line = $line->trim();
if($line->isEmpty())
continue;
$body .= "<p>{$line}</p>";
}
$body .= '</div>';
if(!empty($links)) {
$body .= '<div class="project-links">';
foreach($links as $link)
$body .= "<a class=\"project-link project-link-{$link['class']}\" href=\"{$link['url']}\" rel=\"noopener\" target=\"_blank\">{$link['text']}</a>";
$body .= '</div>';
}
$body .= '</div></div>';
}
}
$body .= fm_component('footer');
return $body;
});

View file

@ -1,92 +0,0 @@
<?php
namespace Makai;
$router->get('/ssh.php', function() {
$query = '';
$minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT);
if($minLevel > 0)
$query .= sprintf('l=%d&', $minLevel);
if(!empty($_GET['c']))
$query .= 'c=1&';
if(!empty($_GET['j']))
$query .= 'j=1&';
if($query !== '')
$query = '?' . substr($query, 0, -1);
header('Location: /ssh_keys' . $query);
return 302;
});
$router->get('/ssh_keys', function() use ($db) {
$minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT);
$includeComment = !empty($_GET['c']);
$json = !empty($_GET['j']);
$keys = (new SSHKeys($db))->getKeys($minLevel);
if($json) {
$items = [];
foreach($keys as $key) {
$items[] = $item = new \stdClass;
$item->algo = $key->getAlgorithm();
$item->key = $key->getBody();
if($includeComment) {
$item->comment = (string)$key->getComment();
$item->created = $key->getCreatedAt()->format(\DateTime::ATOM);
$item->level = $key->getLevel();
}
}
return $items;
}
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString($includeComment) . "\n";
return $body;
});
$router->get('/authorized_keys', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(500);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(true) . "\n";
return $body;
});
$router->get('/git_keys_ro', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(100);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(false) . "\n";
return $body;
});
$router->get('/git_keys_rw', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(200);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(false) . "\n";
return $body;
});

View file

@ -1,204 +1,268 @@
<?php
namespace Makai;
use Index\DateTime;
use Index\Routing\Router;
use Index\Routing\RoutePathNotFoundException;
use Index\Routing\RouteMethodNotSupportedException;
use Index\Http\HttpFx;
require_once __DIR__ . '/../makai.php';
define('FM_NAV', [
['title' => 'Home', 'link' => '/'],
['title' => 'Projects', 'link' => '/projects'],
['title' => 'Contact', 'link' => '/contact'],
define('MKI_REDIRS', [
'/about' => '/',
'/about.html' => '/',
'/about.php' => '/',
'/index.php' => '/',
'/index.html' => '/',
'/related.php' => '/',
'/related.html' => '/',
'/friends' => '/',
'/friends.php' => '/',
'/friends.html' => '/',
'/related' => '/',
'/etc.php' => '/',
'/etc.html' => '/',
'/etcetera' => '/',
'/etcetera.html' => '/',
'/etcetera.php' => '/',
'/misc' => '/',
'/misc.html' => '/',
'/misc.php' => '/',
'/etc' => '/',
'/365' => '/',
'/donate' => '/',
'/blog.php' => '/',
'/blog-post.php' => '/',
'/blog/:id' => '/',
'/old-blog' => '/',
'/old-blog/:id' => '/',
'/projects.php' => '/projects',
'/projects.html' => '/projects',
'/utilities' => '/projects',
'/utilities.php' => '/projects',
'/utilities.html' => '/projects',
'/contact.php' => '/contact',
'/contact.html' => '/contact',
'/nintendo' => '/contact',
'/nintendo.php' => '/contact',
]);
define('FM_BGS', [
'/assets/headers/krk-000.jpg', '/assets/headers/krk-001.jpg', '/assets/headers/krk-002.jpg',
'/assets/headers/krk-003.jpg', '/assets/headers/krk-004.jpg', '/assets/headers/krk-005.jpg',
'/assets/headers/krk-006.jpg', '/assets/headers/krk-007.jpg', '/assets/headers/krk-008.jpg',
$router = new HttpFx;
'/assets/headers/mkt-000.jpg', '/assets/headers/mkt-001.jpg', '/assets/headers/mkt-002.jpg',
'/assets/headers/mkt-003.jpg', '/assets/headers/mkt-004.jpg', '/assets/headers/mkt-005.jpg',
'/assets/headers/mkt-006.jpg', '/assets/headers/mkt-007.jpg', '/assets/headers/mkt-008.jpg',
'/assets/headers/mkt-009.jpg', '/assets/headers/mkt-010.jpg', '/assets/headers/mkt-011.jpg',
'/assets/headers/mkt-012.jpg', '/assets/headers/mkt-013.jpg', '/assets/headers/mkt-014.jpg',
'/assets/headers/mkt-015.jpg', '/assets/headers/mkt-016.jpg', '/assets/headers/mkt-017.jpg',
'/assets/headers/mkt-018.jpg', '/assets/headers/mkt-019.jpg', '/assets/headers/mkt-020.jpg',
'/assets/headers/mkt-021.jpg', '/assets/headers/mkt-022.jpg', '/assets/headers/mkt-023.jpg',
'/assets/headers/mkt-024.jpg', '/assets/headers/mkt-025.jpg', '/assets/headers/mkt-026.jpg',
'/assets/headers/mkt-027.jpg', '/assets/headers/mkt-028.jpg', '/assets/headers/mkt-029.jpg',
'/assets/headers/mkt-030.jpg', '/assets/headers/mkt-031.jpg', '/assets/headers/mkt-032.jpg',
'/assets/headers/mkt-033.jpg', '/assets/headers/mkt-034.jpg', '/assets/headers/mkt-035.jpg',
'/assets/headers/mkt-036.jpg', '/assets/headers/mkt-037.jpg', '/assets/headers/mkt-038.jpg',
'/assets/headers/mkt-039.jpg', '/assets/headers/mkt-040.jpg', '/assets/headers/mkt-041.jpg',
'/assets/headers/mkt-042.jpg', '/assets/headers/mkt-043.jpg', '/assets/headers/mkt-044.jpg',
'/assets/headers/mkt-045.jpg', '/assets/headers/mkt-046.jpg', '/assets/headers/mkt-047.jpg',
'/assets/headers/mkt-048.jpg', '/assets/headers/mkt-049.jpg', '/assets/headers/mkt-050.jpg',
'/assets/headers/mkt-051.jpg', '/assets/headers/mkt-052.jpg', '/assets/headers/mkt-053.jpg',
'/assets/headers/mkt-054.jpg', '/assets/headers/mkt-055.jpg', '/assets/headers/mkt-056.jpg',
'/assets/headers/mkt-057.jpg', '/assets/headers/mkt-058.jpg', '/assets/headers/mkt-059.jpg',
'/assets/headers/mkt-060.jpg', '/assets/headers/mkt-061.jpg', '/assets/headers/mkt-062.jpg',
'/assets/headers/mkt-063.jpg',
]);
define('FM_FEET', [
'if it ain\'t broke, i\'ll break it',
]);
define('FM_ERRS' , [
403 => [
'code' => 403,
'title' => 'Access Denied',
'image' => '/assets/errors/403.jpg',
'desc' => 'You are not supposed to be here.',
],
404 => [
'code' => 404,
'title' => 'Not Found',
'image' => '/assets/errors/404.jpg',
'desc' => 'Whatever you\'re looking for is no longer here, or might not have been here in the first place.',
],
405 => [
'code' => 405,
'title' => 'Method Not Supported',
'image' => '/assets/errors/405.jpg',
'desc' => 'You\'re up to something, aren\'t you?',
],
]);
function fm_component(string $_component_name, array $vars = []): string {
extract($vars);
ob_start();
require __DIR__ . '/../components/' . $_component_name . '.php';
$meow = ob_get_contents();
ob_clean();
return $meow;
}
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));
}
$reqMethod = (string)filter_input(INPUT_SERVER, 'REQUEST_METHOD');
$reqPath = '/' . trim(parse_url((string)filter_input(INPUT_SERVER, 'REQUEST_URI'), PHP_URL_PATH), '/');
$reqHead = false;
if($reqMethod == 'HEAD') {
$reqMethod = 'GET';
$reqHead = true;
}
$router = new Router;
$router->get('/error/:code', function(string $code) use ($reqHead) {
$errorInfo = FM_ERRS[$code ?? 404] ?? FM_ERRS[404];
http_response_code($errorInfo['code']);
if(!$reqHead) {
$body = fm_component('header', ['title' => $errorInfo['title']]);
$body .= <<<HTML
<div class="http-error">
<h2 class="http-error-head">{$errorInfo['title']}</h2>
<img src="{$errorInfo['image']}" alt="{$errorInfo['image']}" class="http-error-image"/>
<div class="http-error-desc">{$errorInfo['desc']}</div>
</div>
HTML;
$body .= fm_component('footer');
return $body;
}
$router->setDefaultErrorHandler(function($response, $request, $code, $text) use ($ctx) {
$path = 'errors/' . $code;
$tpl = $ctx->getTemplating();
$response->setContent($tpl->render($tpl->exists($path) ? $path : 'errors/master', [
'http_error_code' => $code,
'http_error_title' => $text,
]));
});
function mkiRedirect(string $to): callable {
return function() use ($to) {
header('Location: ' . $to);
return 302;
};
}
$router->get('/error/:code', function($response, $request, $code) {
return intval($code);
});
require_once MKI_DIR_PAGES . '/contact.php';
require_once MKI_DIR_PAGES . '/index.php';
require_once MKI_DIR_PAGES . '/projects.php';
require_once MKI_DIR_PAGES . '/ssh.php';
foreach(MKI_REDIRS as $source => $target)
$router->get($source, function($response) use ($target) {
$response->redirect($target);
});
header('X-Powered-By: Makai');
$router->use('/', function($response) {
$response->setPoweredBy('Makai+Index');
});
try {
$handlers = $router->resolve($reqMethod, $reqPath);
} catch(RoutePathNotFoundException $ex) {
$handlers = $router->resolve('GET', '/error/404');
} catch(RouteMethodNotSupportedException $ex) {
$handlers = $router->resolve('GET', '/error/405');
}
$router->get('/header-bgs.json', function() {
return json_encode(FM_BGS);
});
$result = $handlers->run();
$router->get('/now-listening', function() use ($ctx) {
return $ctx->getTemplating()->render('np', [
'header_offset' => (int)filter_input(INPUT_GET, 'offset', FILTER_SANITIZE_NUMBER_INT),
]);
});
if(is_int($result) && $result >= 400 && $result < 600)
$result = $router->resolve('GET', "/error/{$result}")->run();
$router->get('/now-listening.json', function() {
$lfmInfo = json_decode(file_get_contents('https://now.flash.moe/get.php?u=flashwave_'));
if($result !== null && !is_bool($result)) {
if(is_int($result)) {
http_response_code($result);
header('Content-Type: text/plain; charset=us-ascii');
echo $result;
} elseif(is_array($result)
|| $result instanceof \stdClass
|| $result instanceof \JsonSerializable) {
http_response_code(200);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($result);
} else {
$result = (string)$result;
if(empty($lfmInfo[0]->name))
return [];
if(strtolower(substr($result, 0, 14)) === '<!doctype html') {
header('Content-Type: text/html; charset=utf-8');
echo $result;
} else {
echo $result;
$lfmInfo = $lfmInfo[0];
return [
'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],
],
];
});
$router->get('/home', function() use ($ctx) {
return $ctx->getTemplating()->render('home/index');
});
$router->get('/', function() use ($ctx) {
$legacyPage = (string)filter_input(INPUT_GET, 'p');
if(!empty($legacyPage)) {
$legacyPages = [
'projects' => '/projects',
'contact' => '/contact',
'about' => '/',
'etc' => '/etc',
'hosted' => '/etc',
'friends' => '/related',
];
if(isset($legacyPages[$legacyPage])) {
header('Location: ' . $legacyPages[$legacyPage]);
return 302;
}
}
}
$db = $ctx->getDatabase();
return $ctx->getTemplating()->render('index', [
'projects' => (new Projects($db))->getFeatured(),
'languages' => new Languages($db),
'contacts' => (new Contacts($db))->getHomePage(),
]);
});
$router->get('/contact', function() use ($ctx) {
return $ctx->getTemplating()->render('contact', [
'contacts' => (new Contacts($ctx->getDatabase()))->getAll(),
]);
});
$router->get('/projects', function() use ($ctx) {
$db = $ctx->getDatabase();
$projects = (new Projects($db))->getAll();
$sections = [
'projects' => [
'title' => 'Active Projects',
'desc' => 'Projects that I work on on a fairly regular basis.',
'items' => [],
],
'tools' => [
'title' => 'Tools',
'desc' => 'Personal quality of life tools that I update when I need something new from them.',
'items' => [],
],
'archives' => [
'title' => 'Archived Projects',
'desc' => 'Past projects that I no longer work on.',
'items' => [],
],
];
foreach($projects as $project) {
if($project->isArchived())
$sections['archives']['items'][] = $project;
else {
if($project->isTool())
$sections['tools']['items'][] = $project;
else
$sections['projects']['items'][] = $project;
}
}
return $ctx->getTemplating()->render('projects', [
'sections' => $sections,
'languages' => new Languages($db),
]);
});
$router->get('/ssh.php', function() {
$query = '';
$minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT);
if($minLevel > 0)
$query .= sprintf('l=%d&', $minLevel);
if(!empty($_GET['c']))
$query .= 'c=1&';
if(!empty($_GET['j']))
$query .= 'j=1&';
if($query !== '')
$query = '?' . substr($query, 0, -1);
header('Location: /ssh_keys' . $query);
return 302;
});
$router->get('/ssh_keys', function() use ($db) {
$minLevel = (int)filter_input(INPUT_GET, 'l', FILTER_SANITIZE_NUMBER_INT);
$includeComment = !empty($_GET['c']);
$json = !empty($_GET['j']);
$keys = (new SSHKeys($db))->getKeys($minLevel);
if($json) {
$items = [];
foreach($keys as $key) {
$items[] = $item = new \stdClass;
$item->algo = $key->getAlgorithm();
$item->key = $key->getBody();
if($includeComment) {
$item->comment = (string)$key->getComment();
$item->created = $key->getCreatedAt()->format(\DateTime::ATOM);
$item->level = $key->getLevel();
}
}
return $items;
}
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString($includeComment) . "\n";
return $body;
});
$router->get('/authorized_keys', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(500);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(true) . "\n";
return $body;
});
$router->get('/git_keys_ro', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(100);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(false) . "\n";
return $body;
});
$router->get('/git_keys_rw', function() use ($db) {
$keys = (new SSHKeys($db))->getKeys(200);
header('Content-Type: text/plain; charset=us-ascii');
$body = '';
foreach($keys as $key)
$body .= $key->toString(false) . "\n";
return $body;
});
$router->dispatch();

74
src/MakaiContext.php Normal file
View file

@ -0,0 +1,74 @@
<?php
namespace Makai;
use Index\Data\IDbConnection;
final class MakaiContext {
private IDbConnection $db;
private ?TemplateContext $tpl = null;
public function __construct(IDbConnection $db) {
$this->db = $db;
}
public function getDatabase(): IDbConnection {
return $this->db;
}
public function getTemplating(): TemplateContext {
if($this->tpl === null) {
$this->tpl = new TemplateContext(MKI_DIR_TEMPLATES);
$this->tpl->setGlobal('makai', $this);
$this->tpl->setGlobal('header_nav', $this->getDefaultNavigation());
$this->tpl->setGlobal('header_bgs', $this->getDefaultHeaders());
$this->tpl->setGlobal('footer_quotes', $this->getFooterQuotes());
}
return $this->tpl;
}
public function getDefaultNavigation(): array {
return [
['title' => 'Home', 'link' => '/'],
['title' => 'Projects', 'link' => '/projects'],
['title' => 'Contact', 'link' => '/contact'],
];
}
public function getDefaultHeaders(): array {
return [
'/assets/headers/krk-000.jpg', '/assets/headers/krk-001.jpg', '/assets/headers/krk-002.jpg',
'/assets/headers/krk-003.jpg', '/assets/headers/krk-004.jpg', '/assets/headers/krk-005.jpg',
'/assets/headers/krk-006.jpg', '/assets/headers/krk-007.jpg', '/assets/headers/krk-008.jpg',
'/assets/headers/mkt-000.jpg', '/assets/headers/mkt-001.jpg', '/assets/headers/mkt-002.jpg',
'/assets/headers/mkt-003.jpg', '/assets/headers/mkt-004.jpg', '/assets/headers/mkt-005.jpg',
'/assets/headers/mkt-006.jpg', '/assets/headers/mkt-007.jpg', '/assets/headers/mkt-008.jpg',
'/assets/headers/mkt-009.jpg', '/assets/headers/mkt-010.jpg', '/assets/headers/mkt-011.jpg',
'/assets/headers/mkt-012.jpg', '/assets/headers/mkt-013.jpg', '/assets/headers/mkt-014.jpg',
'/assets/headers/mkt-015.jpg', '/assets/headers/mkt-016.jpg', '/assets/headers/mkt-017.jpg',
'/assets/headers/mkt-018.jpg', '/assets/headers/mkt-019.jpg', '/assets/headers/mkt-020.jpg',
'/assets/headers/mkt-021.jpg', '/assets/headers/mkt-022.jpg', '/assets/headers/mkt-023.jpg',
'/assets/headers/mkt-024.jpg', '/assets/headers/mkt-025.jpg', '/assets/headers/mkt-026.jpg',
'/assets/headers/mkt-027.jpg', '/assets/headers/mkt-028.jpg', '/assets/headers/mkt-029.jpg',
'/assets/headers/mkt-030.jpg', '/assets/headers/mkt-031.jpg', '/assets/headers/mkt-032.jpg',
'/assets/headers/mkt-033.jpg', '/assets/headers/mkt-034.jpg', '/assets/headers/mkt-035.jpg',
'/assets/headers/mkt-036.jpg', '/assets/headers/mkt-037.jpg', '/assets/headers/mkt-038.jpg',
'/assets/headers/mkt-039.jpg', '/assets/headers/mkt-040.jpg', '/assets/headers/mkt-041.jpg',
'/assets/headers/mkt-042.jpg', '/assets/headers/mkt-043.jpg', '/assets/headers/mkt-044.jpg',
'/assets/headers/mkt-045.jpg', '/assets/headers/mkt-046.jpg', '/assets/headers/mkt-047.jpg',
'/assets/headers/mkt-048.jpg', '/assets/headers/mkt-049.jpg', '/assets/headers/mkt-050.jpg',
'/assets/headers/mkt-051.jpg', '/assets/headers/mkt-052.jpg', '/assets/headers/mkt-053.jpg',
'/assets/headers/mkt-054.jpg', '/assets/headers/mkt-055.jpg', '/assets/headers/mkt-056.jpg',
'/assets/headers/mkt-057.jpg', '/assets/headers/mkt-058.jpg', '/assets/headers/mkt-059.jpg',
'/assets/headers/mkt-060.jpg', '/assets/headers/mkt-061.jpg', '/assets/headers/mkt-062.jpg',
'/assets/headers/mkt-063.jpg',
];
}
public function getFooterQuotes(): array {
return [
'if it ain\'t broke, i\'ll break it',
];
}
}

47
src/Template.php Normal file
View file

@ -0,0 +1,47 @@
<?php
namespace Makai;
class Template {
public function __construct(
private TemplateContext $context,
private TemplateVars $vars,
private string $path
) {}
public function setVar(string $name, mixed $value): void {
$this->vars->setVar($name, $value);
}
public function removeVar(string $name): void {
$this->vars->removeVar($name);
}
public function render(array $vars = [], ?TemplateSelf $self = null): string {
if(!is_file($this->path))
throw new \RuntimeException('Template file does not exist: ' . $this->path);
$self = new TemplateSelf($this->context->getFunctions() + $vars + $this->vars->getVars(), $self);
$self->tplPath = $this->path;
$self->var = fn(string $name, mixed $default = null) => $this->vars->getVar($name, $default);
$self->setVar = fn(string $name, mixed $value) => $this->vars->setVar($name, $value);
$self->remVar = fn(string $name) => $this->vars->removeVar($name);
$self->block = fn(string $name, mixed $contents) => $this->context->setBlock($name, new TemplateBlock($self, $contents));
$self->ctx = $this->context;
$self->extends = fn(string $name) => $self->extends = $name;
ob_start();
(static function() use ($self) { include $self->tplPath; })();
$buffer = ob_get_contents();
ob_end_clean();
if(is_string($self->extends)) {
if(!empty($buffer))
throw new \RuntimeException('You cannot output from templates that extend another.');
$buffer = $this->context->render($self->extends, [], $self);
}
return $buffer;
}
}

22
src/TemplateBlock.php Normal file
View file

@ -0,0 +1,22 @@
<?php
namespace Makai;
use Closure;
class TemplateBlock {
public function __construct(
private object $self,
private mixed $body
) {}
public function render(): string {
if(is_callable($this->body)) {
ob_start();
($this->body)($this->self);
$body = ob_get_contents();
ob_end_clean();
} else $body = strval($this->body);
return $body;
}
}

54
src/TemplateContext.php Normal file
View file

@ -0,0 +1,54 @@
<?php
namespace Makai;
use Closure;
class TemplateContext {
private const EXT = '.php';
private string $pathFormat = '%s' . self::EXT;
private TemplateVars $vars;
private array $blocks = [];
public function __construct($path = '') {
if(!empty($path))
$this->pathFormat = rtrim($path, '\\/') . DIRECTORY_SEPARATOR . '%s' . self::EXT;
$this->vars = new TemplateVars;
}
public function setGlobal(string $name, mixed $value): void {
$this->vars->setVar($name, $value);
}
public function removeGlobal(string $name): void {
$this->vars->removeVar($name);
}
public function getFunctions(): array {
return [
'global' => $this->vars->getVar(...),
'getBlock' => $this->getBlock(...),
'x' => fn(string $string) => htmlspecialchars($string, ENT_QUOTES, 'utf-8', true),
'e' => function() { return ''; }, // reserve for the HTML builder
];
}
public function create(string $path): Template {
return new Template($this, new TemplateVars($this->vars), sprintf($this->pathFormat, $path));
}
public function exists(string $path): bool {
return is_file(sprintf($this->pathFormat, $path));
}
public function render(string $path, array $vars = [], ?TemplateSelf $self = null): string {
return $this->create($path)->render($vars, $self);
}
public function getBlock(string $name, mixed $default = ''): string {
return ($this->blocks[$name] ?? new TemplateBlock(new \stdClass, $default))->render();
}
public function setBlock(string $name, TemplateBlock $block): void {
$this->blocks[$name] = $block;
}
}

32
src/TemplateSelf.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace Makai;
class TemplateSelf {
public function __construct(
private array $members = [],
?TemplateSelf $inherit = null
) {
if($inherit !== null)
$this->members += $inherit->members;
}
public function __get(string $name): mixed {
return $this->members[$name];
}
public function __set(string $name, mixed $value): void {
$this->members[$name] = $value;
}
public function __isset(string $name): bool {
return isset($this->members[$name]);
}
public function __unset(string $name): void {
unset($this->members[$name]);
}
public function __call(string $name, array $args): mixed {
return ($this->members[$name])(...$args);
}
}

32
src/TemplateVars.php Normal file
View file

@ -0,0 +1,32 @@
<?php
namespace Makai;
class TemplateVars {
public function __construct(
private ?TemplateVars $parent = null,
private array $vars = []
) {}
public function getVars(): array {
$vars = $this->vars;
if($this->parent !== null)
$vars += $this->parent->getVars();
return $vars;
}
public function getVar(string $name, mixed $default = null): mixed {
if(isset($this->vars[$name]))
return $this->vars[$name];
if($this->parent !== null)
return $this->parent->getVar($name, $default);
return $default;
}
public function setVar(string $name, mixed $value): void {
$this->vars[$name] = $value;
}
public function removeVar(string $name): void {
unset($this->vars[$name]);
}
}

33
tpl/contact.php Normal file
View file

@ -0,0 +1,33 @@
<?php
$self->extends('home/master');
$self->header_title = 'flash.moe / contact';
$self->block('container', function($self) {
?>
<div class="section">
<div class="section-content">
<div class="section-background"></div>
<h1>Contacts</h1>
</div>
</div>
<div class="socials">
<?php foreach($self->contacts as $contact): ?>
<div class="social social-<?=$contact->getName();?>" style="--social-colour: <?=$contact->getColourHex();?>">
<?php if($contact->hasLink()): ?>
<a href="<?=$contact->getLink();?>" 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 <?=$contact->getIcon();?>"></div>
<div class="social-content">
<div class="social-name"><?=$contact->getName();?></div>
<div class="social-handle"><?=$contact->getDisplay();?></div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php
});

5
tpl/errors/403.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_image = '/assets/errors/403.jpg';
$self->http_error_desc = 'You are not supposed to be here.';

5
tpl/errors/404.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_image = '/assets/errors/404.jpg';
$self->http_error_desc = 'Whatever you\'re looking for is no longer here, or might not have been here in the first place.';

5
tpl/errors/405.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$self->extends('errors/master');
$self->http_error_image = '/assets/errors/405.jpg';
$self->http_error_desc = 'You\'re up to something, aren\'t you?';

14
tpl/errors/master.php Normal file
View file

@ -0,0 +1,14 @@
<?php
$self->extends('master');
$self->block('container', function($self) {
?>
<div class="http-error">
<h2 class="http-error-head"><?=($self->http_error_title ?? ('Unknown Error #' . $self->http_error_code));?></h2>
<?php if(isset($self->http_error_image)): ?>
<img src="<?=$self->http_error_image;?>" alt="<?=$self->http_error_image;?>" class="http-error-image"/>
<?php endif; ?>
<div class="http-error-desc"><?=($self->http_error_desc ?? 'No additional information is available.');?></div>
</div>
<?php
});

46
tpl/home/index.php Normal file
View file

@ -0,0 +1,46 @@
<?php
$self->extends('home/master');
$self->header_title = 'flash.moe / homepage';
$self->header_full = true;
$self->footer_onload = [['fm.initClock'], ['fm.initIndex', 10]];
$self->block('container', function($self) {
?>
<div class="php">
<div class="php-time">
<div class="php-time-analog">
<div class="clock">
<div class="clock-background"></div>
<div class="clock-center"></div>
<div class="clock-hand clock-hand-hours"><div class="clock-hand-display"></div></div>
<div class="clock-hand clock-hand-minutes"><div class="clock-hand-display"></div></div>
<div class="clock-hand clock-hand-seconds"><div class="clock-hand-display"></div></div>
<div class="clock-number clock-number-3"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-6"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-9"><div class="clock-number-display"></div></div>
<div class="clock-number clock-number-12"><div class="clock-number-display"></div></div>
</div>
</div>
<div class="php-time-alter">
<div class="php-time-digital">
<div class="php-time-digital-hours">--</div>
<div class="php-time-digital-separator">:</div>
<div class="php-time-digital-minutes">--</div>
</div>
<div class="php-time-date">
<span class="php-date-label">Week</span>
<span class="php-date-week">--</span>
<span class="php-date-label">&nbsp;&mdash;&nbsp;</span>
<span class="php-date-year">----</span><span class="php-date-label">-</span><span class="php-date-month">---</span><span class="php-date-label">-</span><span class="php-date-day">--</span>
</div>
</div>
</div>
<form class="php-search" method="get" action="https://duckduckgo.com/">
<div class="php-search-input">
<input type="search" name="q" placeholder="Search using DuckDuckGo..."/>
</div>
</form>
</div>
<?php
});

2
tpl/home/master.php Normal file
View file

@ -0,0 +1,2 @@
<?php
$self->extends('master');

85
tpl/index.php Normal file
View file

@ -0,0 +1,85 @@
<?php
$self->extends('master');
$self->header_title = 'flash.moe';
$self->header_is_index = true;
$self->footer_onload = [['fm.initIndex']];
$self->block('container', function($self) {
?>
<div class="index-menu">
<?php for($i = 1; $i < count($self->header_nav); ++$i): $link = $self->header_nav[$i]; ?>
<a href="<?=$link['link'];?>"><?=$link['title'];?></a>
<?php endfor; ?>
</div>
<div class="index-featured">
<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
foreach($self->projects as $project):
$links = [];
if($project->hasHomePageUrl())
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()];
if($project->hasSourceUrl())
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()];
if($project->hasDiscussionUrl())
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()];
$colour = $project->hasColour() ? $project->getColour() : $self->languages->getProjectColour($project);
$colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT);
?>
<div class="index-project" style="background-color: #<?=$colour;?>;">
<a href="/projects#<?=$project->getCleanName();?>" class="index-project-anchor"></a>
<div class="index-project-content">
<div class="index-project-name"><?=$project->getName();?></div>
<?php if($project->hasSummary()): ?>
<div class="index-project-summary"><?=$project->getSummary();?></div>
<?php endif; ?>
</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($self->contacts as $contact): ?>
<div class="social social-<?=$contact->getName();?>" style="--social-colour: <?=$contact->getColourHex();?>">
<?php if($contact->hasLink()): ?>
<a href="<?=$contact->getLink();?>" 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 <?=$contact->getIcon();?>"></div>
<div class="social-content">
<div class="social-name"><?=$contact->getTitle();?></div>
<div class="social-handle"><?=$contact->getDisplay();?></div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php
});

View file

@ -1,28 +1,19 @@
<?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'];
$showNowPlaying = !empty($is_index) || !empty($do_fullscreen_header);
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8"/>
<title><?=($title ?? 'flash.moe');?></title>
<title><?=($self->header_title ?? 'flash.moe');?></title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>
<link href="<?=$baseUrl;?>/css/2021.css" type="text/css" rel="stylesheet"/>
<link href="<?=$baseUrl;?>/assets/sprite.css" type="text/css" rel="stylesheet"/>
<link href="<?=$baseUrl;?>/assets/fonts/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;?>
<link href="/css/2021.css" type="text/css" rel="stylesheet"/>
<link href="/assets/sprite.css" type="text/css" rel="stylesheet"/>
<link href="/assets/fonts/electrolize/style.css" type="text/css" rel="stylesheet"/>
</head>
<body class="<?php if(isset($is_index)) { echo 'index ';} if(isset($do_fullscreen_header)) { echo 'fullscreen-header ';} if(isset($is_now_playing)) { echo 'now-playing ';} ?>">
<body class="<?php if(isset($self->header_is_index)) { echo 'index ';} if(isset($self->header_full)) { echo 'fullscreen-header ';} if(isset($self->header_now_playing)) { echo 'now-playing ';} ?>">
<div class="header">
<div class="header-background">
<img src="<?=($backgrounds[array_rand($backgrounds)]);?>" alt=""/>
<img src="<?=($self->header_bgs[array_rand($self->header_bgs)]);?>" alt=""/>
</div>
<div class="header-foreground"<?php if(isset($offset) && $offset > 0) echo " style=\"padding-bottom: {$offset}px\""; ?>>
<div class="header-foreground"<?php if(isset($self->header_offset) && $self->header_offset > 0) echo " style=\"padding-bottom: {$self->header_offset}px\""; ?>>
<a class="header-logo" href="/">
<div class="header-flash">flash</div>
<div class="header-wave">wave</div>
@ -41,7 +32,7 @@ $showNowPlaying = !empty($is_index) || !empty($do_fullscreen_header);
</div>
</div>
<div class="header-menu">
<?php foreach($menu as $link): ?>
<?php foreach($self->header_nav as $link): ?>
<a href="<?=$link['link'];?>"<?php if($link['link'][0] === '/' && substr($link['link'], 0, 2) !== '//') { echo ''; } ?>><?=$link['title'];?></a>
<?php endforeach; ?>
</div>
@ -49,3 +40,16 @@ $showNowPlaying = !empty($is_index) || !empty($do_fullscreen_header);
</div>
</div>
<div class="container">
<?=$self->getBlock('container');?>
</div>
<div class="footer">
<div class="footer-text">&copy; flashwave 2010-<?=date('Y');?> - <?=($self->footer_quotes[array_rand($self->footer_quotes)]);?></div>
</div>
<?php if(isset($self->footer_onload) && is_array($self->footer_onload)): ?>
<script type="text/javascript">
window.fm = { onload: <?=json_encode($self->footer_onload);?> };
</script>
<?php endif; ?>
<script src="/assets/2021.js" charset="utf-8" type="text/javascript"></script>
</body>
</html>

7
tpl/np.php Normal file
View file

@ -0,0 +1,7 @@
<?php
$self->extends('master');
$self->header_title = 'flash.moe / now listening';
$self->header_full = true;
$self->header_now_playing = true;
$self->footer_onload = [['fm.initIndex', 10]];

85
tpl/projects.php Normal file
View file

@ -0,0 +1,85 @@
<?php
$self->extends('home/master');
$self->header_title = 'flash.moe / projects';
$self->block('container', function($self) {
?>
<?php foreach($self->sections as $sectionId => $section): ?>
<div class="section" id="section-<?=$sectionId;?>">
<div class="section-content">
<div class="section-background"></div>
<h1><?=$section['title'];?></h1>
<p><?=$section['desc'];?></p>
</div>
</div>
<?php
foreach($section['items'] as $project):
$links = [];
if($project->hasHomePageUrl())
$links[] = ['class' => 'homepage', 'text' => 'Homepage', 'url' => $project->getHomePageUrl()];
if($project->hasSourceUrl())
$links[] = ['class' => 'repository', 'text' => 'Source', 'url' => $project->getSourceUrl()];
if($project->hasDiscussionUrl())
$links[] = ['class' => 'forum', 'text' => 'Discussion', 'url' => $project->getDiscussionUrl()];
$descLines = $project->hasDescription() ? $project->getDescription()->trim()->split("\n") : [];
$langs = $self->languages->getByProject($project);
if($project->hasColour())
$colour = $project->getColour();
else
foreach($langs as $lang)
if($lang->hasColour()) {
$colour = $lang->getColour();
break;
}
$colour = str_pad(dechex($colour), 6, '0', STR_PAD_LEFT);
?>
<div class="project project-type-<?=$sectionId;?>" id="<?=$project->getCleanName();?>" style="--project-colour: #<?=$colour;?>;">
<div class="project-content">
<div class="project-details">
<h2><?=$project->getName();?><div class="project-languages">
<?php
foreach($langs as $lang):
$langColour = str_pad(dechex($lang->getColour() ?? 0), 6, '0', STR_PAD_LEFT);
?>
<div class="project-language" style="--language-colour: #<?=$langColour;?>;"><?=$lang->getName();?></div>
<?php endforeach; ?>
</div></h2>
<?php if($project->hasSummary()): ?>
<p class="project-details-summary"><?=$project->getSummary();?></p>
<?php endif; ?>
<?php
foreach($descLines as $line):
$line = $line->trim();
if($line->isEmpty())
continue;
?>
<p><?=$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; ?>
<?php endforeach; ?>
<?php
});