Compare commits

..

10 commits

63 changed files with 2158 additions and 1126 deletions

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
deploy.sh
deploy-excludes
composer.lock
.idea
vendor
cache/*

View file

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon Town - 401</title>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/style_errors.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="background"></div>
<header>
<img src="/assets/subcon/cloud_64.png" alt="logo"/>
</header>
<main>
<h1>You are not authorized to access that page.</h1>
</main>
<footer>
HTTP status: 401 - Unauthorized
</footer>
</body>
</html>

View file

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon Town - 404</title>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/style_errors.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="background"></div>
<header>
<img src="/assets/subcon/cloud_64.png" alt="logo"/>
</header>
<main>
<h1>That page does not exist.</h1>
</main>
<footer>
HTTP status: 404 - Not found
</footer>
</body>
</html>

View file

@ -1,27 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon Town - 410</title>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/style_errors.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="background"></div>
<header>
<img src="/assets/subcon/cloud_64.png" alt="logo"/>
</header>
<main>
<h1>That page is gone. Forever.</h1>
</main>
<footer>
HTTP status: 410 - Gone
</footer>
</body>
</html>

View file

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon Town - 500</title>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/style_errors.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="background"></div>
<header>
<img src="/assets/subcon/cloud_64.png" alt="logo"/>
</header>
<main>
<h1>An error occurred on the server.</h1>
<p>
The page you requested could not be served due to an internal server error. Please try again
later.
</p>
</main>
<footer>
HTTP status: 500 - Internal Server Error
</footer>
</body>
</html>

View file

@ -1,31 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon Town - 502</title>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/style_errors.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="background"></div>
<header>
<img src="/assets/subcon/cloud_64.png" alt="logo"/>
</header>
<main>
<h1>Bad Gateway.</h1>
<p>
The page you requested could not be served due to an internal communication error. Please
try again later.
</p>
</main>
<footer>
HTTP status: 502 - Bad gateway
</footer>
</body>
</html>

View file

@ -1,3 +1,25 @@
# subcon.town # Subcon Town Frontpage
The subcon.town frontpage. This project contains the complete code and assets used by the Subcon Town frontpage.
## Licensing
All code is licensed under the BOML-1.0.0 license (Blue Oak Model License).
See the LICENSE file for more details.
The "Subcon Town" branding and its associated assets (located in public_html/assets/subcon)
are licensed under the Creative Commons BY-NC-ND license. You may share and redistribute
these works, but please do not edit them or use them for commercial purposes. For more
details, read the LICENSE file in public_html/assets/subcon.
### Third party works
The fonts and background image used on subcon.town are not owned by Subcon Town. Subcon
Town does not claim any rights to these original works unless otherwise specified by their
respective licensing.
### Complains
If you feel any material is incorrectly used in this project, or otherwise infringes on
your rights, please file a complaint with fristi@subcon.town. Please allow up to 48 hours
for a response.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 470 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View file

@ -1,93 +0,0 @@
Copyright (c) 2015 Ek Type (www.ektype.in)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

View file

@ -1,532 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg>
<metadata>
Created by FontForge 20110222 at Mon Mar 7 13:22:36 2011
By www-data
Copyright (c) Joseph Spicer, 2004. All rights reserved.
</metadata>
<defs>
<font id="DuckHunt" horiz-adv-x="629" >
<font-face
font-family="Duck Hunt"
font-weight="400"
font-stretch="normal"
units-per-em="1000"
panose-1="2 11 0 0 0 0 0 0 0 0"
ascent="800"
descent="-200"
x-height="559"
cap-height="699"
bbox="18 -200 1062 800.09"
underline-thickness="50"
underline-position="-50"
unicode-range="U+0020-2122"
/>
<missing-glyph />
<glyph glyph-name="space" unicode=" "
/>
<glyph glyph-name="space" unicode="&#xa0;"
/>
<glyph glyph-name="H" unicode="H"
d="M310 0h-280v699h280v-206h152v206h137v-699h-137v277h-152v-277z" />
<glyph glyph-name="N" unicode="N"
d="M310 0h-280v699h280v-131q23 0 47 -8.5t44.5 -23t36.5 -35t24 -44.5v242h137v-699h-137v91q0 70 -18 109t-42.5 57.5t-50 22.5t-41.5 4v-284z" />
<glyph glyph-name="T" unicode="T"
d="M457 0h-280v550h-147v149h569v-149h-142v-550z" />
<glyph glyph-name="I" unicode="I"
d="M599 149v-149h-569v149h147v401h-147v149h569v-149h-142v-401h142z" />
<glyph glyph-name="L" unicode="L"
d="M310 286h289v-286h-569v699h280v-413z" />
<glyph glyph-name="O" unicode="O"
d="M30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="A" unicode="A"
d="M462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16z" />
<glyph glyph-name="P" unicode="P"
d="M310 424q0 -73 22 -106t54 -33t54 33t22 106t-22 106t-54 33t-54 -33t-22 -106zM310 149v-149h-280v699h434q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-292q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-154z" />
<glyph glyph-name="J" unicode="J"
d="M319 699h280v-566q0 -60 -38 -96.5t-110 -36.5h-421v286h213q32 0 54 32.5t22 105.5v275z" />
<glyph glyph-name="one" unicode="1"
d="M599 149v-149h-569v149h147v401l-147 -50v149l147 50h280v-550h142z" />
<glyph glyph-name="zero" unicode="0"
d="M30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="Z" unicode="Z"
d="M599 149v-149h-569v149l289 401h-289v149h569v-149l-289 -401h289z" />
<glyph glyph-name="F" unicode="F"
d="M599 418v-136h-289v-282h-280v699h569v-136h-289v-145h289z" />
<glyph glyph-name="a" unicode="a" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13z" />
<glyph glyph-name="c" unicode="c" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84t43.5 -26q22 0 38.5 20t20.5 65h111v-208q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5
q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="d" unicode="d" horiz-adv-x="503"
d="M24 559h347q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-337v559zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="f" unicode="f" horiz-adv-x="503"
d="M479 334v-108h-231v-226h-224v559h455v-109h-231v-116h231z" />
<glyph glyph-name="h" unicode="h" horiz-adv-x="503"
d="M248 0h-224v559h224v-165h122v165h109v-559h-109v222h-122v-222z" />
<glyph glyph-name="i" unicode="i" horiz-adv-x="503"
d="M479 119v-119h-455v119h118v321h-118v119h455v-119h-113v-321h113z" />
<glyph glyph-name="j" unicode="j" horiz-adv-x="503"
d="M255 559h224v-453q0 -48 -30 -77t-88 -29h-337v229h170q26 0 43.5 26t17.5 84v220z" />
<glyph glyph-name="k" unicode="k" horiz-adv-x="503"
d="M248 0h-224v559h224v-223q16 0 37 4.5t40 20.5t32 45.5t13 79.5v73h109v-85q0 -60 -19 -89.5t-58 -48.5q33 -12 55 -41t22 -80v-215h-109v73q0 56 -12 87t-30 46t-39.5 18t-40.5 3v-227z" />
<glyph glyph-name="l" unicode="l" horiz-adv-x="503"
d="M248 229h231v-229h-455v559h224v-330z" />
<glyph glyph-name="n" unicode="n" horiz-adv-x="503"
d="M248 0h-224v559h224v-105q19 0 38 -6.5t35.5 -18.5t29.5 -28t19 -35v193h109v-559h-109v73q0 56 -14.5 87t-34 46t-40 18t-33.5 3v-227z" />
<glyph glyph-name="o" unicode="o" horiz-adv-x="503"
d="M24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z
" />
<glyph glyph-name="p" unicode="p" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84.5t43.5 -26.5q25 0 43 26.5t18 84.5q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85zM248 119v-119h-224v559h347q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-234q0 -28 -9.5 -48t-25 -32t-35 -17.5t-38.5 -5.5h-123z" />
<glyph glyph-name="t" unicode="t" horiz-adv-x="503"
d="M366 0h-224v440h-118v119h455v-119h-113v-440z" />
<glyph glyph-name="u" unicode="u" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-453q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v453h224v-220z" />
<glyph glyph-name="z" unicode="z" horiz-adv-x="503"
d="M479 119v-119h-455v119l231 321h-231v119h455v-119l-231 -321h231z" />
<glyph glyph-name="E" unicode="E"
d="M599 136v-136h-569v699h569v-136h-289v-145h289v-136h-289v-146h289z" />
<glyph glyph-name="e" unicode="e" horiz-adv-x="503"
d="M479 109v-109h-455v559h455v-109h-231v-116h231v-108h-231v-117h231z" />
<glyph glyph-name="X" unicode="X"
d="M410 0l-95 175l-95 -175h-190l190 350l-189 349h190l94 -174l94 174h190l-189 -349l190 -350h-190z" />
<glyph glyph-name="x" unicode="x" horiz-adv-x="503"
d="M328 0l-76 140l-76 -140h-152l152 280l-151 279h152l75 -139l75 139h152l-151 -279l152 -280h-152z" />
<glyph glyph-name="R" unicode="R"
d="M310 0h-280v699h434q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-182q0 -55 -26.5 -84t-62.5 -39q17 -4 33 -19.5t28.5 -37t20 -47.5t7.5 -52v-109h-137v61q0 55 -18 88t-42.5 51t-50 23.5t-41.5 5.5v-229zM310 464q0 -51 22 -75t54 -24t54 24t22 75t-22 75t-54 24
t-54 -24t-22 -75z" />
<glyph glyph-name="r" unicode="r" horiz-adv-x="503"
d="M248 0h-224v559h347q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-146q0 -44 -21 -67t-50 -31q14 -3 27 -15.5t22.5 -30t15.5 -38t6 -41.5v-87h-109v49q0 44 -14.5 70.5t-34 40.5t-40 18.5t-33.5 4.5v-183zM248 371q0 -40 17.5 -59.5t43.5 -19.5q25 0 43 19.5t18 59.5
q0 41 -18 60t-43 19q-26 0 -43.5 -19t-17.5 -60z" />
<glyph glyph-name="Y" unicode="Y"
d="M177 0v378l-146 321h190l94 -224l94 224h190l-142 -312v-387h-280z" />
<glyph glyph-name="y" unicode="y" horiz-adv-x="503"
d="M142 0v302l-117 257h152l75 -179l75 179h152l-113 -249v-310h-224z" />
<glyph glyph-name="V" unicode="V"
d="M386 286l76 413h137l-148 -699h-273l-148 699h280z" />
<glyph glyph-name="W" unicode="W" horiz-adv-x="1061"
d="M818 286l76 413h137l-148 -699h-273l-80 375l-79 -375h-273l-148 699h280l76 -413l76 413h280z" />
<glyph glyph-name="v" unicode="v" horiz-adv-x="503"
d="M309 229l61 330h109l-118 -559h-219l-118 559h224z" />
<glyph glyph-name="w" unicode="w" horiz-adv-x="849"
d="M654 229l61 330h110l-119 -559h-218l-64 300l-63 -300h-219l-118 559h224l61 -330l61 330h224z" />
<glyph glyph-name="B" unicode="B"
d="M502 353q25 -10 43 -23t30 -30.5t18 -41.5t6 -57v-68q0 -60 -38 -96.5t-110 -36.5h-421v699h434q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-65q0 -67 -23.5 -102.5t-73.5 -49.5zM310 491q0 -43 22 -58.5t54 -15.5t54 15.5t22 58.5t-22 57.5t-54 14.5t-54 -14.5
t-22 -57.5zM310 210q0 -43 22 -63.5t54 -20.5t54 20.5t22 63.5t-22 62.5t-54 19.5t-54 -19.5t-22 -62.5z" />
<glyph glyph-name="b" unicode="b" horiz-adv-x="503"
d="M402 282q39 -16 58 -42t19 -79v-55q0 -48 -30 -77t-88 -29h-337v559h347q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-52q0 -54 -19 -82.5t-58 -39.5zM248 393q0 -35 17.5 -47t43.5 -12q25 0 43 12t18 47q0 34 -18 45.5t-43 11.5q-26 0 -43.5 -11.5t-17.5 -45.5z
M248 168q0 -35 17.5 -51t43.5 -16q25 0 43 16t18 51t-18 50.5t-43 15.5q-26 0 -43.5 -15.5t-17.5 -50.5z" />
<glyph glyph-name="G" unicode="G"
d="M310 424q0 -73 22 -105.5t54 -32.5q28 0 48.5 25t25.5 82h139v-393h-210v60q0 -8 -9.5 -18.5t-24 -19.5t-31.5 -15.5t-32 -6.5h-114q-72 0 -110 36.5t-38 96.5v437q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139
q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -33t-22 -106z" />
<glyph glyph-name="g" unicode="g" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84t43.5 -26q22 0 38.5 20t20.5 65h111v-314h-168v48q0 -7 -7.5 -15t-19 -15.5t-25 -12.5t-25.5 -5h-92q-57 0 -87.5 29t-30.5 77v350q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111
q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="M" unicode="M" horiz-adv-x="1061"
d="M243 413l-76 -413h-137l148 699h273l80 -375l79 375h273l148 -699h-280l-76 413l-76 -413h-280z" />
<glyph glyph-name="m" unicode="m" horiz-adv-x="849"
d="M194 330l-60 -330h-110l118 559h219l64 -300l63 300h218l119 -559h-224l-61 330l-61 -330h-224z" />
<glyph glyph-name="Q" unicode="Q"
d="M30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-31l110 -200h-190l-111 200h-51q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33
t-54 -33t-22 -106z" />
<glyph glyph-name="q" unicode="q" horiz-adv-x="503"
d="M24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-25l88 -160h-152l-89 160h-41q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26
q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="S" unicode="S"
d="M165 295q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v146q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -24t-22 -75q0 -21 8 -33t26 -18t47.5 -7.5t72.5 -1.5q24 0 48 -6.5
t43.5 -22t31.5 -40t12 -60.5v-146q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5q32 0 54 24t22 75q0 21 -8 33t-26 18t-47.5 7.5t-72.5 1.5z" />
<glyph glyph-name="s" unicode="s" horiz-adv-x="503"
d="M132 236q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v117q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -19t-17.5 -60q0 -17 6.5 -26.5t21 -14.5t38 -6t57.5 -1
q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-117q0 -29 -9.5 -48.5t-25 -31.5t-35 -17.5t-38.5 -5.5h-239q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v92h111q4 -45 20.5 -65.5t38.5 -20.5q26 0 43.5 19t17.5 60q0 17 -6.5 26.5t-21 14.5t-38 6t-57.5 1z" />
<glyph glyph-name="five" unicode="5"
d="M30 345v354h569v-136h-289v-109h154q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-196q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5q32 0 54 24t22 75q0 21 -4 41t-19 35t-46 24.5
t-85 9.5h-135z" />
<glyph glyph-name="six" unicode="6"
d="M30 129v441q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -24t-22 -75q0 -21 8 -33t26 -18t47.5 -7.5t72.5 -1.5q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-146q0 -36 -12 -60.5
t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5zM460 216q0 35 -22 57t-54 22q-28 0 -51 -19t-23 -60t23 -60.5t51 -19.5q32 0 54 22.5t22 57.5z" />
<glyph glyph-name="seven" unicode="7"
d="M110 0l209 550h-289v149h569v-149l-209 -550h-280z" />
<glyph glyph-name="eight" unicode="8"
d="M178 0q-72 0 -110 36.5t-38 96.5v68q0 33 6 57t18 41.5t30 30.5t43 23q-50 14 -73.5 49.5t-23.5 102.5v65q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-65q0 -67 -23.5 -102.5t-73.5 -49.5q25 -10 43 -23t30 -30.5t18 -41.5
t6 -57v-68q0 -60 -38 -96.5t-110 -36.5h-273zM310 491q0 -43 22 -58.5t54 -15.5t54 15.5t22 58.5t-22 57.5t-54 14.5t-54 -14.5t-22 -57.5zM310 210q0 -43 22 -63.5t54 -20.5t54 20.5t22 63.5t-22 62.5t-54 19.5t-54 -19.5t-22 -62.5z" />
<glyph glyph-name="nine" unicode="9"
d="M599 570v-441q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5q32 0 54 24t22 75q0 21 -8 33t-26 18t-47.5 7.5t-72.5 1.5q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v146
q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5zM169 483q0 -35 22 -57t54 -22q28 0 51 19t23 60t-23 60.5t-51 19.5q-32 0 -54 -22.5t-22 -57.5z" />
<glyph glyph-name="dollar" unicode="$"
d="M394 800v-101h70q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -24t-22 -75q0 -21 8 -33t26 -18t47.5 -7.5t72.5 -1.5q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-146q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-70
v-101h-139v101h-90q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5q32 0 54 24t22 75q0 21 -8 33t-26 18t-47.5 7.5t-72.5 1.5q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v146q0 36 12 60.5t31.5 40t43.5 22t48 6.5h90v101h139z" />
<glyph glyph-name="three" unicode="3"
d="M243 136q32 0 54 14.5t22 57.5t-22 58.5t-54 15.5v125q32 0 54 19.5t22 62.5t-22 63.5t-54 20.5q-28 0 -48.5 -30.5t-25.5 -87.5h-139v115q0 36 12 60.5t31.5 40t43.5 22t48 6.5h286q72 0 110 -36.5t38 -96.5v-68q0 -33 -6 -57t-18 -41.5t-30 -30t-43 -23.5
q50 -14 73.5 -49.5t23.5 -102.5v-65q0 -36 -12 -60.5t-31 -40t-43.5 -22t-48.5 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5z" />
<glyph glyph-name="four" unicode="4"
d="M319 699h280v-699h-280v295h-289v404h139v-295h150v295z" />
<glyph glyph-name="two" unicode="2"
d="M464 295q-43 0 -72.5 -1.5t-47.5 -7.5t-26 -18t-8 -33v-99h289v-136h-569v275q0 36 12 60.5t31.5 40t43.5 22t48 6.5q43 0 72.5 1.5t47.5 7.5t26 18t8 33q0 51 -22 75t-54 24q-28 0 -48.5 -25.5t-25.5 -82.5h-139v115q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299
q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-146q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5z" />
<glyph glyph-name="exclam" unicode="!" horiz-adv-x="280"
d="M230 277h-180l-20 422h220zM220 0h-160v161h160v-161z" />
<glyph glyph-name="colon" unicode=":" horiz-adv-x="280"
d="M220 0h-160v161h160v-161zM220 398h-160v161h160v-161z" />
<glyph glyph-name="comma" unicode="," horiz-adv-x="280"
d="M220 0l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="period" unicode="." horiz-adv-x="280"
d="M220 0h-160v161h160v-161z" />
<glyph glyph-name="question" unicode="?"
d="M410 0h-160v161h160v-161zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-96q0 -34 -12 -57.5t-29.5 -40t-38 -29t-38 -25t-29.5 -28t-12 -37.5h-210q0 33 6.5 53.5t16.5 34.5t21.5 24.5t21.5 22.5t16.5 28.5t6.5 43.5
q0 51 -22 75t-54 24q-28 0 -48.5 -25.5t-25.5 -82.5h-139v115z" />
<glyph glyph-name="equal" unicode="=" horiz-adv-x="409"
d="M30 508h349v-136h-349v136zM30 228h349v-136h-349v136z" />
<glyph glyph-name="less" unicode="&#x3c;" horiz-adv-x="409"
d="M30 338l349 170v-136l-180 -72l180 -72v-136l-349 170v76z" />
<glyph glyph-name="greater" unicode="&#x3e;" horiz-adv-x="409"
d="M379 262l-349 -170v136l180 72l-180 72v136l349 -170v-76z" />
<glyph glyph-name="numbersign" unicode="#" horiz-adv-x="642"
d="M502 699v-96h97v-126h-97v-124h97v-126h-97v-97h-126v97h-124v-97h-126v97h-96v126h96v124h-96v126h96v96h126v-96h124v96h126zM252 353h124v124h-124v-124z" />
<glyph glyph-name="quotedbl" unicode="&#x22;" horiz-adv-x="500"
d="M220 538l-40 -130h-60l20 130h-80v161h160v-161zM440 538l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quotesingle" unicode="'" horiz-adv-x="280"
d="M220 538l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quotesinglbase" unicode="&#x201a;" horiz-adv-x="280"
d="M220 0l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quotedblbase" unicode="&#x201e;" horiz-adv-x="500"
d="M220 0l-40 -130h-60l20 130h-80v161h160v-161zM440 0l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quoteright" unicode="&#x2019;" horiz-adv-x="280"
d="M220 538l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quoteleft" unicode="&#x2018;" horiz-adv-x="280"
d="M60 569l40 130h60l-20 -130h80v-161h-160v161z" />
<glyph glyph-name="quotedblright" unicode="&#x201d;" horiz-adv-x="500"
d="M220 538l-40 -130h-60l20 130h-80v161h160v-161zM440 538l-40 -130h-60l20 130h-80v161h160v-161z" />
<glyph glyph-name="quotedblleft" unicode="&#x201c;" horiz-adv-x="500"
d="M280 569l40 130h60l-20 -130h80v-161h-160v161zM60 569l40 130h60l-20 -130h80v-161h-160v161z" />
<glyph glyph-name="acute" unicode="&#xb4;" horiz-adv-x="280"
d="M120 598h-60l30 101h160z" />
<glyph glyph-name="aacute" unicode="&#xe1;" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13zM285 598h-60l30 101h160z" />
<glyph glyph-name="eacute" unicode="&#xe9;" horiz-adv-x="503"
d="M479 109v-109h-455v559h455v-109h-231v-116h231v-108h-231v-117h231zM273 598h-60l30 101h160z" />
<glyph glyph-name="iacute" unicode="&#xed;" horiz-adv-x="503"
d="M273 598h-60l30 101h160zM479 119v-119h-455v119h118v321h-118v119h455v-119h-113v-321h113z" />
<glyph glyph-name="oacute" unicode="&#xf3;" horiz-adv-x="503"
d="M273 598h-60l30 101h160zM24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26
q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="uacute" unicode="&#xfa;" horiz-adv-x="503"
d="M273 598h-60l30 101h160zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-453q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v453h224v-220z" />
<glyph glyph-name="grave" unicode="`" horiz-adv-x="280"
d="M60 699h160l30 -101h-60z" />
<glyph glyph-name="agrave" unicode="&#xe0;" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13zM135 699h160l30 -101h-60z" />
<glyph glyph-name="egrave" unicode="&#xe8;" horiz-adv-x="503"
d="M479 109v-109h-455v559h455v-109h-231v-116h231v-108h-231v-117h231zM123 699h160l30 -101h-60z" />
<glyph glyph-name="igrave" unicode="&#xec;" horiz-adv-x="503"
d="M123 699h160l30 -101h-60zM479 119v-119h-455v119h118v321h-118v119h455v-119h-113v-321h113z" />
<glyph glyph-name="ograve" unicode="&#xf2;" horiz-adv-x="503"
d="M123 699h160l30 -101h-60zM24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26
q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="ugrave" unicode="&#xf9;" horiz-adv-x="503"
d="M123 699h160l30 -101h-60zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-453q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v453h224v-220z" />
<glyph glyph-name="circumflex" unicode="&#x2c6;" horiz-adv-x="440"
d="M220 699l190 -101h-130l-60 41l-60 -41h-130z" />
<glyph glyph-name="asciicircum" unicode="^" horiz-adv-x="500"
d="M250 699l220 -371h-130l-90 191l-90 -191h-130z" />
<glyph glyph-name="acircumflex" unicode="&#xe2;" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13zM260 699l190 -101h-130l-60 41l-60 -41h-130z" />
<glyph glyph-name="ecircumflex" unicode="&#xea;" horiz-adv-x="503"
d="M260 699l190 -101h-130l-60 41l-60 -41h-130zM479 109v-109h-455v559h455v-109h-231v-116h231v-108h-231v-117h231z" />
<glyph glyph-name="icircumflex" unicode="&#xee;" horiz-adv-x="503"
d="M260 699l190 -101h-130l-60 41l-60 -41h-130zM479 119v-119h-455v119h118v321h-118v119h455v-119h-113v-321h113z" />
<glyph glyph-name="ocircumflex" unicode="&#xf4;" horiz-adv-x="503"
d="M260 699l190 -101h-130l-60 41l-60 -41h-130zM24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84
q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="ucircumflex" unicode="&#xfb;" horiz-adv-x="503"
d="M260 699l190 -101h-130l-60 41l-60 -41h-130zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-453q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v453h224v-220z" />
<glyph glyph-name="slash" unicode="/"
d="M220 -200h-190l379 1000h190z" />
<glyph glyph-name="backslash" unicode="\"
d="M30 800h190l379 -1000h-190z" />
<glyph glyph-name="semicolon" unicode=";" horiz-adv-x="280"
d="M220 0l-40 -130h-60l20 130h-80v161h160v-161zM220 398h-160v161h160v-161z" />
<glyph glyph-name="semicolon" unicode="&#x37e;" horiz-adv-x="280"
d="M220 0l-40 -130h-60l20 130h-80v161h160v-161zM220 398h-160v161h160v-161z" />
<glyph glyph-name="underscore" unicode="_" horiz-adv-x="349"
d="M30 -60h289v-76h-289v76z" />
<glyph glyph-name="OE" unicode="&#x152;" horiz-adv-x="918"
d="M888 136v-136h-710q-72 0 -110 36.5t-38 96.5v437q0 36 12 60.5t31.5 40t43.5 22t48 6.5h723v-136h-289v-145h289v-136h-289v-146h289zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="oe" unicode="&#x153;" horiz-adv-x="734"
d="M710 109v-109h-568q-57 0 -87.5 29t-30.5 77v350q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h578v-109h-231v-116h231v-108h-231v-117h231zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="bracketleft" unicode="[" horiz-adv-x="429"
d="M220 -64h179v-136h-369v1000h369v-136h-179v-728z" />
<glyph glyph-name="bracketright" unicode="]" horiz-adv-x="429"
d="M209 664h-179v136h369v-1000h-369v136h179v728z" />
<glyph glyph-name="plus" unicode="+" horiz-adv-x="449"
d="M272 484v-146h147v-96h-147v-147h-96v147h-146v96h146v146h96z" />
<glyph glyph-name="parenleft" unicode="(" horiz-adv-x="429"
d="M220 35q0 -51 22 -75t54 -24h103v-136h-221q-72 0 -110 36.5t-38 96.5v738q0 36 12 60.5t31.5 40t43.5 22t48 6.5h234v-136h-103q-32 0 -54 -24t-22 -75v-530z" />
<glyph glyph-name="parenright" unicode=")" horiz-adv-x="429"
d="M209 565q0 51 -22 75t-54 24h-103v136h234q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-738q0 -60 -38 -96.5t-110 -36.5h-221v136h103q32 0 54 24t22 75v530z" />
<glyph glyph-name="percent" unicode="%"
d="M30 590q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM80 590q0 -25 17 -42t42 -17t42 17t17 42t-17 42t-42 17t-42 -17t-17 -42
zM381 109q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM431 109q0 -25 17 -42t42 -17t42 17t17 42t-17 42t-42 17t-42 -17t-17 -42
zM160 0h-110l419 700h110z" />
<glyph glyph-name="guilsinglleft" unicode="&#x2039;" horiz-adv-x="409"
d="M30 318l349 241v-96l-180 -183l180 -184v-96l-349 242v76z" />
<glyph glyph-name="guilsinglright" unicode="&#x203a;" horiz-adv-x="409"
d="M379 242l-349 -242v96l180 184l-180 183v96l349 -241v-76z" />
<glyph glyph-name="guillemotleft" unicode="&#xab;" horiz-adv-x="509"
d="M30 318l209 241v-96l-100 -183l100 -184v-96l-209 242v76zM270 318l209 241v-96l-100 -183l100 -184v-96l-209 242v76z" />
<glyph glyph-name="guillemotright" unicode="&#xbb;" horiz-adv-x="509"
d="M479 242l-209 -242v96l100 184l-100 183v96l209 -241v-76zM239 242l-209 -242v96l100 184l-100 183v96l209 -241v-76z" />
<glyph glyph-name="sterling" unicode="&#xa3;"
d="M30 368h50v202q0 36 12 60.5t31.5 40t43.5 22t48 6.5h249q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -24t-22 -75v-96h109v-96h-109v-86h289v-186h-569v186h50v86h-50v96z" />
<glyph glyph-name="Oslash" unicode="&#xd8;"
d="M30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h257l47 101h110l-53 -113q31 -12 52 -40.5t21 -76.5v-437q0 -60 -38 -96.5t-110 -36.5h-245l-46 -100h-110l53 114q-36 16 -54.5 46.5t-18.5 72.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33
t-54 -33t-22 -106z" />
<glyph glyph-name="cent" unicode="&#xa2;" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84t43.5 -26q22 0 38.5 20t20.5 65h111v-208q0 -48 -30 -77t-88 -29h-196l-37 -80h-88l42 91q-29 13 -43.5 37.5t-14.5 57.5v350q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h206l37 81h88l-42 -90q25 -10 41.5 -33t16.5 -61v-92h-111q-4 45 -20.5 65.5
t-38.5 20.5q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="oslash" unicode="&#xf8;" horiz-adv-x="503"
d="M24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h206l37 81h88l-42 -90q25 -10 41.5 -33t16.5 -61v-350q0 -48 -30 -77t-88 -29h-196l-37 -80h-88l42 91q-29 13 -43.5 37.5t-14.5 57.5v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26
q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="Agrave" unicode="&#xc0;"
d="M462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16zM230 799.711h160l30 -92h-60z" />
<glyph glyph-name="Aacute" unicode="&#xc1;"
d="M462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16zM359.37 707.711h-59.6846l29.8418 92h159.16z" />
<glyph glyph-name="Acircumflex" unicode="&#xc2;"
d="M462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16zM347 799.889l190 -90.0029h-130l-60 36.5361l-60 -36.5361h-130z" />
<glyph glyph-name="Egrave" unicode="&#xc8;"
d="M230 799.711h160l30 -92h-60zM599 136v-136h-569v699h569v-136h-289v-145h289v-136h-289v-146h289z" />
<glyph glyph-name="Eacute" unicode="&#xc9;"
d="M359.37 707.711h-59.6846l29.8418 92h159.16zM599 136v-136h-569v699h569v-136h-289v-145h289v-136h-289v-146h289z" />
<glyph glyph-name="Ecircumflex" unicode="&#xca;"
d="M347 799.889l190 -90.0029h-130l-60 36.5361l-60 -36.5361h-130zM599 136v-136h-569v699h569v-136h-289v-145h289v-136h-289v-146h289z" />
<glyph glyph-name="Ograve" unicode="&#xd2;"
d="M230 799.711h160l30 -92h-60zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33
t-54 -33t-22 -106z" />
<glyph glyph-name="Oacute" unicode="&#xd3;"
d="M359.37 707.711h-59.6846l29.8418 92h159.16zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5
t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="Ocircumflex" unicode="&#xd4;"
d="M347 799.889l190 -90.0029h-130l-60 36.5361l-60 -36.5361h-130zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5t54 -32.5
t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="Ugrave" unicode="&#xd9;"
d="M230 799.711h160l30 -92h-60zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5v275h137v-566q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v566h280v-275z" />
<glyph glyph-name="Uacute" unicode="&#xda;"
d="M359.37 707.711h-59.6846l29.8418 92h159.16zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5v275h137v-566q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v566h280v-275z" />
<glyph glyph-name="Ucircumflex" unicode="&#xdb;"
d="M347 799.889l190 -90.0029h-130l-60 36.5361l-60 -36.5361h-130zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5v275h137v-566q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v566h280v-275z" />
<glyph glyph-name="AE" unicode="&#xc6;" horiz-adv-x="918"
d="M888 136v-136h-426v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h723v-136h-289v-145h289v-136h-289v-146h289zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16z" />
<glyph glyph-name="ae" unicode="&#xe6;" horiz-adv-x="734"
d="M710 109v-109h-340v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h578v-109h-231v-116h231v-108h-231v-117h231zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13z" />
<glyph glyph-name="questiondown" unicode="&#xbf;"
d="M219 699h160v-161h-160v161zM599 244h-139q-5 -57 -25.5 -82.5t-48.5 -25.5q-32 0 -54 24t-22 75q0 27 6.5 43.5t16.5 28.5t21.5 22.5t21.5 24.5t16.5 34.5t6.5 53.5h-210q0 -22 -12 -37.5t-29.5 -28t-38 -25t-38 -29t-29.5 -40t-12 -57.5v-96q0 -36 12 -60.5t31.5 -40
t43.5 -22t48 -6.5h299q24 0 48 6.5t43.5 22t31.5 40t12 60.5v115z" />
<glyph glyph-name="exclamdown" unicode="&#xa1;" horiz-adv-x="280"
d="M50 422h180l20 -422h-220zM60 699h160v-161h-160v161z" />
<glyph glyph-name="florin" unicode="&#x192;" horiz-adv-x="634"
d="M205 389v305q0 48 30 77t88 29h287v-119h-120q-26 0 -43.5 -26t-17.5 -84v-182h181v-119h-181v-364q0 -48 -30 -77t-88 -29h-287v119h120q26 0 43.5 26t17.5 84v241h-181v119h181z" />
<glyph glyph-name="dieresis" unicode="&#xa8;" horiz-adv-x="440"
d="M140 588h-110v111h110v-111zM410 588h-110v111h110v-111z" />
<glyph glyph-name="Adieresis" unicode="&#xc4;"
d="M462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16zM275.109 709.745h-88.8721v89.998h88.8721v-89.998zM493.248 709.745h-88.8711
v89.998h88.8711v-89.998z" />
<glyph glyph-name="Edieresis" unicode="&#xcb;"
d="M275.109 709.745h-88.8721v89.998h88.8721v-89.998zM493.248 709.745h-88.8711v89.998h88.8711v-89.998zM599 136v-136h-569v699h569v-136h-289v-145h289v-136h-289v-146h289z" />
<glyph glyph-name="Idieresis" unicode="&#xcf;"
d="M255.109 709.745h-88.8721v89.998h88.8721v-89.998zM473.248 709.745h-88.8711v89.998h88.8711v-89.998zM599 149v-149h-569v149h147v401h-147v149h569v-149h-142v-401h142z" />
<glyph glyph-name="Odieresis" unicode="&#xd6;"
d="M275.109 709.745h-88.8721v89.998h88.8721v-89.998zM493.248 709.745h-88.8711v89.998h88.8711v-89.998zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437
zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="Udieresis" unicode="&#xdc;"
d="M275.109 709.745h-88.8721v89.998h88.8721v-89.998zM493.248 709.745h-88.8711v89.998h88.8711v-89.998zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5v275h137v-566q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v566h280v-275z" />
<glyph glyph-name="Igrave" unicode="&#xcc;"
d="M200 799.711h160l30 -92h-60zM599 149v-149h-569v149h147v401h-147v149h569v-149h-142v-401h142z" />
<glyph glyph-name="Iacute" unicode="&#xcd;"
d="M349.37 707.711h-59.6846l29.8418 92h159.16zM599 149v-149h-569v149h147v401h-147v149h569v-149h-142v-401h142z" />
<glyph glyph-name="Icircumflex" unicode="&#xce;"
d="M337 799.889l190 -90.0029h-130l-60 36.5361l-60 -36.5361h-130zM599 149v-149h-569v149h147v401h-147v149h569v-149h-142v-401h142z" />
<glyph glyph-name="adieresis" unicode="&#xe4;" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13zM180 588h-110v111h110v-111zM450 588h-110v111h110v-111z" />
<glyph glyph-name="edieresis" unicode="&#xeb;" horiz-adv-x="503"
d="M180 588h-110v111h110v-111zM450 588h-110v111h110v-111zM479 109v-109h-455v559h455v-109h-231v-116h231v-108h-231v-117h231z" />
<glyph glyph-name="idieresis" unicode="&#xef;" horiz-adv-x="503"
d="M170 588h-110v111h110v-111zM440 588h-110v111h110v-111zM479 119v-119h-455v119h118v321h-118v119h455v-119h-113v-321h113z" />
<glyph glyph-name="odieresis" unicode="&#xf6;" horiz-adv-x="503"
d="M180 588h-110v111h110v-111zM450 588h-110v111h110v-111zM24 456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26
t18 84q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="udieresis" unicode="&#xfc;" horiz-adv-x="503"
d="M180 588h-110v111h110v-111zM450 588h-110v111h110v-111zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-453q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v453h224v-220z" />
<glyph glyph-name="ydieresis" unicode="&#xff;" horiz-adv-x="503"
d="M180 588h-110v111h110v-111zM450 588h-110v111h110v-111zM142 0v302l-117 257h152l75 -179l75 179h152l-113 -249v-310h-224z" />
<glyph glyph-name="Ydieresis" unicode="&#x178;"
d="M265.109 709.745h-88.8721v89.998h88.8721v-89.998zM483.248 709.745h-88.8711v89.998h88.8711v-89.998zM177 0v378l-146 321h190l94 -224l94 224h190l-142 -312v-387h-280z" />
<glyph glyph-name="endash" unicode="&#x2013;"
d="M30 338h569v-96h-569v96z" />
<glyph glyph-name="emdash" unicode="&#x2014;" horiz-adv-x="861"
d="M30 338h801v-96h-801v96z" />
<glyph glyph-name="K" unicode="K"
d="M310 0h-280v699h280v-279q20 0 46 6t49.5 25.5t40 56.5t16.5 100v91h137v-107q0 -74 -23.5 -111.5t-73.5 -60.5q42 -15 69.5 -51.5t27.5 -99.5v-269h-137v91q0 70 -15 109t-37.5 57.5t-49.5 22.5t-50 4v-284z" />
<glyph glyph-name="hyphen" unicode="-" horiz-adv-x="349"
d="M30 358h289v-136h-289v136z" />
<glyph glyph-name="bar" unicode="|" horiz-adv-x="250"
d="M220 -200h-190v1000h190v-1000z" />
<glyph glyph-name="braceleft" unicode="{" horiz-adv-x="541"
d="M30 217v186h36q32 0 54 24t22 75v169q0 36 12 60.5t31.5 40t43.5 22t48 6.5h234v-136h-103q-32 0 -54 -24t-22 -75v-530q0 -51 22 -75t54 -24h103v-136h-221q-72 0 -110 36.5t-38 96.5v185q0 51 -22 75t-54 24h-36z" />
<glyph glyph-name="braceright" unicode="}" horiz-adv-x="541"
d="M475 217q-32 0 -54 -24t-22 -75v-185q0 -60 -38 -96.5t-110 -36.5h-221v136h103q32 0 54 24t22 75v530q0 51 -22 75t-54 24h-103v136h234q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-169q0 -51 22 -75t54 -24h36v-186h-36z" />
<glyph glyph-name="yen" unicode="&#xa5;"
d="M177 0v152h-147v86h147v94h-147v86h129l-128 281h190l94 -224l94 224h190l-128 -281h128v-86h-142v-94h142v-86h-142v-152h-280z" />
<glyph glyph-name="at" unicode="@" horiz-adv-x="822"
d="M587 112v365q0 23 -7.5 38.5t-20 25.5t-28 14t-30.5 4h-191q-15 0 -30.5 -4t-28 -14t-20.5 -25.5t-8 -38.5v-365h179v162h98v-162h87zM500 373h-98v10q0 48 14.5 68.5t34.5 20.5t34.5 -20.5t14.5 -68.5v-10zM20 333q0 81 30.5 152t84 124.5t124.5 84t152 30.5t152 -30.5
t124.5 -84t84 -124.5t30.5 -152t-30.5 -152t-84 -124.5t-124.5 -84t-152 -30.5t-152 30.5t-124.5 84t-84 124.5t-30.5 152z" />
<glyph glyph-name="asterisk" unicode="*" horiz-adv-x="468"
d="M282 699v-429h-96v429h96zM282 699v-429h-96v429h96zM396 633l48 -83l-371 -214l-48 83zM72 633l371 -214l-48 -83l-371 214z" />
<glyph glyph-name="logicalnot" unicode="&#xac;" horiz-adv-x="449"
d="M30 388h389v-216h-79v120h-310v96z" />
<glyph glyph-name="D" unicode="D"
d="M30 699h434q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-421v699zM310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="U" unicode="U"
d="M310 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5v275h137v-566q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v566h280v-275z" />
<glyph glyph-name="C" unicode="C"
d="M310 424q0 -73 22 -105.5t54 -32.5q28 0 48.5 25t25.5 82h139v-260q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5
q-32 0 -54 -33t-22 -106z" />
<glyph glyph-name="trademark" unicode="&#x2122;" horiz-adv-x="1082"
d="M293 252h-179v352h-95v95h364v-95h-90v-352zM558 516l-48 -264h-88l94 447h175l51 -240l51 240h174l95 -447h-179l-49 264l-48 -264h-180z" />
<glyph glyph-name="mu" unicode="&#xb5;" horiz-adv-x="503"
d="M248 -160h-224v719h224v-220q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-559h-231v-160z" />
<glyph glyph-name="mu" unicode="&#x3bc;" horiz-adv-x="503"
d="M248 -160h-224v719h224v-220q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84v220h109v-559h-231v-160z" />
<glyph glyph-name="ordmasculine" unicode="&#xba;" horiz-adv-x="402"
d="M19 617q0 23 8 38.5t20.5 25.5t28 14t30.5 4h191q15 0 30.5 -4t28 -14t20 -25.5t7.5 -38.5v-280q0 -38 -24 -61.5t-70 -23.5h-175q-46 0 -70.5 23.5t-24.5 61.5v280zM198 523q0 -46 14.5 -67t34.5 -21t34.5 21t14.5 67q0 48 -14.5 68.5t-34.5 20.5t-34.5 -20.5
t-14.5 -68.5z" />
<glyph glyph-name="ordfeminine" unicode="&#xaa;" horiz-adv-x="402"
d="M296 252v162h-98v-162h-179v365q0 23 8 38.5t20.5 25.5t28 14t30.5 4h191q15 0 30.5 -4t28 -14t20 -25.5t7.5 -38.5v-365h-87zM296 523q0 48 -14.5 68.5t-34.5 20.5t-34.5 -20.5t-14.5 -68.5v-10h98v10z" />
<glyph glyph-name="copyright" unicode="&#xa9;" horiz-adv-x="822"
d="M402 383q0 48 14.5 68.5t34.5 20.5q17 0 30.5 -16.5t16.5 -52.5h89v74q0 23 -7.5 38.5t-20 25.5t-28 14t-30.5 4h-191q-15 0 -30.5 -4t-28 -14t-20.5 -25.5t-8 -38.5v-280q0 -38 24.5 -61.5t70.5 -23.5h175q46 0 70 23.5t24 61.5v166h-89q-3 -36 -16.5 -52t-30.5 -16
q-20 0 -34.5 21t-14.5 67zM20 333q0 81 30.5 152t84 124.5t124.5 84t152 30.5t152 -30.5t124.5 -84t84 -124.5t30.5 -152t-30.5 -152t-84 -124.5t-124.5 -84t-152 -30.5t-152 30.5t-124.5 84t-84 124.5t-30.5 152z" />
<glyph glyph-name="registered" unicode="&#xae;" horiz-adv-x="822"
d="M402 258q11 0 27.5 -3.5t32 -14.5t27 -32.5t11.5 -56.5v-39h87v70q0 17 -4.5 33.5t-12.5 30t-18.5 23.5t-21.5 13q23 6 40 24.5t17 53.5v117q0 23 -7.5 38.5t-20 25.5t-28 14t-30.5 4h-278v-447h179v146zM402 409q0 32 14.5 47.5t34.5 15.5t34.5 -15.5t14.5 -47.5
t-14.5 -47.5t-34.5 -15.5t-34.5 15.5t-14.5 47.5zM20 333q0 81 30.5 152t84 124.5t124.5 84t152 30.5t152 -30.5t124.5 -84t84 -124.5t30.5 -152t-30.5 -152t-84 -124.5t-124.5 -84t-152 -30.5t-152 30.5t-124.5 84t-84 124.5t-30.5 152z" />
<glyph glyph-name="degree" unicode="&#xb0;" horiz-adv-x="278"
d="M30 590q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM80 590q0 -25 17 -42t42 -17t42 17t17 42t-17 42t-42 17t-42 -17t-17 -42
z" />
<glyph glyph-name="periodcentered" unicode="&#xb7;" horiz-adv-x="280"
d="M220 210h-160v161h160v-161z" />
<glyph glyph-name="periodcentered" unicode="&#x2219;" horiz-adv-x="280"
d="M220 210h-160v161h160v-161z" />
<glyph glyph-name="germandbls" unicode="&#xdf;" horiz-adv-x="1006"
d="M132 236q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v117q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -19t-17.5 -60q0 -17 6.5 -26.5t21 -14.5t38 -6t57.5 -1
q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-117q0 -29 -9.5 -48.5t-25 -31.5t-35 -17.5t-38.5 -5.5h-239q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v92h111q4 -45 20.5 -65.5t38.5 -20.5q26 0 43.5 19t17.5 60q0 17 -6.5 26.5t-21 14.5t-38 6t-57.5 1zM635 236
q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v117q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -19t-17.5 -60q0 -17 6.5 -26.5t21 -14.5t38 -6t57.5 -1q19 0 38.5 -5.5
t35 -17.5t25 -31.5t9.5 -48.5v-117q0 -29 -9.5 -48.5t-25 -31.5t-35 -17.5t-38.5 -5.5h-239q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v92h111q4 -45 20.5 -65.5t38.5 -20.5q26 0 43.5 19t17.5 60q0 17 -6.5 26.5t-21 14.5t-38 6t-57.5 1z" />
<glyph glyph-name="aring" unicode="&#xe5;" horiz-adv-x="503"
d="M211 642q0 24 16.5 40.5t40.5 16.5q23 0 39.5 -16.5t16.5 -40.5q0 -23 -16.5 -39.5t-39.5 -16.5q-24 0 -40.5 16.5t-16.5 39.5zM237 642q0 -13 8.5 -21.5t22.5 -8.5q13 0 21.5 8.5t8.5 21.5q0 14 -8.5 22.5t-21.5 8.5q-14 0 -22.5 -8.5t-8.5 -22.5zM370 0v202h-122v-202
h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13z" />
<glyph glyph-name="Aring" unicode="&#xc5;"
d="M324.425 753.683q0 19.54 13.4326 32.9736q13.4336 13.4336 32.9736 13.4336q18.7256 0 32.1582 -13.4336q13.4336 -13.4336 13.4336 -32.9736q0 -18.7246 -13.4336 -32.1582q-13.4326 -13.4336 -32.1582 -13.4336q-19.54 0 -32.9736 13.4336
q-13.4326 13.4336 -13.4326 32.1582zM345.592 753.683q0 -10.584 6.9209 -17.5039q6.91992 -6.91992 18.3184 -6.91992q10.584 0 17.5039 6.91992t6.91992 17.5039q0 11.3984 -6.91992 18.3184q-6.91992 6.9209 -17.5039 6.9209q-11.3984 0 -18.3184 -6.9209
q-6.9209 -6.91992 -6.9209 -18.3184zM462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16z" />
<glyph glyph-name="divide" unicode="&#xf7;" horiz-adv-x="449"
d="M30 242h389v96h-389v-96zM280 0h-120v121h120v-121zM280 438h-120v121h120v-121z" />
<glyph glyph-name="plusminus" unicode="&#xb1;" horiz-adv-x="449"
d="M30 208h389v-96h-389v96zM272 614v-146h147v-96h-147v-147h-96v147h-146v96h146v146h96z" />
<glyph glyph-name="perthousand" unicode="&#x2030;" horiz-adv-x="907"
d="M30 590q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM80 590q0 -25 17 -42t42 -17t42 17t17 42t-17 42t-42 17t-42 -17t-17 -42
zM381 109q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM431 109q0 -25 17 -42t42 -17t42 17t17 42t-17 42t-42 17t-42 -17t-17 -42
zM160 0h-110l419 700h110zM659 109q0 23 8.5 42.5t23.5 34.5t35 23.5t42 8.5q23 0 42.5 -8.5t34.5 -23.5t23.5 -34.5t8.5 -42.5q0 -22 -8.5 -42t-23.5 -35t-34.5 -23.5t-42.5 -8.5q-22 0 -42 8.5t-35 23.5t-23.5 35t-8.5 42zM709 109q0 -25 17 -42t42 -17t42 17t17 42
t-17 42t-42 17t-42 -17t-17 -42z" />
<glyph glyph-name="ampersand" unicode="&#x26;" horiz-adv-x="659"
d="M599 129q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48.5 6.5t-43.5 22t-31 40t-12 60.5v65q0 67 23.5 102.5t73.5 49.5q-25 11 -43 23.5t-30 30t-18 41.5t-6 57v68q0 60 38 96.5t110 36.5h286q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-75h-139
q-5 42 -25.5 60t-48.5 18q-32 0 -54 -20.5t-22 -63.5t22 -62.5t54 -19.5h243v-125h-30v-153zM310 208q0 -43 22 -57.5t54 -14.5q28 0 49 21t25 77v48h-74q-32 0 -54 -15.5t-22 -58.5z" />
<glyph glyph-name="macron" unicode="&#xaf;" horiz-adv-x="503"
d="M24 699h455v-76h-455v76z" />
<glyph glyph-name="macron" unicode="&#x2c9;" horiz-adv-x="503"
d="M24 699h455v-76h-455v76z" />
<glyph glyph-name="ellipsis" unicode="&#x2026;" horiz-adv-x="840"
d="M220 0h-160v161h160v-161zM500 0h-160v161h160v-161zM780 0h-160v161h160v-161z" />
<glyph glyph-name="asciitilde" unicode="~" horiz-adv-x="664"
d="M71 418q15 57 48.5 85.5t77.5 28.5q39 0 71.5 -7.5t59.5 -16.5t49 -16.5t38 -7.5q32 0 51.5 15t25.5 38l11 42l131 -35l-41 -156q-15 -57 -48.5 -85.5t-77.5 -28.5q-39 0 -71.5 7.5t-59.5 17t-48.5 17t-38.5 7.5q-32 0 -51.5 -15.5t-25.5 -38.5l-11 -42l-131 35z" />
<glyph glyph-name="tilde" unicode="&#x2dc;" horiz-adv-x="436"
d="M55 700q10 35 31 53t48 18q24 0 44.5 -4.5t37 -10.5t30 -10.5t23.5 -4.5q20 0 32 9.5t16 23.5l7 26l82 -22l-26 -97q-9 -35 -30 -53t-48 -18q-24 0 -44.5 5t-37 10.5t-30 10.5t-24.5 5q-20 0 -32 -10t-16 -24l-7 -26l-81 22z" />
<glyph glyph-name="atilde" unicode="&#xe3;" horiz-adv-x="503"
d="M370 0v202h-122v-202h-224v456q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-456h-109zM370 339q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85v-13h122v13zM85 700q10 35 31 53t48 18q24 0 44.5 -4.5t37 -10.5t30 -10.5
t23.5 -4.5q20 0 32 9.5t16 23.5l7 26l82 -22l-26 -97q-9 -35 -30 -53t-48 -18q-24 0 -44.5 5t-37 10.5t-30 10.5t-24.5 5q-20 0 -32 -10t-16 -24l-7 -26l-81 22z" />
<glyph glyph-name="ntilde" unicode="&#xf1;" horiz-adv-x="503"
d="M85 700q10 35 31 53t48 18q24 0 44.5 -4.5t37 -10.5t30 -10.5t23.5 -4.5q20 0 32 9.5t16 23.5l7 26l82 -22l-26 -97q-9 -35 -30 -53t-48 -18q-24 0 -44.5 5t-37 10.5t-30 10.5t-24.5 5q-20 0 -32 -10t-16 -24l-7 -26l-81 22zM248 0h-224v559h224v-105q19 0 38 -6.5
t35.5 -18.5t29.5 -28t19 -35v193h109v-559h-109v73q0 56 -14.5 87t-34 46t-40 18t-33.5 3v-227z" />
<glyph glyph-name="otilde" unicode="&#xf5;" horiz-adv-x="503"
d="M85 700q10 35 31 53t48 18q24 0 44.5 -4.5t37 -10.5t30 -10.5t23.5 -4.5q20 0 32 9.5t16 23.5l7 26l82 -22l-26 -97q-9 -35 -30 -53t-48 -18q-24 0 -44.5 5t-37 10.5t-30 10.5t-24.5 5q-20 0 -32 -10t-16 -24l-7 -26l-81 22zM24 456q0 29 9.5 48.5t25 31.5t35 17.5
t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350zM248 339q0 -58 17.5 -84t43.5 -26q25 0 43 26t18 84q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="cedilla" unicode="&#xb8;" horiz-adv-x="218"
d="M124 -116q0 15 -9 19t-24 4h-28q-15 0 -24 9t-9 24v97h70v-77h55q15 0 24 -9t9 -24v-93q0 -15 -9 -24t-24 -9h-124v55h60q15 0 24 4t9 19v5z" />
<glyph glyph-name="ccedilla" unicode="&#xe7;" horiz-adv-x="503"
d="M248 339q0 -58 17.5 -84t43.5 -26q22 0 38.5 20t20.5 65h111v-208q0 -48 -30 -77t-88 -29h-219q-57 0 -87.5 29t-30.5 77v350q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5
q-26 0 -43.5 -26t-17.5 -85zM284 -116q0 15 -9 19t-24 4h-28q-15 0 -24 9t-9 24v97h70v-77h55q15 0 24 -9t9 -24v-93q0 -15 -9 -24t-24 -9h-124v55h60q15 0 24 4t9 19v5z" />
<glyph glyph-name="Ccedilla" unicode="&#xc7;" horiz-adv-x="503"
d="M344 -116q0 15 -9 19t-24 4h-28q-15 0 -24 9t-9 24v97h70v-77h55q15 0 24 -9t9 -24v-93q0 -15 -9 -24t-24 -9h-124v55h60q15 0 24 4t9 19v5zM310 424q0 -73 22 -105.5t54 -32.5q28 0 48.5 25t25.5 82h139v-260q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5
v437q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -33t-22 -106z" />
<glyph glyph-name="Ntilde" unicode="&#xd1;"
d="M310 0h-280v699h280v-131q23 0 47 -8.5t44.5 -23t36.5 -35t24 -44.5v242h137v-699h-137v91q0 70 -18 109t-42.5 57.5t-50 22.5t-41.5 4v-284zM204.853 758.073q9.97266 14.7041 30.917 22.2656q20.9434 7.5625 47.8711 7.5625q23.9355 0 44.3799 -1.89062
q20.4453 -1.89062 36.9004 -4.41113q16.4561 -2.52051 29.9199 -4.41113t23.4365 -1.89062q19.9463 0 31.9141 3.99121q11.9678 3.99023 15.957 9.87207l6.98145 10.9229l81.7803 -9.24219l-25.9307 -40.75q-8.97559 -14.7041 -29.9189 -22.2656
q-20.9443 -7.5625 -47.8711 -7.5625q-23.9355 0 -44.3809 2.10059t-36.9004 4.41113q-16.4561 2.31055 -29.9199 4.41113t-24.4336 2.10059q-19.9473 0 -31.9141 -4.20117q-11.9688 -4.20117 -15.957 -10.082l-6.98145 -10.9229l-80.7822 9.24219z" />
<glyph glyph-name="Atilde" unicode="&#xc3;"
d="M164.853 758.073q9.97266 14.7041 30.917 22.2656q20.9434 7.5625 47.8711 7.5625q23.9355 0 44.3799 -1.89062q20.4453 -1.89062 36.9004 -4.41113q16.4561 -2.52051 29.9199 -4.41113t23.4365 -1.89062q19.9463 0 31.9141 3.99121q11.9678 3.99023 15.957 9.87207
l6.98145 10.9229l81.7803 -9.24219l-25.9307 -40.75q-8.97559 -14.7041 -29.9189 -22.2656q-20.9443 -7.5625 -47.8711 -7.5625q-23.9355 0 -44.3809 2.10059t-36.9004 4.41113q-16.4561 2.31055 -29.9199 4.41113t-24.4336 2.10059q-19.9473 0 -31.9141 -4.20117
q-11.9688 -4.20117 -15.957 -10.082l-6.98145 -10.9229l-80.7822 9.24219zM462 0v252h-152v-252h-280v570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-570h-137zM462 424q0 73 -22 106t-54 33t-54 -33t-22 -106v-16h152v16z" />
<glyph glyph-name="Otilde" unicode="&#xd5;"
d="M164.853 758.073q9.97266 14.7041 30.917 22.2656q20.9434 7.5625 47.8711 7.5625q23.9355 0 44.3799 -1.89062q20.4453 -1.89062 36.9004 -4.41113q16.4561 -2.52051 29.9199 -4.41113t23.4365 -1.89062q19.9463 0 31.9141 3.99121q11.9678 3.99023 15.957 9.87207
l6.98145 10.9229l81.7803 -9.24219l-25.9307 -40.75q-8.97559 -14.7041 -29.9189 -22.2656q-20.9443 -7.5625 -47.8711 -7.5625q-23.9355 0 -44.3809 2.10059t-36.9004 4.41113q-16.4561 2.31055 -29.9199 4.41113t-24.4336 2.10059q-19.9473 0 -31.9141 -4.20117
q-11.9688 -4.20117 -15.957 -10.082l-6.98145 -10.9229l-80.7822 9.24219zM30 570q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-273q-72 0 -110 36.5t-38 96.5v437zM310 424q0 -73 22 -105.5
t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="bullet" unicode="&#x2022;" horiz-adv-x="292"
d="M30 404q0 36 9.5 62.5t25.5 43.5t37 25t44 8t44 -8t37 -25t25.5 -43.5t9.5 -62.5t-9.5 -62.5t-25.5 -43t-37 -24.5t-44 -8t-44 8t-37 24.5t-25.5 43t-9.5 62.5z" />
<glyph glyph-name="dagger" unicode="&#x2020;" horiz-adv-x="634"
d="M205 609v191h224v-191h181v-119h-184l-27 -690h-164l-28 690h-183v119h181z" />
<glyph glyph-name="daggerdbl" unicode="&#x2021;" horiz-adv-x="634"
d="M205 609v191h224v-191h181v-119h-189l-22 -190l22 -190h189v-119h-181v-191h-224v191h-181v119h189l22 190l-22 190h-189v119h181z" />
<glyph glyph-name="section" unicode="&#xa7;" horiz-adv-x="503"
d="M24 520v177q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -19t-17.5 -60q0 -57 27 -82.5t96 -25.5q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-202q0 -36 -16 -60
q16 -24 16 -59v-177q0 -29 -9.5 -48.5t-25 -31.5t-35 -17.5t-38.5 -5.5h-239q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v92h111q4 -45 20.5 -65.5t38.5 -20.5q26 0 43.5 19t17.5 60q0 57 -27 82.5t-96 25.5q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v202q0 18 4 33
t11 26q-7 11 -11 26t-4 34zM327 301q0 35 -22 57t-54 22q-28 0 -51 -19t-23 -60t23 -60.5t51 -19.5q32 0 54 22.5t22 57.5z" />
<glyph glyph-name="Thorn" unicode="&#xde;"
d="M310 364q0 -73 22 -106t54 -33t54 33t22 106t-22 106t-54 33t-54 -33t-22 -106zM310 89v-89h-280v699h280v-60h154q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-292q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-154z" />
<glyph glyph-name="thorn" unicode="&#xfe;" horiz-adv-x="503"
d="M248 291q0 -58 17.5 -84.5t43.5 -26.5q25 0 43 26.5t18 84.5q0 59 -18 85t-43 26q-26 0 -43.5 -26t-17.5 -85zM248 71v-71h-224v559h224v-48h123q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-234q0 -28 -9.5 -48t-25 -32t-35 -17.5t-38.5 -5.5h-123z" />
<glyph glyph-name="yacute" unicode="&#xfd;" horiz-adv-x="503"
d="M273 598h-60l30 101h160zM142 0v302l-117 257h152l75 -179l75 179h152l-113 -249v-310h-224z" />
<glyph glyph-name="Euro" unicode="&#x20ac;"
d="M30 318h100v54h-80v86h80v112q0 36 12 60.5t31.5 40t43.5 22t48 6.5h186q72 0 110 -36.5t38 -96.5v-77h-120q0 25 -5 41.5t-17 26t-32.5 13t-51.5 3.5q-50 0 -74.5 -30t-29.5 -85h140v-86h-140v-54h120v-86h-119q6 -51 30.5 -73.5t72.5 -22.5q31 0 51.5 3t32.5 11.5
t17 22.5t5 35h120v-79q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-199q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v103h-100v86z" />
<glyph glyph-name="Yacute" unicode="&#xdd;"
d="M359.37 707.711h-59.6846l29.8418 92h159.16zM177 0v378l-146 321h190l94 -224l94 224h190l-142 -312v-387h-280z" />
<glyph glyph-name="Eth" unicode="&#xd0;" horiz-adv-x="730"
d="M30 455h101v244h434q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-437q0 -60 -38 -96.5t-110 -36.5h-421v319h-101v136zM411 424q0 -73 22 -105.5t54 -32.5t54 32.5t22 105.5t-22 106t-54 33t-54 -33t-22 -106z" />
<glyph glyph-name="eth" unicode="&#xf0;" horiz-adv-x="584"
d="M24 364h81v195h347q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-350q0 -48 -30.5 -77t-87.5 -29h-337v255h-81v109zM329 339q0 -58 17.5 -84t43.5 -26q25 0 42.5 26t17.5 84q0 59 -17.5 85t-42.5 26q-26 0 -43.5 -26t-17.5 -85z" />
<glyph glyph-name="Zcaron" unicode="&#x17d;"
d="M157 799.889h130l60 -36.5361l60 36.5361h130l-190 -90.0029zM599 149v-149h-569v149l289 401h-289v149h569v-149l-289 -401h289z" />
<glyph glyph-name="zcaron" unicode="&#x17e;" horiz-adv-x="503"
d="M70 699h130l60 -41l60 41h130l-190 -101zM479 119v-119h-455v119l231 321h-231v119h455v-119l-231 -321h231z" />
<glyph glyph-name="Scaron" unicode="&#x160;"
d="M157 799.889h130l60 -36.5361l60 36.5361h130l-190 -90.0029zM165 295q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v146q0 36 12 60.5t31.5 40t43.5 22t48 6.5h299q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-115h-139q-5 57 -25.5 82.5t-48.5 25.5q-32 0 -54 -24t-22 -75
q0 -21 8 -33t26 -18t47.5 -7.5t72.5 -1.5q24 0 48 -6.5t43.5 -22t31.5 -40t12 -60.5v-146q0 -36 -12 -60.5t-31.5 -40t-43.5 -22t-48 -6.5h-299q-24 0 -48 6.5t-43.5 22t-31.5 40t-12 60.5v115h139q5 -57 25.5 -82.5t48.5 -25.5q32 0 54 24t22 75q0 21 -8 33t-26 18
t-47.5 7.5t-72.5 1.5z" />
<glyph glyph-name="scaron" unicode="&#x161;" horiz-adv-x="503"
d="M70 699h130l60 -41l60 41h130l-190 -101zM132 236q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v117q0 29 9.5 48.5t25 31.5t35 17.5t38.5 5.5h239q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-92h-111q-4 45 -20.5 65.5t-38.5 20.5q-26 0 -43.5 -19t-17.5 -60
q0 -17 6.5 -26.5t21 -14.5t38 -6t57.5 -1q19 0 38.5 -5.5t35 -17.5t25 -31.5t9.5 -48.5v-117q0 -29 -9.5 -48.5t-25 -31.5t-35 -17.5t-38.5 -5.5h-239q-19 0 -38.5 5.5t-35 17.5t-25 31.5t-9.5 48.5v92h111q4 -45 20.5 -65.5t38.5 -20.5q26 0 43.5 19t17.5 60
q0 17 -6.5 26.5t-21 14.5t-38 6t-57.5 1z" />
<glyph glyph-name="twosuperior" unicode="&#xb2;" horiz-adv-x="402"
d="M297 441q-55 0 -77 -6t-22 -33v-63h185v-87h-364v176q0 23 8 39t20.5 25.5t28 13.5t30.5 4q54 0 76 6t22 33q0 32 -14.5 47.5t-34.5 15.5q-17 0 -30.5 -16.5t-16.5 -52.5h-89v74q0 23 8 38.5t20.5 25.5t28 14t30.5 4h191q15 0 30.5 -4t28 -14t20 -25.5t7.5 -38.5v-94
q0 -23 -7.5 -38.5t-20 -25.5t-28 -14t-30.5 -4z" />
<glyph glyph-name="threesuperior" unicode="&#xb3;" horiz-adv-x="402"
d="M155 339q20 0 34.5 9.5t14.5 36.5q0 28 -14.5 38t-34.5 10v80q20 0 34.5 12.5t14.5 39.5q0 28 -14.5 40.5t-34.5 12.5q-17 0 -30.5 -19.5t-16.5 -55.5h-89v74q0 23 8 38.5t20.5 25.5t28 14t30.5 4h183q46 0 70 -23.5t24 -61.5v-44q0 -42 -15 -62.5t-46 -33.5
q31 -9 46 -32t15 -66v-42q0 -23 -7.5 -38.5t-20 -25.5t-28 -14t-30.5 -4h-191q-15 0 -30.5 4t-28 14t-20.5 25.5t-8 38.5v74h89q3 -36 16.5 -52.5t30.5 -16.5z" />
<glyph glyph-name="onesuperior" unicode="&#xb9;" horiz-adv-x="402"
d="M383 347v-95h-364v95h95v257l-95 -32v95l95 32h179v-352h90z" />
<glyph glyph-name="brokenbar" unicode="&#xa6;" horiz-adv-x="250"
d="M220 -200h-190v420h190v-420zM30 800h190v-420h-190v420z" />
<glyph glyph-name="multiply" unicode="&#xd7;" horiz-adv-x="449"
d="M86 496l138 -138l140 139l67 -68l-139 -139l139 -140l-67 -67l-140 139l-138 -139l-68 68l138 139l-138 138z" />
</font>
</defs></svg>

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Binary file not shown.

3
cmd.php Normal file
View file

@ -0,0 +1,3 @@
<?php
//TODO: add CLI commands for handling certain tasks.

23
composer.json Normal file
View file

@ -0,0 +1,23 @@
{
"name": "subcon/frontpage",
"description": "Subcon town frontpage",
"type": "project",
"license": "BOML-1.0.0",
"autoload": {
"psr-4": {
"Subcon\\Frontpage\\": "src/",
"Subcon\\Zap\\": "libs/zap/"
}
},
"authors": [
{
"name": "Fristi",
"email": "fristi@subcon.town"
}
],
"minimum-stability": "stable",
"require": {
"league/commonmark": "^2.4",
"symfony/yaml": "^6.3"
}
}

27
config/application.php Normal file
View file

@ -0,0 +1,27 @@
<?php return [
/*
* Application configuration
*
* These settings contain the base configuration of your application.
* It is recommended that you set encryption.key as this key will be used
* to provide data encryption to your application.
*/
'application' => [
'name' => 'Subcon Town Frontpage',
'url' => 'http://localhost',
'debug' => false
],
'encryption' => [
'key' => ''
],
'frontpage' => [
'articles' => [
'2023/frontpage_updates_and_more.md',
'2023/plans_for_the_year.md'
]
]
];

View file

@ -1,138 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Subcon.town - Happy Dreams!</title>
<link rel="shortcut icon" type="image/jpg" href="assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="style.css?v=1.0.0" rel="stylesheet"/>
</head>
<body>
<div class="container">
<header>
<img src="assets/subcon/logo_128.png" alt="Subcon Town" />
<ul>
<li><a href="#home">Home</a></li>
<li><a href="#services">Services</a></li>
<li><a href="#faq">FAQ</a></li>
<li><a href="#contact">Contact</a></li>
</ul>
</header>
<main>
<div class="content" id="home">
<p>
Welcome to subcon.town! Subcon is a small server ran by <a href="#">@fristi</a> that
hosts some personal and small community services.
</p>
</div>
<hr />
<div class="content" id="services">
<h1>Services</h1>
<p>
Subcon.town currently hosts the following services:
</p>
<table>
<tbody>
<tr> <td>Bookstack wiki:</td> <td><a href="https://wiki.subcon.town">wiki.subcon.town</a></td> </tr>
<tr> <td>Gitea:</td> <td><a href="https://vcs.subcon.town">vcs.subcon.town</a></td> </tr>
<tr> <td>Minecraft:</td> <td class="muted">OFFLINE</td> </tr>
<tr> <td>Minecraft map:</td> <td class="muted">OFFLINE</td> </tr>
<tr> <td>Nextcloud:</td> <td><a href="https://cloud.subcon.town">cloud.subcon.town</a></td> </tr>
</tbody>
</table>
<p>
Additionally, subcon.town also serves the webpages for <a href="https://comfitu.re">comfitu.re</a>
and its subdomains.
</p>
</div>
<hr />
<div class="content" id="faq">
<h1>FAQ</h1>
<p>
<strong>Q</strong>: <em>What is Subcon Town?</em>
<br/>
<strong>A</strong>: This server you're talking to. <br/>
<strong>Better answer</strong>: Subcon Town is the little me-space on the web where I live;
it is themed after the concept of a subconscious dreamscape.
</p>
<p>
<strong>Q</strong>: <em>Can I get an account on one of your services?</em>
<br/>
<strong>A</strong>: No. Subcon is currently not open for new people, so it's a little
local club for close friends only at the moment.
</p>
<p>
<strong>Q</strong>: <em>Didn't you also run kartoffel.cafe?</em>
<br/>
<strong>A</strong>: Yes, but a recent change of plans made running an activitypub
instance not very feasible, so I made the difficult choice of closing
the instance, for good. I may run a new instance in the future though.
</p>
<p>
<strong>Q</strong>: <em>Is that the Duck Hunt font?</em>
<br/>
<strong>A</strong>: Yes.
</p>
<p>
<strong>Q</strong>: <em>What do you intend to run in the future?</em>
<br/>
<strong>A</strong>: That's not yet certain. But I've had the idea to
start converting the server into a <a href="https://tildeverse.org/">tilde community</a>
and offering more community-tailored services. I won't offer e-mail hosting though.
<em>Fuck e-mail servers.</em>
</p>
</div>
<hr />
<div class="content" id="contact">
<h1>Contact</h1>
<p>
Feel free to drop me a line if you have a question or just wanna talk to me. I can be
contacted at:
</p>
<ul>
<li>Email: <a href="mailto:info@subcon.town">info@subcon.town</a></li>
<li>Fediverse: <a href="https://mk.toast.cafe/@fristi">@fristi@mk.toast.cafe</a></li>
</ul>
</div>
<hr />
<div class="content" id="copyright">
<h1>Copyright</h1>
<p>
Media used on this website, including images, videos, audio and fonts, fall under their
own respective licenses. The Subcon branding, including the logo and favicons, fall under
the <a target="_blank" href="assets/subcon/LICENSE">CC BY-NC-ND</a> license.
</p>
<p>
The remaining content and website source code are licensed under the
<a target="_blank" href="LICENSE">Blue Oak Model License</a>. The website source code
can be found <a target="_blank" href="https://vcs.subcon.town/fristi/subcon.town">here</a>.
</p>
</div>
</main>
<footer>
<div style="text-align: right">
© 2021 Subcon Town.
&nbsp;<a href="#copyright">License details</a>
&nbsp;<a target="_blank" href="https://vcs.subcon.town/fristi/subcon.town">Source</a>
</div>
</footer>
</div>
</body>
</html>

106
libs/zap/Application.php Normal file
View file

@ -0,0 +1,106 @@
<?php
namespace Subcon\Zap;
use Subcon\Zap\Config\Repository;
use Subcon\Zap\ErrorHandling\Handler;
use Subcon\Zap\Http\Response;
use Subcon\Zap\Routing\Router;
class Application
{
const VERSION = '2.0.0';
protected static $instance;
protected string $basePath;
protected Repository $config;
protected Router $router;
public function __construct(string $basePath = null)
{
if(!$basePath || !file_exists($basePath) || !is_dir($basePath)) {
$basePath = implode('/', array_slice(explode('/', $_SERVER['SCRIPT_NAME']), 0, -2)) . '/';
}
//Initialize paths and load up environment and config files.
static::$instance = $this;
$this->setBasePath($basePath);
$this->initConfiguration();
$this->initErrorHandling();
$this->initRouting();
}
public static function getInstance(): static
{
return static::$instance;
}
public function getBasePath($path = '')
{
return $this->basePath . (!empty($path) ? DIRECTORY_SEPARATOR.$path : '');
}
public function getPublicPath($path = '')
{
return $this->basePath . DIRECTORY_SEPARATOR . 'public_html' . (!empty($path) ? DIRECTORY_SEPARATOR.$path : '');
}
public function getViewPath($path = '')
{
return $this->basePath . DIRECTORY_SEPARATOR . 'resources/views' . (!empty($path) ? DIRECTORY_SEPARATOR.$path : '');
}
public function getConfig()
{
return $this->config;
}
public function run()
{
if(!$this->router) {
(new Response('Could not get router instance, aborting.', 500))
->send();
}
$this->router->run();
}
protected function setBasePath($basePath): static
{
$this->basePath = rtrim($basePath, '\/');
return $this;
}
protected function initConfiguration()
{
$this->config = new Repository($this);
}
protected function initErrorHandling()
{
set_error_handler(function (int $nr, string $error, ?string $file, ?int $line) {
(new Handler($this))->reportError($nr, $error, $file, $line);
$this->sendErrorResponse();
});
set_exception_handler(function (\Throwable $e) {
(new Handler($this))->report($e);
$this->sendErrorResponse();
});
}
protected function initRouting()
{
$this->router = new Router($this);
}
protected function sendErrorResponse()
{
(new Response('Whoops, something went wrong.', 500))
->send();
}
}

View file

@ -0,0 +1,66 @@
<?php
namespace Subcon\Zap\Config;
use Subcon\Zap\Application;
class Repository
{
protected array $items;
public function __construct(Application $app)
{
//Load from cache if it exists
if($this->loadFromCache(realpath($app->getBasePath('cache')))) {
return;
}
//Otherwise, load from config files
$this->loadConfigurationFiles(realpath($app->getBasePath('config')));
}
public function get(string $key, $default = null)
{
$keys = explode('.', $key);
$value = $this->items;
foreach($keys as $index) {
if(!key_exists($index, $value)) return $default;
$value = $value[$index];
}
return $value;
}
public function buildCache()
{
}
protected function loadFromCache(string $cachePath): bool
{
if(!file_exists($cachePath) || !file_exists($cachePath . DIRECTORY_SEPARATOR . 'config-cache.php')) {
return false;
}
$this->items = require $cachePath . DIRECTORY_SEPARATOR . 'config-cache.php';
return true;
}
protected function loadConfigurationFiles(string $configPath)
{
$files = glob($configPath . DIRECTORY_SEPARATOR . '*.php');
if($files === false || empty($files)) {
throw new \RuntimeException('Unable to load configuration, no configuration files found.');
} elseif(!in_array($configPath . DIRECTORY_SEPARATOR . 'application.php', $files)) {
throw new \RuntimeException('Unable to load the application configuration, app configuration file is missing.');
}
$this->items = [];
foreach($files as $file) {
$items = require $file;
$this->items = array_merge($this->items, $items);
}
}
}

View file

@ -0,0 +1,31 @@
<?php
namespace Subcon\Zap\ErrorHandling;
use Subcon\Zap\Application;
class Handler
{
protected $app;
public function __construct(Application $app)
{
$this->app = $app;
}
public function report(\Throwable $e)
{
$resource = fopen('php://stderr', 'w');
fwrite($resource, "An error was thrown! {$e->getMessage()}");
fwrite($resource, "{$e->getTraceAsString()}");
//Log the error.
}
public function reportError(int $error_nr, string $error, ?string $file, ?int $line)
{
$resource = fopen('php://stderr', 'w');
fwrite($resource, "An error occurred! {$error}");
fwrite($resource, "Occurred in {$file}, at line {$line}.");
//Log the error.
}
}

235
libs/zap/Http/Response.php Normal file
View file

@ -0,0 +1,235 @@
<?php
namespace Subcon\Zap\Http;
class Response
{
// HTTP Response codes (shamelessly stolen from Symfony's Response class)
public const HTTP_CONTINUE = 100;
public const HTTP_SWITCHING_PROTOCOLS = 101;
public const HTTP_PROCESSING = 102; // RFC2518
public const HTTP_EARLY_HINTS = 103; // RFC8297
public const HTTP_OK = 200;
public const HTTP_CREATED = 201;
public const HTTP_ACCEPTED = 202;
public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
public const HTTP_NO_CONTENT = 204;
public const HTTP_RESET_CONTENT = 205;
public const HTTP_PARTIAL_CONTENT = 206;
public const HTTP_MULTI_STATUS = 207; // RFC4918
public const HTTP_ALREADY_REPORTED = 208; // RFC5842
public const HTTP_IM_USED = 226; // RFC3229
public const HTTP_MULTIPLE_CHOICES = 300;
public const HTTP_MOVED_PERMANENTLY = 301;
public const HTTP_FOUND = 302;
public const HTTP_SEE_OTHER = 303;
public const HTTP_NOT_MODIFIED = 304;
public const HTTP_USE_PROXY = 305;
public const HTTP_RESERVED = 306;
public const HTTP_TEMPORARY_REDIRECT = 307;
public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
public const HTTP_BAD_REQUEST = 400;
public const HTTP_UNAUTHORIZED = 401;
public const HTTP_PAYMENT_REQUIRED = 402;
public const HTTP_FORBIDDEN = 403;
public const HTTP_NOT_FOUND = 404;
public const HTTP_METHOD_NOT_ALLOWED = 405;
public const HTTP_NOT_ACCEPTABLE = 406;
public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
public const HTTP_REQUEST_TIMEOUT = 408;
public const HTTP_CONFLICT = 409;
public const HTTP_GONE = 410;
public const HTTP_LENGTH_REQUIRED = 411;
public const HTTP_PRECONDITION_FAILED = 412;
public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
public const HTTP_REQUEST_URI_TOO_LONG = 414;
public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
public const HTTP_EXPECTATION_FAILED = 417;
public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
public const HTTP_LOCKED = 423; // RFC4918
public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
public const HTTP_INTERNAL_SERVER_ERROR = 500;
public const HTTP_NOT_IMPLEMENTED = 501;
public const HTTP_BAD_GATEWAY = 502;
public const HTTP_SERVICE_UNAVAILABLE = 503;
public const HTTP_GATEWAY_TIMEOUT = 504;
public const HTTP_VERSION_NOT_SUPPORTED = 505;
public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
public const HTTP_LOOP_DETECTED = 508; // RFC5842
public const HTTP_NOT_EXTENDED = 510; // RFC2774
public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
/**
* Status codes translation table.
*
* Shamelessly stolen from Symfony's Response class.
*
* The list of codes is complete according to the
* {@link https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml Hypertext Transfer Protocol (HTTP) Status Code Registry}
* (last updated 2021-10-01).
*
* Unless otherwise noted, the status code is defined in RFC2616.
*
* @var array
*/
public static $statusTexts = [
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing', // RFC2518
103 => 'Early Hints',
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status', // RFC4918
208 => 'Already Reported', // RFC5842
226 => 'IM Used', // RFC3229
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect', // RFC7238
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Content Too Large', // RFC-ietf-httpbis-semantics
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
418 => 'I\'m a teapot', // RFC2324
421 => 'Misdirected Request', // RFC7540
422 => 'Unprocessable Content', // RFC-ietf-httpbis-semantics
423 => 'Locked', // RFC4918
424 => 'Failed Dependency', // RFC4918
425 => 'Too Early', // RFC-ietf-httpbis-replay-04
426 => 'Upgrade Required', // RFC2817
428 => 'Precondition Required', // RFC6585
429 => 'Too Many Requests', // RFC6585
431 => 'Request Header Fields Too Large', // RFC6585
451 => 'Unavailable For Legal Reasons', // RFC7725
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates', // RFC2295
507 => 'Insufficient Storage', // RFC4918
508 => 'Loop Detected', // RFC5842
510 => 'Not Extended', // RFC2774
511 => 'Network Authentication Required', // RFC6585
];
protected string $charset = 'UTF-8';
protected string $protocol = '1.1';
protected array $headers;
protected int $status;
protected string $content;
public function __construct($content = '', int $status = 200, array $headers = [])
{
$this->headers = $headers;
$this->status = $status;
$this->setContent($content);
}
public function setHeader($header, $content): static
{
$this->headers[$header] = $content;
return $this;
}
public function setStatus(int $status): static
{
$this->status = $status;
return $this;
}
public function setContent($content): static
{
//If our content is an array of data, convert it to a json response.
if($content instanceof \ArrayObject || is_array($content)) {
$this->setHeader('Content-Type', 'application/json');
$json = json_encode($content);
if($json === false) {
throw new \InvalidArgumentException(json_last_error_msg());
}
$content = $json;
}
//Otherwise look if it can be rendered.
/*elseif($content instanceof RenderableInterface) {
$content = $content->render();
}*/
$this->content = $content;
return $this;
}
public function setProtocol(string $protocol): static
{
$this->protocol = $protocol;
return $this;
}
public function send()
{
$this->prepare();
//Send headers
if(!headers_sent()) {
foreach($this->headers as $header => $content) {
header("{$header}: $content", false, $this->status);
}
//TODO: send cookies
$statusText = static::$statusTexts[$this->status] ?? '';
header("HTTP/{$this->protocol} {$this->status} {$statusText}", true, $this->status);
}
//Send content (if any)
if(!empty($this->content)) {
echo $this->content;
}
}
protected function prepare()
{
if(!key_exists('Content-Type', $this->headers)) {
$this->headers['Content-Type'] = 'text/html; charset='.$this->charset;
} elseif (stripos($this->headers['Content-Type'], 'text/') === 0 && stripos($this->headers['Content-Type'], 'charset') === false) {
$this->headers['Content-Type'] = $this->headers['Content-Type'] . '; charset='.$this->charset;
}
}
}

View file

@ -0,0 +1,82 @@
<?php
namespace Subcon\Zap\Routing;
class Repository
{
/**
* @var array
*/
private array $routes;
/**
* RoutingRepository constructor.
*/
public function __construct()
{
$this->routes = [];
}
/**
* Register a new route.
*
* @param string $pattern The uri pattern to match
* @param array|string $methods One or more HTTP methods to match
* @param string|array|callable $callback A callback to run if this route is called
* @param array $middlewares List of extra middlewares to run for this route
* @return void
*/
public function register(string $pattern, array|string $methods, string|array|callable $callback, array $middlewares = []): void
{
$route = new Route($pattern, $methods, $callback, $middlewares);
foreach($route->getMethods() as $method) {
$this->routes[$method][] = $route;
}
}
public function get(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'GET', $callback, $middlewares);
}
public function post(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'POST', $callback, $middlewares);
}
public function put(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'PUT', $callback, $middlewares);
}
public function delete(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'DELETE', $callback, $middlewares);
}
public function options(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'OPTIONS', $callback, $middlewares);
}
public function patch(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'PATCH', $callback, $middlewares);
}
public function head(string $pattern, string|array|callable $callback, array $middlewares = []): void
{
$this->register($pattern, 'HEAD', $callback, $middlewares);
}
/**
* Get the list of registered routes.
*
* @return array
*/
public function getRoutes(): array
{
return $this->routes;
}
}

190
libs/zap/Routing/Route.php Normal file
View file

@ -0,0 +1,190 @@
<?php
namespace Subcon\Zap\Routing;
class Route
{
/**
* @var string
*/
private string $name;
/**
* @var string
*/
private string $pattern;
/**
* @var string|array|callable
*/
private $callback;
/**
* @var array<string>
*/
private array $methods = [];
/**
* @var array<string>
*/
private array $middlewares = [];
/**
* @var array<string>
*/
private array $vars = [];
/**
* Route constructor.
*/
public function __construct(string $pattern, string|array $methods, string|array|callable $callback, array $middlewares = [])
{
if ($methods === []) {
throw new \InvalidArgumentException('HTTP methods argument was empty; must contain at least one method');
}
if(is_string($methods)) $methods = [$methods];
foreach($methods as $key => $method) {
$methods[$key] = strtoupper($method);
if (!in_array($methods[$key], ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH', 'HEAD'])) {
throw new \InvalidArgumentException("Invalid method '{$method}' specified.");
}
}
$this->pattern = $pattern;
$this->callback = $callback;
$this->methods = $methods;
$this->middlewares = $middlewares;
}
/**
* @param string $path
* @param string $method
* @return bool
*/
public function match(string $path, string $method): bool
{
$regex = $this->getPattern();
foreach ($this->getVarsNames() as $variable) {
$varName = trim($variable, '{\}');
$regex = str_replace($variable, '(?P<' . $varName . '>[^/]++)', $regex);
}
if (in_array($method, $this->getMethods()) && preg_match('#^' . $regex . '$#sD', self::trimPath($path), $matches)) {
$values = array_filter($matches, static function ($key) {
return is_string($key);
}, ARRAY_FILTER_USE_KEY);
foreach ($values as $key => $value) {
$this->vars[$key] = $value;
}
return true;
}
return false;
}
/**
* @return string
*/
public function getName(): string
{
return $this->name;
}
/**
* @param string $name
* @return $this
*/
public function setName(string $name): static
{
$this->name = $name;
return $this;
}
/**
* @return string
*/
public function getPattern(): string
{
return $this->pattern;
}
/**
* @return string|array|callable
*/
public function getCallback(): string|array|callable
{
return $this->callback;
}
/**
* @return array|string[]
*/
public function getMethods(): array
{
return $this->methods;
}
/**
* @return array
*/
public function getVarsNames(): array
{
preg_match_all('/{[^}]*}/', $this->pattern, $matches);
return reset($matches) ?? [];
}
/**
* @return bool
*/
public function hasVars(): bool
{
return $this->getVarsNames() !== [];
}
/**
* @return string[]
*/
public function getVars(): array
{
return $this->vars;
}
/**
* @return string[]
*/
public function getMiddlewares(): array
{
return $this->middlewares;
}
/**
* @return mixed
*/
public function run()
{
//TODO: middlewares
//TODO: allow for automatically injecting dependencies for function arguments
if(is_callable($this->callback)) {
return call_user_func_array($this->callback, $this->vars);
} elseif (is_array($this->callback) && [0,1] == array_keys($this->callback)) {
$class = $this->callback[0];
$method = $this->callback[1];
if(class_exists($class) && method_exists($class, $method)) {
$controller = new $class();
return $controller->$method(...$this->vars);
}
}
throw new \RuntimeException("Could not resolve callable function for route.");
}
/**
* @param string $path
* @return string
*/
public static function trimPath(string $path): string
{
return '/' . rtrim(ltrim(trim($path), '/'), '/');
}
}

230
libs/zap/Routing/Router.php Normal file
View file

@ -0,0 +1,230 @@
<?php
namespace Subcon\Zap\Routing;
use Subcon\Zap\Application;
use Subcon\Zap\Http\Response;
class Router
{
/**
* @var Application
*/
private Application $app;
/**
* @var array
*/
private array $routes;
/**
* @var string
*/
private string $requestUri;
/**
* @var string
*/
private string $requestMethod;
/**
* @var array
*/
private array $requestHeaders;
/**
* @param Application $app
*/
public function __construct(Application $app)
{
$this->app = $app;
//Load routes from cache, or fall back to loading from file
if($this->loadFromCache(realpath($app->getBasePath('cache')))) {
return;
}
$this->loadFromRouteFile(realpath($app->getBasePath('routes')));
}
/**
* @return void
*/
public function run(): void
{
//Parse request
$this->requestUri = $this->getRequestUri();
$this->requestMethod = $this->getRequestMethod();
$this->requestHeaders ??= $this->getRequestHeaders();
//Find the first matching route. Only one route will ever be executed.
$route = $this->findRoute($this->requestUri, $this->requestMethod);
//If route was found, run it. Otherwise return a 404 response.
if($route) {
$response = $this->handleRoute($route);
} else {
$response = $this->handleNotFoundResponse();
}
//Send response.
if (!is_a($response, Response::class)) {
$response = new Response((string) $response);
}
$response->send();
//If the request was a HEAD request, empty the output buffer.
if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
ob_end_clean();
}
}
public function buildCache()
{
}
/**
* Define the current relative URI.
*
* @return string
*/
protected function getRequestUri(): string
{
$uri = rawurldecode($_SERVER['REQUEST_URI']);
// Don't take query params into account on the URL
if (strstr($uri, '?')) {
$uri = substr($uri, 0, strpos($uri, '?'));
}
// Remove trailing slash + enforce a slash at the start
return '/' . trim($uri, '/');
}
/**
* Get all request headers.
*
* @return array The request headers
*/
protected function getRequestHeaders(): array
{
if(!empty($this->requestHeaders)) return $this->requestHeaders;
$headers = [];
// If getallheaders() is available, use that
if (function_exists('getallheaders')) {
$headers = getallheaders();
// getallheaders() can return false if something went wrong
if ($headers !== false) {
$this->requestHeaders = $headers;
return $headers;
}
}
// Method getallheaders() not available or went wrong: manually extract them
foreach ($_SERVER as $name => $value) {
if ((substr($name, 0, 5) == 'HTTP_') || ($name == 'CONTENT_TYPE') || ($name == 'CONTENT_LENGTH')) {
$headers[str_replace([' ', 'Http'], ['-', 'HTTP'], ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
}
}
$this->requestHeaders = $headers;
return $headers;
}
/**
* Get the request method used, taking overrides into account.
*
* @return string The request method to handle
*/
protected function getRequestMethod(): string
{
// Take the method as found in $_SERVER
$method = $_SERVER['REQUEST_METHOD'];
// If it's a HEAD request override it to being GET and prevent any output, as per HTTP Specification
// @url http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html#sec9.4
if ($_SERVER['REQUEST_METHOD'] == 'HEAD') {
ob_start();
$method = 'GET';
}
// If it's a POST request, check for a method override header
elseif ($_SERVER['REQUEST_METHOD'] == 'POST') {
$headers = $this->getRequestHeaders();
if (isset($headers['X-HTTP-Method-Override']) && in_array($headers['X-HTTP-Method-Override'], array('PUT', 'DELETE', 'PATCH'))) {
$method = $headers['X-HTTP-Method-Override'];
}
}
return $method;
}
protected function loadFromCache(string $cachePath): bool
{
if(!file_exists($cachePath) || !file_exists($cachePath . DIRECTORY_SEPARATOR . 'route-cache.php')) {
return false;
}
$this->routes = require $cachePath . DIRECTORY_SEPARATOR . 'route-cache.php';
return true;
}
protected function loadFromRouteFile(string $routePath): void
{
if(!file_exists($routePath) || !file_exists($routePath . DIRECTORY_SEPARATOR . 'routes.php')) {
throw new \RuntimeException('Unable to initialize routes: route file not found.');
}
$repository = new Repository();
$routes = require $routePath . DIRECTORY_SEPARATOR . 'routes.php';
if(!is_callable($routes)) {
throw new \RuntimeException('Unable to initialize routes: route file does not contain callable function.');
}
//Initialize routes
call_user_func($routes, $repository);
$this->routes = $repository->getRoutes();
}
protected function findRoute(string $path, string $method): ?Route
{
$method = strtoupper($method);
if(key_exists($method, $this->routes)) {
foreach($this->routes[$method] as $route) {
if($route->match($path, $method)) {
return $route;
}
}
}
return null;
}
protected function handleRoute(Route $route)
{
//Todo: run route middleware and route handler.
return $route->run();
}
protected function handleNotFoundResponse()
{
//Do we have an error template?
$template = $this->app->getViewPath('errors/404.html');
if(file_exists($template)) {
ob_start();
require $template;
$response = ob_get_clean();
return new Response($response, 404);
} else {
return new Response('Whoops! Could not find that file.', 404);
}
}
}

4
public_html/.htaccess Normal file
View file

@ -0,0 +1,4 @@
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule . index.php [L]

55
public_html/LICENSE Normal file
View file

@ -0,0 +1,55 @@
# Blue Oak Model License
Version 1.0.0
## Purpose
This license gives everyone as much permission to work with
this software as possible, while protecting contributors
from liability.
## Acceptance
In order to receive this license, you must agree to its
rules. The rules of this license are both obligations
under that agreement and conditions to your license.
You must not do anything with this software that triggers
a rule that you cannot or will not follow.
## Copyright
Each contributor licenses you to do everything with this
software that would otherwise infringe that contributor's
copyright in it.
## Notices
You must ensure that everyone who gets a copy of
any part of this software from you, with or without
changes, also gets the text of this license or a link to
<https://blueoakcouncil.org/license/1.0.0>.
## Excuse
If anyone notifies you in writing that you have not
complied with [Notices](#notices), you can keep your
license by taking all practical steps to comply within 30
days after the notice. If you do not do so, your license
ends immediately.
## Patent
Each contributor licenses you to do everything with this
software that would otherwise infringe any patent claims
they can license or become able to license.
## Reliability
No contributor can revoke this license.
## No Liability
***As far as the law allows, this software comes as is,
without any warranty or condition, and no contributor
will be liable to anyone for any damages related to this
software or this license, under any kind of legal claim.***

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 545 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 B

View file

@ -0,0 +1,187 @@
@font-face {
font-family: 'yoster_islandregular';
src: url('../fonts/yoster-web.woff2') format('woff2'),
url('../fonts/yoster-web.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* Variables */
:root {
--font: "yoster_islandregular", monospace;
--font-size: 16px;
--line-height: 24px;
--spacing-normal: var(--font-size);
--spacing-large: var(--line-height);
--fg-color: rgba(255, 255, 255, 1);
--fg-shadow: 0px 2px 0px rgba(0,0,0, 0.4);
--bg-color: rgba(0, 0, 0, 0.3);
--bg-backdrop: saturate(300%) contrast(30%);
--link-color: rgba(215, 215, 109, 1);
}
/* Structure */
html, body {
margin:0;
padding:0;
}
body {
background-image: url("../background3.png");
background-position: center center;
background-size: cover;
background-attachment: fixed;
overflow: hidden;
font-size: var(--font-size);
line-height: var(--line-height);
font-family: var(--font);
color: var(--fg-color);
text-shadow: var(--fg-shadow);
}
.container {
position: absolute;
left:0;
right:0;
height: 100%;
max-width: 640px;
padding: 0 10px;
margin: auto;
display:flex;
flex-direction: column;
flex-wrap: nowrap;
overflow: hidden;
background-color: var(--bg-color);
/*backdrop-filter: var(--bg-backdrop);*/
}
header {
display: flex;
margin: var(--spacing-large);
flex: 0 0 auto;
align-items: flex-end;
}
header img {
width: 265px;
object-fit: contain;
flex: 0 0 auto;
}
header ul {
display: flex;
position: relative;
padding: 0;
height: 80px;
list-style: none;
font-size: 0;
flex: 1 1 auto;
flex-wrap: wrap;
flex-direction: column;
justify-content: flex-end;
}
header ul li {
position: relative;
width: 50%;
flex: 0 0 auto;
}
header ul li a{
display: block;
color: #fff;
font-size: 20px;
font-weight: normal;
line-height: 36px;
text-align: center;
}
header ul li a:hover:before {
content: "> ";
}
main {
flex: 1 1 auto;
height: 488px;
padding: var(--spacing-large);
border: 32px solid transparent;
border-image: url('../border2x2.png') 32 fill round;
overflow-y: auto;
scrollbar-color: #fff transparent;
scrollbar-width: thin;
}
main .inner-main {
text-align: justify;
}
main .inner-main::-webkit-scrollbar { width: 3px; }
main .inner-main::-webkit-scrollbar-track { background: transparent; }
main .inner-main::-webkit-scrollbar-thumb { background: #fff; }
footer {
margin: var(--spacing-large);
height: var(--line-height);
flex: 0 0 auto;
}
/* Element styling */
p {
margin: var(--spacing-large) 0;
}
hr {
color: rgba(255, 255, 255, 0.5);
margin: 48px;
}
h1 {
font-weight: normal;
font-size: 24px;
text-align: center;
margin: 24px 0 36px 0;
text-shadow: 0px 3px 0px rgba(0,0,0, 0.4);
}
h1:first-child {
margin-top: 0;
}
h2 {
font-weight: normal;
font-size: var(--font-size);
margin: var(--spacing-large) 0;
}
h2:before {
content: "* ";
}
a {
color: var(--link-color);
text-decoration: none;
}
a:hover {
color: #fff;
}
a:not(header a):before, a:not(header a):after { color: var(--link-color); }
a:not(header a):before { content:"["; margin-right: 6px; }
a:not(header a):after { content:"]"; margin-left: 6px; }
table tbody tr td:first-child {
padding: 0 3em 0 0;
}
.article-stub p {
margin-bottom: 0;
}
.article-stub-end {
text-align: right;
margin-bottom: var(--spacing-large);
}

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,66 @@
(function(gogo){
let loading = false;
let elContent;
let elMain;
function loadContent(href, ignoreHistory = false)
{
if(!loading) {
loading = true;
gogo.ajax(href, 'GET')
.then((response) => {
if('error' in response) {
replaceContent(response.error);
}
if('content' in response) {
replaceContent(response.content);
if(!ignoreHistory) {
window.history.pushState(null, '', href);
}
}
loading = false;
});
}
}
function replaceContent(content)
{
if(elContent) {
elContent.innerHTML = content;
elMain.scrollTo(0, 0);
showAddresses()
}
}
function showAddresses() {
let a = 'fristi';
let b = 'subcon.town';
let c = 'akkos.fritu.re';
gogo.getAll('.em').forEach( element => {
element.innerHTML = a + '@' + b;
element.setAttribute('href', 'mailto:' + a + '@' + b);
});
gogo.getAll('.fv').forEach( element => {
element.innerHTML = a + '@' + c;
});
}
window.addEventListener('load', function(){
elContent = gogo.get('.content');
elMain = gogo.get('main');
gogo.get('.container').addEventListener('click', event => {
if(!event.target.classList.contains('page-link')) return;
loadContent(event.target.getAttribute('href'));
event.preventDefault();
});
showAddresses();
});
window.addEventListener('popstate', event => {
loadContent(document.location, true);
});
})(gogo);

View file

@ -0,0 +1,91 @@
/*!
* Subcon GoGo Tools v1.0.0
* Licensed under BOML-1.0.0 license.
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) { //AMD
define(factory);
} else if (typeof exports === "object") { //CommonJS
module.exports = factory();
} else { //Browser global
root.gogo = factory();
}
//Let everything know the library is ready
const event = new Event("gogoReady");
document.dispatchEvent(event);
}(this, function () {
let gogo = {};
//Query selectors
gogo.get = function(selector) {
return document.querySelector(selector);
};
gogo.getAll = function(selector) {
return document.querySelectorAll(selector);
};
//Ajax
gogo.ajax = async function(url = '', method = 'GET', data = {}) {
//Set up fetch settings
let settings = {
method: method, // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'same-origin', // include, *same-origin, omit
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
// 'Content-Type': 'application/x-www-form-urlencoded',
//'X-CSRF-TOKEN': qs('meta[name="csrf-token"]').getAttribute('content'),
'X-Requested-With': 'XMLHttpRequest' //Allow laravel to properly detect this as an AJAX request
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
}
//If method is get or head, prepare data as query string
if(method === 'GET' || method === 'HEAD') {
if(Object.keys(data).length !== 0) {
let query = new URLSearchParams(data).toString()
url = url + '?' + query;
}
} else {
settings['body'] = JSON.stringify(data);
}
//Fetch
const response = await fetch(url, settings);
if(!response.ok || response.headers.get('content-type').indexOf('application/json') === -1) {
return {
'error': 'An error occurred while trying to fetch page data. Please refresh and try again.'
}
}
//Return response (uses Promise API)
return response.json();
}
//Used to allow injecting html and javascript from ajax requests
gogo.setHTML = function (element, html) {
element.innerHTML = html;
Array.from(element.querySelectorAll("script"))
.forEach( oldScriptEl => {
const newScriptEl = document.createElement("script");
Array.from(oldScriptEl.attributes).forEach( attr => {
newScriptEl.setAttribute(attr.name, attr.value)
});
const scriptText = document.createTextNode(oldScriptEl.innerHTML);
newScriptEl.appendChild(scriptText);
oldScriptEl.parentNode.replaceChild(newScriptEl, oldScriptEl);
});
}
return gogo;
}));

View file

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 2 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

View file

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View file

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

12
public_html/index.php Normal file
View file

@ -0,0 +1,12 @@
<?php
define('APP_START', microtime(true));
// Autoload classes
require __DIR__.'/../vendor/autoload.php';
// Boot application
$app = new \Subcon\Zap\Application(dirname(__DIR__));
//TODO: Console mode compatibility.
$app->run();

View file

@ -0,0 +1,30 @@
---
title: Introducing Funkwhale
author: Fristi
date: 2022-11
description: After some fiddling around on the server, I have managed to finally get a Funkwhale instance set up!
---
# Introducing Funkwhale
*November 2022, by Fristi*
After some fiddling around on the server, I have managed to finally get a Funkwhale instance
set up! Funkwhale is a piece of software that will let us host our music libraries. You can listen
to your and others' music from the web interface, or use any compatible app on your phone or tablet.
Besides that, Funkwhale federates, much like our Akkoma instance. You will be able to listen to the
libraries of people on other instances as well!
On a technical note, getting Funkwhale to work took a little bit of doing; it has been manually
set up according to the installation instructions for Arch Linux, adapted here and there to suit
Fedora. One remaining irk because of this, is that Funkwhale was not made with SELinux in mind.
One remaining problem that you might run into is that freshly uploaded music may not play initially;
this is caused by incorrect SELinux permissions. I have currently added a cronjob that will check
these permissions and fix them every few minutes. If your music does not play, please wait a few
minutes and try again.
For the time being, users will be limited to 5GB libraries. If needed, you may request to have this
limit changed. Please do check before uploading if your music has already been uploaded by others;
preventing duplicate uploads will help conserve space on the server.
Happy listening!

View file

@ -0,0 +1,39 @@
---
title: Phasing out Gitea and introducing Fossil SCM
author: Fristi
date: 2022-11
description: Recently it has come to my attention that the owners of Gitea have been making some changes...
---
# Phasing out Gitea and introducing Fossil SCM
*November 2022, by Fristi*
Recently it has come to my attention that the owners of Gitea have been making some changes
in their project management, mainly in the form of providing support to businesses. For more
information, view [the Gitea blog](https://blog.gitea.io/2022/10/open-source-sustainment-and-the-future-of-gitea/).
Unfortunately, with the way things have gone, the community seems to be weary and distrustful of a
number of moves made by the current owners. My personal concerns are about one of the owners using
Gitea is a lucrative money-making scheme. To me, that's a deal breaker, as I don't know if I can
still trust the project after this. Unfortunate, since I know Gitea is supposed to be a community-run
effort.
As such, I have decided to deprecate the Gitea service on Subcon Town. It will no longer be updated,
but will be kept alive for a while until all projects have been migrated. I am additionally looking
into migrating the data to a Gogs installation, continuing a git hosting service that way. Meanwhile,
I have opted to set up Fossil as an alternative. Fossil SCM is a slightly different VCS, similar to git,
but more geared towards tightly nit development groups that work closer together.
While fossil may feel a bit more old-fashioned, I think it's also a bit more of a fit with Subcon's
pubnix feel. Users will be able to set up and manage their fossil repositories from their home
directories, in the new "public_fossil" directory. To create new repositories, you can use the
"mkfossil &lt;filename&gt;" command. This is a wrapper around "fossil init" that helps set the
correct file permissions. If done right, your new repo will pop up at
[museum.subcon.town](https://museum.subcon.town) from where you'll be able to administrate it.
With the introduction of new commands and functions for users, I plan on writing a wiki article
that lists all available functions and how to use them. This should help new users settle in.
Note that the README files in your home directories will be kept updated as well.
Hope to see you soon on Subcon Town!

View file

@ -0,0 +1,41 @@
---
title: Frontpage updates and more
author: fristi
date: 2023-06
description: It's been quiet for half a year. Subcon has been chucking along
quite well with nothing special happening. Even the dreaded Fedora 38 update
did not prove too much of a headache. All has been moseying along easily.
---
# Frontpage updates and more
*June 2023, by Fristi*
It's been quiet for half a year. Subcon has been chucking along quite well with
nothing special happening. Even the dreaded Fedora 38 update did not prove too
much of a headache. All has been moseying along easily.
Actually, I've just not been in the mood to work much on the server recently
because of life stuff. I've recently quit my job as a programmer and took a much
needed hiatus away from programming to clear my head of stuff. I think this was
good, because I've slowly regained my interest in programming and maintaining my
server again.
And that means updates! The website has been updated; not much new on the front,
but it's now a PHP-powered website with actual pages. It even has fancy ajax
page loading now (but the website will also work without javascript). This means
people can now properly link to different pages as well as the news articles.
Besides the website, I've also set up Radicale, a caldav and carddav server, as
a potential replacement for Nextcloud's calendars and addressbooks. With recent
Nextcloud updates it has become clear that updating it usually leads to downtime
and issues, which for me defeats the purpose of having such a fully-featured
groupware service. As such I will also be searching for a replacement for
Nextcloud's file hosting and syncing.
Up on the todo list are to update Funkwhale, which I hope isn't gonna spit in my
face. Also I will be picking up work on the custom artwork for the subcon
frontpage; parts of it have already been finished, so I really wanna get it all
over and done with. Other than that, I'll be busy with just the usual. Keep tabs
on the [wiki](https://library.subcon.town) for any new guides and stuff; I will
be adding one for using Radicale.

View file

@ -0,0 +1,42 @@
---
title: Plans for the year
author: fristi
date: 2023-01
description: Happy new year! Subcon has come a long way since the change of the
year. The server is now powered by an Odroid H3 board. With that, a number of
other changes have been made as well.
---
# Plans for the year
*Januari 2023, by Fristi*
Happy new year! Subcon has come a long way since the change of the year. First
off: we have moved to new hardware! The server is now powered by an Odroid H3
board. With that, a number of other changes have been made as well.
Storage on the server has been totally reworked; the old storage media have been
replaced with a 2TB NVMe drive that will, for one, house the user home
directories, and a 4TB SATA SSD that will house all variable data; user
repositories, websites, databases, the lot. All of this has been set up with
convenient LVM partitions that can be easily resized if more space is required
in the future.
Many services have been updated. The gitea server has been replaced with a new
gogs server, now hosted at [factory.subcon.town](https://factory.subcon.town).
The old snikket server has also been replaced with a new prosody setup, offering
most of the same xmpp services at [jab.subcon.town](https://jab.subcon.town).
Note that old snikket accounts are thus gone. The fedora installation has also
been updated to fedora 37.
The most notable change however will be the decrease in processing power. While
we still have about 75% of the processing power, the upside is that the system
as a whole is seriously more power efficient. We are now running at a fraction
of the power the old server needed, going from 60Wh on average to a mere 6Wh.
Serious power savings like that will lower upkeep cost, which I can in turn
invest in improving other things, such as my god awful internet bandwidth.
Remember that even with all these changes done, the server is, and will likely
forever, be a work in progress. There are still some rough edges needing work,
further configuring of services, and for the current while, a lot of work
writing wiki pages at [library.subcon.town](https://library.subcon.town).

View file

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php if(isset($title)) echo "{$title} - "; ?>Welcome to Subcon Town!</title>
<?php if(isset($meta) && is_array($meta)) {
foreach($meta as $key => $value) { ?>
<meta name="<?php echo $key; ?>" content="<?php echo $value; ?>">
<?php }} ?>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_16.png" sizes="16x16"/>
<link rel="shortcut icon" type="image/jpg" href="/assets/subcon/favicon_32.png" sizes="32x32"/>
<link href="/assets/css/style.css?v=<?php echo \Subcon\Zap\Application::VERSION ?>" rel="stylesheet"/>
<script src="/assets/js/gogo.js" type="text/javascript"></script>
<script src="/assets/js/app.js?v=<?php echo \Subcon\Zap\Application::VERSION ?>" type="text/javascript"></script>
</head>
<body>
<div class="container">
<header>
<img src="/assets/subcon/logo_128.png" alt="Subcon Town" />
<ul>
<li><a class="page-link" href="/">Home</a></li>
<li><a target="_blank" href="https://library.subcon.town/">Wiki</a></li>
<li><a class="page-link" href="/services">Services</a></li>
<li><a class="page-link" href="/faq">FAQ</a></li>
</ul>
</header>
<main>
<div class="inner-main">
<div class="content">
<?php echo $content; ?>
</div>
</div>
</main>
<footer>
<div style="text-align: right">
© 2021-<?php echo date("Y"); ?> Subcon Town.
&nbsp;<a class="page-link" href="/copyright">License</a>
</div>
</footer>
</div>
</body>
</html>

View file

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>404 - Not Found</title>
<link type="text/css" rel="stylesheet" href="https://cdn.subcon.town/system/page.css" />
</head>
<body>
<div class="container">
<div class="col">
<h1>404 - Not found!</h1>
<p>
Whoops! Looks like we don't have that thing you're looking for. Maybe it got
moved or deleted, we don't know. Sorry!
</p>
<p>
<a href="/">&lt;&lt; Back to the homepage</a>
</p>
</div>
<div class="col right">
<img style="max-height: 300px;" src="https://cdn.subcon.town/system/img/404.jpg" alt="Internet Explorer" />
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,25 @@
<h1>Copyright notice</h1>
<p>
The "Subcon Town" branding, including logos and icons used on this website, are
licensed as CC BY-NC-ND. You may share and redistribute these works, but please do not edit them or use them for
commercial purposes. For more details, read the <a target="_blank" href="./assets/subcon/LICENSE">license</a>.
</p>
<p>
The source code of this website is licensed under the Blue Oak Model License 1.0.0.
You may use this source code as you wish.
For more details, read the <a target="_blank" href="./LICENSE">license</a>.
The source code of this website may be viewed <a target="_blank" href="https://factory.subcon.town/fristi/subcon.town">here</a>.
</p>
<h2>Third party works</h2>
<p>
The fonts and background image used on this website are not owned by Subcon Town. Subcon Town does
not claim any rights to these original works unless otherwise specified by their respective licensing.
</p>
<h2>Complaints</h2>
<p>
If you feel any material is incorrectly used on this website, or otherwise infringes on your rights,
feel free to file a complaint with <a class="em" href="#">fristi「AT」subcon.town</a>. Please allow up
to 48 hours for a response.
</p>

View file

@ -0,0 +1,25 @@
<h1>FAQ</h1>
<p>
<strong>Q</strong>: <em>You're running on the Odroid H3 now, right?</em>
<br/>
<strong>A</strong>: Yes, all Subcon services have been moved to the new hardware
and are fully functional.
</p>
<p>
<strong>Q</strong>: <em>Is Subcon Town open for new users?</em>
<br/>
<strong>A</strong>: We're still doing some work on some services, but in general
everything is ready. Note that Subcon Town is an invite-only community; you may certainly
ask for an invite, but generally we will only accept people that are known to us already.
</p>
<p>
<strong>Q</strong>: <em>Can I get an account on one of your services?</em>
<br/>
<strong>A</strong>: Only as an existing member. If you're already a member, ask
<a target="_blank" href="https://akkos.fritu.re/fristi">@fristi</a> for an account.
</p>
<p>
<strong>Q</strong>: <em>Is that the Yoshi's Island font?</em>
<br/>
<strong>A</strong>: Yes. It was good game.
</p>

View file

@ -0,0 +1,30 @@
<h1>Hello world!</h1>
<p>
Welcome to Subcon Town! We are a small tilde community, hosted for friends and family.
We offer SSH access, hosting of websites, gopher holes and gemini pods, alongside
other services like cloud services, git and fossil hosting and an activitypub server.
</p>
<p>
Tilde communities are pubnixes, <i>public access unix systems</i>. We took our inspiration
from older such servers like <a target="_blank" href="http://tilde.club">tilde.club</a>
and the tildes from <a target="_blank" href="https://tildeverse.org">tildeverse.org</a>.
</p>
<p>
Users may find out more about this system by visiting the <a target="_blank" href="https://library.subcon.town/">wiki</a>.
Keep in mind that the wiki is still a work in progress.
</p>
<hr />
<h1>News</h1>
<!-- List of article stubs -->
<?php foreach($articles as $article) { ?>
<h2><?php echo $article->getTitle(); ?></h2>
<div class="article-stub">
<p><?php echo $article->getDescription(); ?></p>
<div class="article-stub-end">
<a class="page-link" href="/news/<?php echo $article->getSlug(); ?>">Read more</a>
</div>
</div>
<?php } ?>

View file

View file

@ -0,0 +1,34 @@
<h1>Services</h1>
<p>
We provide the following services for our members:
</p>
<table>
<tbody>
<tr> <td>Akkoma:</td> <td><a title="Our microblogging service." target="_blank" href="https://akkos.fritu.re">akkos.fritu.re</a></td> </tr>
<tr> <td>Doku wiki:</td> <td><a title="Our wiki hosting service." target="_blank" href="https://library.subcon.town">library.subcon.town</a></td> </tr>
<tr> <td>Funkwhale:</td> <td><a title="Our music hosting instance." target="_blank" href="https://odeon.subcon.town">odeon.subcon.town</a></td> </tr>
<tr> <td>Gogs:</td> <td><a title="Our git respository hosting service." target="_blank" href="https://factory.subcon.town">factory.subcon.town</a></td> </tr>
<tr> <td>Fossil SCM:</td> <td><a title="Our fossil repository hosting service." target="_blank" href="https://museum.subcon.town">museum.subcon.town</a></td> </tr>
<tr> <td>Nextcloud:</td> <td><a title="Our cloud file hosting and syncing service." target="_blank" href="https://cloud.subcon.town">cloud.subcon.town</a></td> </tr>
<tr> <td>Radicale:</td> <td><a title="Our caldav and carddav hosting service." target="_blank" href="https://cal.subcon.town/radicale">cal.subcon.town</a></td> </tr>
</tbody>
</table>
<p>
We also host member websites, gopher holes and gemini pods. User websites are listed below,
for gopher holes and gemini pods, visit <a target="_blank" href="gopher://subcon.town">gopher://subcon.town</a> and
<a target="_blank" href="gemini://subcon.town">gemini://subcon.town</a> respectively using a compatible browser.
</p>
<hr />
<h1>User tildes:</h1>
<table>
<tr>
<td width="50%"><a target="_blank" href="/~fristi">fristi</a></td>
<td width="50%"></td>
</tr>
</table>

15
routes/routes.php Normal file
View file

@ -0,0 +1,15 @@
<?php
use Subcon\Frontpage\Controllers\FrontpageController;
use Subcon\Zap\Routing\Repository;
return function (Repository $routes) {
//Frontpage routes
$routes->get('/', [FrontpageController::class, 'index']);
$routes->get('/news/{slug}', [FrontpageController::class, 'article']);
$routes->get('/services', [FrontpageController::class, 'services']);
$routes->get('/faq', [FrontpageController::class, 'faq']);
$routes->get('/copyright', [FrontpageController::class, 'copyright']);
};

View file

@ -0,0 +1,106 @@
<?php
namespace Subcon\Frontpage\Controllers;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
use League\CommonMark\MarkdownConverter;
use Subcon\Frontpage\Models\Article;
use Subcon\Zap\Application;
use Subcon\Zap\Http\Response;
class FrontpageController
{
private Application $app;
public function __construct()
{
$this->app = Application::getInstance();
}
public function index()
{
$articles = Article::getNewestArticles(3);
return $this->loadPage('index', ['articles' => $articles]);
}
public function article(string $slug)
{
$article = Article::getArticle($slug);
if(!$article) {
return $this->notFoundError();
}
return $this->loadArticle($article);
}
public function services()
{
return $this->loadPage('services');
}
public function faq()
{
return $this->loadPage('faq');
}
public function copyright()
{
return $this->loadPage('copyright');
}
private function loadPage(string $page, array $data = [])
{
if(isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] == 'application/json') {
//Only load content without the main template, return as array
$content = $this->toBuffer($this->app->getViewPath("pages/{$page}.php"), $data);
return new Response([
'content' => $content
]);
} else {
$content = $this->toBuffer($this->app->getViewPath("pages/{$page}.php"), $data);
$view = $this->toBuffer($this->app->getViewPath('_layout/layout.php'), array_merge($data, ['content' => $content]));
return new Response($view);
}
}
private function loadArticle(Article $article)
{
$data = [
'content' => $article->getRender()->getContent(),
'meta' => [
'title' => $article->getTitle(),
'author' => $article->getAuthor(),
'date' => date('F Y', $article->getTimestamp()),
'description' => $article->getDescription()
]
];
if(isset($_SERVER['HTTP_ACCEPT']) && $_SERVER['HTTP_ACCEPT'] == 'application/json') {
//Only load content without the main template, return as array
return new Response($data);
} else {
$view = $this->toBuffer($this->app->getViewPath('_layout/layout.php'), $data);
return new Response($view);
}
}
private function toBuffer($path, array $data = [])
{
ob_start();
if(!empty($data)) {
extract($data);
}
require $path;
return ob_get_clean();
}
private function notFoundError()
{
$html = $this->toBuffer($this->app->getViewPath('errors/404.html'));
return new Response($html, 404);
}
}

259
src/Models/Article.php Normal file
View file

@ -0,0 +1,259 @@
<?php
namespace Subcon\Frontpage\Models;
use League\CommonMark\Environment\Environment;
use League\CommonMark\Extension\CommonMark\CommonMarkCoreExtension;
use League\CommonMark\Extension\FrontMatter\Data\SymfonyYamlFrontMatterParser;
use League\CommonMark\Extension\FrontMatter\FrontMatterExtension;
use League\CommonMark\Extension\FrontMatter\FrontMatterParser;
use League\CommonMark\MarkdownConverter;
use League\CommonMark\Output\RenderedContentInterface;
use Subcon\Zap\Application;
class Article
{
/**
* @var string
*/
private string $path;
/**
* @var string
*/
private string $title;
/**
* @var int
*/
private int $timestamp;
/**
* @var string
*/
private string $description;
/**
* @var string
*/
private string $author;
/**
* @var RenderedContentInterface
*/
private RenderedContentInterface $render;
/**
* Initialize reference to main application instance.
* @param string $path Full path of the article this model will represent.
*/
public function __construct(string $path, array $cacheData = [])
{
$this->path = $path;
if(!empty($cacheData)) {
foreach($cacheData as $property => $value) {
switch ($property) {
case 'title':
case 'author':
case 'description':
$this->$property = $value ?? '';
break;
case 'date':
$this->timestamp = $value;
break;
}
}
}
}
/**
* Get an article
* @param string $slug
* @return static|null
*/
public static function getArticle(string $slug): ?static
{
$app = Application::getInstance();
if(!$app) throw new \RuntimeException('Unable to get articles; Application not yet initialized.');
if(!file_exists($app->getBasePath('cache/articles/cache.php'))) {
self::createArticleCache();
}
$cache = require $app->getBasePath('cache/articles/cache.php');
if(key_exists($slug, $cache)) {
$article = $cache[$slug];
return new Article($article['path'], $article);
} else {
return null;
}
}
/**
* Get a list of Article instances for each article on file.
*
* @return array
*/
public static function getArticles(int $amount = -1): array
{
$app = Application::getInstance();
if(!$app) throw new \RuntimeException('Unable to get articles; Application not yet initialized.');
if(!file_exists($app->getBasePath('cache/articles/cache.php'))) {
self::createArticleCache();
}
$articles = [];
$cache = require $app->getBasePath('cache/articles/cache.php');
foreach($cache as $article) {
$articles[] = new Article($article['path'], $article);
$amount--;
if($amount == 0) break;
}
return $articles;
}
/**
* Get a list of Article instances for the newest articles, optionally specifying
* the amount of articles.
*
* @param int $amount The amount of articles to load, defaults to 5.
* @return array
*/
public static function getNewestArticles(int $amount = 5): array
{
return self::getArticles($amount);
}
/**
* Create an article cache containing a sorted list of article file paths and their metadata.
*
* @return void
*/
public static function createArticleCache()
{
$app = Application::getInstance();
if(!$app) throw new \RuntimeException('Unable to cache articles; Application not yet initialized.');
$cache = [];
//Init frontmatter parser
$parser = new FrontMatterParser(new SymfonyYamlFrontMatterParser());
$paths = glob(Application::getInstance()->getBasePath("resources/articles") . '/*/*.md');
foreach ($paths as $path) {
$content = file_get_contents($path);
$result = $parser->parse($content);
$yaml = $result->getFrontMatter();
$article = [
'path' => $path,
'slug' => str_replace('_', '-', basename($path, '.md'))
];
foreach($yaml as $property => $value) {
switch ($property) {
case 'date': //Convert to timestamp for easier handling and sorting
$article['date'] = strtotime($value);
break;
default:
$article[$property] = $value;
}
}
$cache[$article['slug']] = $article;
}
//Sort cache by timestamp
uasort($cache, function ($a, $b) {
return $b['date'] <=> $a['date'];
});
//Check if directories exist
if(!file_exists($app->getBasePath('cache/articles'))) {
mkdir($app->getBasePath('cache/articles'), 0755, true);
}
//Write to file
$php = '<?php return ' . var_export($cache, true) . ';' ;
file_put_contents($app->getBasePath('cache/articles/cache.php'), $php);
}
/**
* @return string
*/
public function getPath(): string
{
return $this->path;
}
/**
* @return string
*/
public function getSlug(): string
{
return str_replace('_', '-', basename($this->path, '.md'));
}
/**
* @return string
*/
public function getTitle(): string
{
return $this->title;
}
/**
* @return int
*/
public function getTimestamp(): int
{
return $this->timestamp;
}
/**
* @return string
*/
public function getDescription(): string
{
return $this->description;
}
/**
* @return string
*/
public function getAuthor(): string
{
return $this->author;
}
/**
* @return RenderedContentInterface
*/
public function getRender(): RenderedContentInterface
{
if(!isset($this->render)) {
$this->parseContent();
}
return $this->render;
}
/**
* @return void
* @throws \League\CommonMark\Exception\CommonMarkException
*/
private function parseContent()
{
$environment = new Environment([]);
$environment->addExtension(new CommonMarkCoreExtension());
$environment->addExtension(new FrontMatterExtension());
$converter = new MarkdownConverter($environment);
$markdown = file_get_contents($this->path);
$this->render = $converter->convert($markdown);
}
}

168
style.css
View file

@ -1,168 +0,0 @@
/* @font-face kit by Fonts2u (https://fonts2u.com) */
@font-face {
font-family:"Duck Hunt";
src:url("assets/duck_hunt/duckhunt.eot?") format("eot"),
url("assets/duck_hunt/duckhunt.woff") format("woff"),
url("assets/duck_hunt/duckhunt.ttf") format("truetype"),
url("assets/duck_hunt/duckhunt.svg#DuckHunt") format("svg");
font-weight:normal;
font-style:normal;
}
@font-face {
font-family:"Baloo Thambi 2";
src:url("assets/baloo_thambi_2/BalooThambi2-Medium.ttf") format("truetype");
font-weight:normal;
font-style:normal;
}
@font-face {
font-family:"Baloo Thambi 2";
src:url("assets/baloo_thambi_2/BalooThambi2-Bold.ttf") format("truetype");
font-weight:bold;
font-style:normal;
}
/* Common Elements */
html, body {
margin:0;
padding:0;
}
body {
background-image: url("assets/background1.jpg");
background-position: center center;
background-size: cover;
background-attachment: fixed;
overflow: hidden;
font-size: 16px;
line-height: 24px;
font-family: 'Baloo Thambi 2', cursive;
color: rgba(255, 255, 255, 1);
}
hr {
color: rgba(255, 255, 255, 0.3);
margin: 2.5em 5em;
}
h1 {
font-family: "Duck Hunt", sans-serif;
font-size: 1.5rem;
font-weight: normal;
}
h2 {
font-size: 1.15rem;
}
a {
color: #fff;
font-weight: bold;
}
a:hover {
color: rgba(255, 255, 255, 0.8);
}
table tbody tr td:first-child {
padding: 0 3em 0 0;
}
/* Containers */
.container {
position: absolute;
box-sizing: border-box;
left:0;
right:0;
height: 100%;
max-width: 550px;
padding: 0 20px;
margin:auto;
background-color: rgba(0, 0, 0, 0.4);
display:flex;
flex-direction: column;
flex-wrap: nowrap;
overflow: hidden;
}
header {
margin: 40px 0 20px 0;
text-align: center;
flex: 0 0 auto;
}
main {
flex: 1 1 auto;
overflow-x: auto;
margin-right: -14px;
padding-right: 14px;
scrollbar-color: #fff transparent;
scrollbar-width: thin;
}
main::-webkit-scrollbar {
width: 3px;
}
main::-webkit-scrollbar-track {
background: transparent;
}
main::-webkit-scrollbar-thumb {
background: #fff;
}
footer {
margin: 20px 0 0 0;
height: 30px;
flex: 0 0 auto;
}
/* Custom styling */
header ul {
position: relative;
background: rgba(255, 255, 255, 0.3);
font-family: "Duck Hunt", sans-serif;
list-style: none;
margin: 10px 0 0 0;
padding: 0;
font-size: 0;
}
header ul li {
position: relative;
display: inline-block;
text-align: center;
width: 25%;
}
header ul li a {
display: block;
color: #fff;
text-decoration: none;
font-size: 24px;
line-height: 24px;
padding:10px 0 8px 0;
}
header ul li a:hover {
background-color: rgba(255, 255, 255, 0.3);
}
.muted {
font-style: italic;
color: #999;
}

View file

@ -1,50 +0,0 @@
html, body {
margin:0;
padding:0;
}
body {
width: 100%;
font-size: 16px;
color: white;
text-align: center;
}
.background {
position:fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: url("assets/background_errors.jpg");
background-position: center center;
background-repeat: no-repeat;
background-size: cover;
}
header, main, footer {
position: relative;
max-width: 600px;
box-sizing: border-box;
padding: 10px 0;
}
header {
margin: 100px auto 10px auto;
}
main {
margin: 0 auto 10px auto;
}
footer {
margin: 0 auto 50px auto;
font-size: .8rem;
}
h1 {
margin: 0;
padding: 0;
font-weight: bold;
font-size: 1.2rem;
}