1
0

Added ProjectLawful-ebook downloads (blog::29)
All checks were successful
Build Docker and Deploy / Build Docker (push) Successful in 1m18s
Build Docker and Deploy / Deploy to Server (push) Successful in 27s

This commit is contained in:
Mike Schwörer 2023-09-11 19:59:07 +02:00
parent f7688a224b
commit 300992b49f
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
49 changed files with 689 additions and 84 deletions

View File

@ -6,10 +6,11 @@
<State>
<option name="id" value="b1f51636-21bf-4ca9-a884-b0419489ab5f" />
<option name="name" value="EGG DB" />
<option name="languageId" value="SQLite" />
<option name="dbmsName" value="SQLITE" />
<option name="urls">
<array>
<option value="file://$PROJECT_DIR$/www/extern/egg/db_init.sql" />
<option value="file://$PROJECT_DIR$/data/schema.sql" />
</array>
</option>
</State>

1
.idea/sqldialects.xml generated
View File

@ -2,6 +2,7 @@
<project version="4">
<component name="SqlDialectMappings">
<file url="file://$PROJECT_DIR$" dialect="MySQL" />
<file url="file://$PROJECT_DIR$/data/schema.sql" dialect="SQLite" />
<file url="file://$PROJECT_DIR$/www/extern/egg" dialect="SQLite" />
<file url="PROJECT" dialect="MySQL" />
</component>

View File

@ -58,3 +58,14 @@ CREATE TABLE IF NOT EXISTS updateslog
PRIMARY KEY (ID)
);
CREATE TABLE IF NOT EXISTS projectlawful_downloadcounter
(
id int(11) NOT NULL AUTO_INCREMENT,
variant varchar(512) NOT NULL DEFAULT '0',
timestamp timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
ip varchar(128) NOT NULL,
useragent varchar(4096) NOT NULL,
PRIMARY KEY (ID)
);

View File

@ -1156,7 +1156,7 @@ html, body {
background: transparent !important;
}
.blogcontent_euler, .blogcontent_markdown, .blogcontent_plain {
.blogcontent_euler, .blogcontent_markdown, .blogcontent_plain, .blogcontent_aoclist, .blogcontent_html {
width: 90%;
}
@ -2612,5 +2612,100 @@ html, body {
height: 150px;
}
}
.project-lawful-ebook-blogpost code {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 2px;
padding-left: 2px;
padding-right: 2px;
}
.project-lawful-ebook-blogpost .variant {
display: grid;
grid-template-columns: repeat(6, auto) 1fr;
grid-template-rows: auto auto;
grid-column-gap: 1rem;
}
.project-lawful-ebook-blogpost .variant h3 {
grid-row: 1;
grid-column: 1/-1;
text-decoration: underline;
}
.project-lawful-ebook-blogpost .variant .dlbutton {
position: relative;
height: 100%;
padding: 0;
display: flex;
border: none;
}
.project-lawful-ebook-blogpost .variant .dlbutton img {
opacity: 0;
pointer-events: none;
user-select: none;
}
.project-lawful-ebook-blogpost .variant .dlbutton a {
display: grid;
grid-template-rows: 1fr auto auto;
grid-row-gap: 4px;
justify-content: center;
align-items: center;
justify-items: center;
padding: 0.5rem;
cursor: pointer;
text-decoration: none;
color: #DDDDDD;
background-color: #222222;
border-radius: 4px;
border: 1px solid black;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.project-lawful-ebook-blogpost .variant .dlbutton a:hover {
background-color: #555555;
}
.project-lawful-ebook-blogpost .variant .dlbutton a .span_dl {
font-weight: bold;
font-size: 1.33em;
}
.project-lawful-ebook-blogpost .variant .dlbutton a .span_mb {
font-size: 0.75em;
}
.project-lawful-ebook-blogpost .variant .dlbutton svg {
fill: #BBB;
}
.project-lawful-ebook-blogpost .variant img {
width: 100%;
border: 1px solid #555;
}
.project-lawful-ebook-blogpost .variant.alt-variants {
display: flex;
flex-direction: column;
gap: 4px;
}
.project-lawful-ebook-blogpost .dlbtn_alt {
display: grid;
grid-template-columns: 24px auto 1fr auto;
grid-column-gap: 1rem;
color: #DDDDDD;
background-color: #222222;
border: 1px solid black;
border-radius: 4px;
text-decoration: none;
cursor: pointer;
padding: 6px;
align-items: center;
}
.project-lawful-ebook-blogpost .dlbtn_alt:hover {
background-color: #555555;
}
.project-lawful-ebook-blogpost .dlbtn_alt svg {
fill: #BBB;
}
.project-lawful-ebook-blogpost .dlbtn_alt .span_mb {
font-size: 0.75em;
grid-column: 4;
}
/*# sourceMappingURL=styles.css.map */

View File

@ -217,7 +217,7 @@ html,body{margin:0;padding:0;height:100%}
.mdtable_container{overflow-x:auto}
.notable{width:unset !important;border:none !important}
.notable th,.notable td,.notable tr{border:none !important;background:transparent !important}
.blogcontent_euler,.blogcontent_markdown,.blogcontent_plain{width:90%}
.blogcontent_euler,.blogcontent_markdown,.blogcontent_plain,.blogcontent_aoclist,.blogcontent_html{width:90%}
#PEB_tableProblems .PEB_tablerowProblems:hover{background-color:#999}
#PEB_tableProblems .PEB_TC_Value{font-family:Consolas,Monaco,"Courier New",Menlo,monospace}
#PEB_tableProblems .PEB_TC_Value:not(:hover){color:transparent;-o-transition:.5s;-ms-transition:.5s;-moz-transition:.5s;-webkit-transition:.5s;transition:.5s}
@ -495,3 +495,19 @@ html,body{margin:0;padding:0;height:100%}
.bce_code_out .bce_code_out_left{flex-grow:0;height:100px}
.bce_code_out .bce_code_out_right{width:auto;height:150px}
}
.project-lawful-ebook-blogpost code{background-color:rgba(0,0,0,0.1);border-radius:2px;padding-left:2px;padding-right:2px}
.project-lawful-ebook-blogpost .variant{display:grid;grid-template-columns:repeat(6,auto) 1fr;grid-template-rows:auto auto;grid-column-gap:1rem}
.project-lawful-ebook-blogpost .variant h3{grid-row:1;grid-column:1/-1;text-decoration:underline}
.project-lawful-ebook-blogpost .variant .dlbutton{position:relative;height:100%;padding:0;display:flex;border:0}
.project-lawful-ebook-blogpost .variant .dlbutton img{opacity:0;pointer-events:none;user-select:none}
.project-lawful-ebook-blogpost .variant .dlbutton a{display:grid;grid-template-rows:1fr auto auto;grid-row-gap:4px;justify-content:center;align-items:center;justify-items:center;padding:.5rem;cursor:pointer;text-decoration:none;color:#ddd;background-color:#222;border-radius:4px;border:1px solid black;position:absolute;left:0;right:0;top:0;bottom:0}
.project-lawful-ebook-blogpost .variant .dlbutton a:hover{background-color:#555}
.project-lawful-ebook-blogpost .variant .dlbutton a .span_dl{font-weight:bold;font-size:1.33em}
.project-lawful-ebook-blogpost .variant .dlbutton a .span_mb{font-size:.75em}
.project-lawful-ebook-blogpost .variant .dlbutton svg{fill:#BBB}
.project-lawful-ebook-blogpost .variant img{width:100%;border:1px solid #555}
.project-lawful-ebook-blogpost .variant.alt-variants{display:flex;flex-direction:column;gap:4px}
.project-lawful-ebook-blogpost .dlbtn_alt{display:grid;grid-template-columns:24px auto 1fr auto;grid-column-gap:1rem;color:#ddd;background-color:#222;border:1px solid black;border-radius:4px;text-decoration:none;cursor:pointer;padding:6px;align-items:center}
.project-lawful-ebook-blogpost .dlbtn_alt:hover{background-color:#555}
.project-lawful-ebook-blogpost .dlbtn_alt svg{fill:#BBB}
.project-lawful-ebook-blogpost .dlbtn_alt .span_mb{font-size:.75em;grid-column:4}

View File

@ -28,4 +28,6 @@
@import 'styles_bookspanel';
@import 'styles_bfjoustrunner';
@import 'styles_befungerunner';
@import 'styles_befungerunner';
@import 'styles_projectlawfulebook.scss';

View File

@ -56,4 +56,4 @@
}
}
.blogcontent_euler, .blogcontent_markdown, .blogcontent_plain { width: 90%;}
.blogcontent_euler, .blogcontent_markdown, .blogcontent_plain, .blogcontent_aoclist, .blogcontent_html { width: 90%;}

View File

@ -0,0 +1,123 @@
@import 'styles_config';
.project-lawful-ebook-blogpost
{
code {
background-color: rgba(0, 0, 0, 0.1);
border-radius: 2px;
padding-left: 2px;
padding-right: 2px;
}
.variant {
display: grid;
grid-template-columns: repeat(6, auto) 1fr;
grid-template-rows: auto auto;
grid-column-gap: 1rem;
}
.variant h3 { grid-row: 1; grid-column: 1/-1; text-decoration: underline; }
.variant .dlbutton {
position: relative;
height: 100%;
padding: 0;
display: flex;
border:none;
}
.variant .dlbutton img {
opacity: 0;
pointer-events: none;
user-select: none;
}
.variant .dlbutton a {
display: grid;
grid-template-rows: 1fr auto auto;
grid-row-gap: 4px;
justify-content: center;
align-items: center;
justify-items: center;
padding: 0.5rem;
cursor: pointer;
text-decoration: none;
color: $COL_BUTTON_GENERIC_FG;
background-color: $COL_BUTTON_GENERIC_BG;
border-radius: 4px;
border: 1px solid black;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.variant .dlbutton a:hover {
background-color: $COL_BUTTON_GENERIC_HOVER;
}
.variant .dlbutton a .span_dl {
font-weight: bold;
font-size: 1.33em;
}
.variant .dlbutton a .span_mb {
font-size: 0.75em;
}
.variant .dlbutton svg {
fill: #BBB;
}
.variant img {
width: 100%;
border: 1px solid #555;
}
}
.project-lawful-ebook-blogpost
{
.variant.alt-variants {
display: flex;
flex-direction: column;
gap: 4px;
}
.dlbtn_alt {
display: grid;
grid-template-columns: 24px auto 1fr auto;
grid-column-gap: 1rem;
color: $COL_BUTTON_GENERIC_FG;
background-color: $COL_BUTTON_GENERIC_BG;
border: 1px solid black;
border-radius: 4px;
text-decoration: none;
cursor: pointer;
padding: 6px;
align-items: center;
}
.dlbtn_alt:hover {
background-color: $COL_BUTTON_GENERIC_HOVER;
}
.dlbtn_alt svg {
fill: #BBB;
}
.dlbtn_alt .span_mb {
font-size: 0.75em;
grid-column: 4;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

4
www/data/projectlawful/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
*.epub
*.mobi
*.zip
*.html

View File

View File

@ -19,7 +19,7 @@ $FRAME_OPTIONS->alternative_url = "/adventofcode/$year";
?>
<div class="boxedcontent blogcontent_plain">
<div class="boxedcontent blogcontent_aoclist">
<div style="position: relative;">
<a href="<?php echo $SITE->modules->AdventOfCode()->getGithubLink($year); ?>" style="position: absolute; top: 0; right: 0; border: 0;">

View File

@ -0,0 +1,35 @@
<?php
require_once (__DIR__ . '/../internals/website.php');
/** @var PageFrameOptions $FRAME_OPTIONS */ global $FRAME_OPTIONS;
/** @var URLRoute $ROUTE */ global $ROUTE;
/** @var Website $SITE */ global $SITE;
global $FRAGMENT_PARAM;
/** @var array $parameter */
$parameter = $FRAGMENT_PARAM;
?>
<?php
$post = $parameter['blogpost'];
?>
<div class="boxedcontent blogcontent_html">
<?php if (isset($post['extras']['gh_link'])): ?>
<div style="position: relative;">
<a href="<?= $post['extras']['gh_link'] ?>" style="position: absolute; top: 0; right: 0; border: 0;">
<img src="/data/images/blog/github_band.png" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_darkblue_121621.png">
</a>
</div>
<?php endif; ?>
<div class="bc_header">
<?php echo $post['date']; ?>
</div>
<div class="bc_data">
<?php echo $SITE->modules->Blog()->getPostFragment($post); ?>
</div>
</div>

View File

@ -49,6 +49,8 @@ $URL_RULES =
[ 'url' => ['admin'], 'target' => 'admin.php', 'options' => [ 'password' ], 'parameter' => [ ] ],
[ 'url' => ['blog','29','*','download','?{variant}'], 'target' => 'projectlawful_download.php', 'options' => [ ], 'parameter' => [ 'variant' => '%URL%' ], ],
[ 'url' => ['blog'], 'target' => 'blog_list.php', 'options' => [ ], 'parameter' => [ ], ],
[ 'url' => ['log'], 'target' => 'blog_list.php', 'options' => [ ], 'parameter' => [ ], ],
[ 'url' => ['blogpost', 'index'], 'target' => 'blog_list.php', 'options' => [ ], 'parameter' => [ ], ],
@ -66,7 +68,7 @@ $URL_RULES =
[ 'url' => ['adventofcode', '?{year}'], 'target' => 'adventofcode_year.php', 'options' => [ ], 'parameter' => [ 'year' => '%URL%' ], ],
[ 'url' => ['adventofcode', '?{year}', '?{day}'], 'target' => 'adventofcode_day.php', 'options' => [ ], 'parameter' => [ 'year' => '%URL%', 'day' => '%URL%' ], ],
[ 'url' => ['webapps'], 'target' => 'webapps_list.php', 'options' => [ ], 'parameter' => [ ], ],
[ 'url' => ['webapps'], 'target' => 'webapps_list.php', 'options' => [ ], 'parameter' => [ ], ],
[ 'url' => ['highscores', 'list.php'], 'target' => 'highscores_listentries.php', 'options' => [ 'http' ], 'parameter' => [ 'gameid' => '%GET%' ], ],
[ 'url' => ['highscores', 'list'], 'target' => 'highscores_listentries.php', 'options' => [ 'http' ], 'parameter' => [ 'gameid' => '%GET%' ], ],

View File

@ -75,6 +75,14 @@ class Fragments
]);
}
public function BlogviewHTML(array $blogpost)
{
return $this->evalFragment('BlogviewHTML', 'blogview_html.php',
[
'blogpost' => $blogpost,
]);
}
public function BlogviewEulerList(array $blogpost)
{
return $this->evalFragment('BlogviewEulerList', 'blogview_euler_list.php',

View File

@ -1,5 +1,7 @@
<?php
use internals\modules\ProjectLawful;
require_once 'website.php';
class Modules
@ -15,7 +17,8 @@ class Modules
/** @var WebApps|null */ private $webapps = null;
/** @var MikescherGitGraph|null */ private $extendedgitgraph = null;
/** @var Highscores|null */ private $highscores = null;
/** @var SelfTest|null */ private $selftest = null;
/** @var SelfTest|null */ private $selftest = null;
/** @var ProjectLawful|null */ private $projectlawful = null;
/** @var Website */
private $site;
@ -91,9 +94,15 @@ class Modules
return $this->highscores;
}
public function SelfTest(): SelfTest
{
if ($this->selftest === null) { require_once 'modules/selftest.php'; $this->selftest = new SelfTest(); }
return $this->selftest;
}
public function SelfTest(): SelfTest
{
if ($this->selftest === null) { require_once 'modules/selftest.php'; $this->selftest = new SelfTest(); }
return $this->selftest;
}
public function ProjectLawful(): ProjectLawful
{
if ($this->projectlawful === null) { require_once 'modules/projectlawful.php'; $this->projectlawful = new ProjectLawful($this->site); }
return $this->projectlawful;
}
}

View File

@ -118,9 +118,13 @@ class Blog implements IWebsiteModule
if (!file_exists($post['file_fragment'])) return ['result'=>'err', 'message' => 'Fragment not found ' . $post['fragment']];
} else if ($post['type'] === 'plain') {
} else if ($post['type'] === 'plain') {
if (!file_exists($post['file_fragment'])) return ['result'=>'err', 'message' => 'Fragment not found ' . $post['fragment']];
if (!file_exists($post['file_fragment'])) return ['result'=>'err', 'message' => 'Fragment not found ' . $post['fragment']];
} else if ($post['type'] === 'html') {
if (!file_exists($post['file_fragment'])) return ['result'=>'err', 'message' => 'Fragment not found ' . $post['fragment']];
} else if ($post['type'] === 'euler') {

View File

@ -0,0 +1,76 @@
<?php
namespace internals\modules;
use IWebsiteModule;
use PDO;
use Throwable;
use Website;
class ProjectLawful implements IWebsiteModule
{
/** @var Website */
private $site;
public $variants = [
'avatars-moreinfo' => 'project-lawful-avatars-moreinfo.epub',
'avatars' => 'project-lawful-avatars.epub',
'biggerhtml' => 'project-lawful-biggerhtml.epub',
'inline' => 'project-lawful-inline.epub',
'moreinfo' => 'project-lawful-moreinfo.epub',
'onlymainstory-avatars-moreinfo' => 'project-lawful-onlymainstory-avatars-moreinfo.epub',
'onlymainstory-avatars' => 'project-lawful-onlymainstory-avatars.epub',
'onlymainstory-biggerhtml' => 'project-lawful-onlymainstory-biggerhtml.epub',
'onlymainstory-inline' => 'project-lawful-onlymainstory-inline.epub',
'onlymainstory-moreinfo' => 'project-lawful-onlymainstory-moreinfo.epub',
'sfw-avatars-moreinfo' => 'project-lawful-sfw-avatars-moreinfo.epub',
'sfw-avatars' => 'project-lawful-sfw-avatars.epub',
'sfw-biggerhtml' => 'project-lawful-sfw-biggerhtml.epub',
'sfw-inline' => 'project-lawful-sfw-inline.epub',
'sfw-moreinfo' => 'project-lawful-sfw-moreinfo.epub',
];
public function __construct(Website $site)
{
$this->site = $site;
}
public function insertDownload($variant)
{
try
{
$this->site->modules->Database()->sql_query_assoc_prep('INSERT INTO projectlawful_downloadcounter (variant, ip, useragent) VALUES (:vr, :ip, :ua)',
[
[':vr', $variant, PDO::PARAM_STR],
[':ip', get_client_ip(), PDO::PARAM_STR],
[':ua', $_SERVER['HTTP_USER_AGENT'] ?? '', PDO::PARAM_STR],
]);
return true;
}
catch (Throwable $t)
{
return false;
}
}
public function listDownloadCounts()
{
return $this->site->modules->Database()->sql_query_assoc('SELECT variant, COUNT(*) AS `count` FROM projectlawful_downloadcounter GROUP BY variant ORDER BY variant');
}
public function variantExists(string $variant)
{
return isset($this->variants[$variant]);
}
public function checkConsistency()
{
foreach ($this->variants as $key => $val) {
$fn = __DIR__ . '/../../data/projectlawful/'.$val;
if (!file_exists($fn)) {
return ['result'=>'err', 'message' => 'File not found: ' . $fn];
}
}
return ['result' => 'ok', 'message' => ''];
}
}

View File

@ -8,30 +8,31 @@ class SelfTest implements IWebsiteModule
private const DISPLAY_NAMES =
[
'web::main' => 'Website (http)',
'web::programs' => 'Programs (http)',
'web::programs-ext' => 'Programs (links)',
'web::programs-dl' => 'Programs (download)',
'web::books' => 'Books (http)',
'web::blog' => 'Blog (http)',
'web::webapps' => 'WebApps (http)',
'web::euler' => 'Project Euler (http)',
'web::aoc' => 'Advent of Code (http)',
'api::default' => 'API',
'api::highscore' => 'Highscores API',
'modules::database' => 'Database',
'modules::blog' => 'Blog (data)',
'modules::euler' => 'Project Euler (data)',
'modules::books' => 'Books (data)',
'modules::extgitgraph' => 'ExtendedGitGraph (data)',
'modules::programs' => 'Programs (data)',
'modules::adventofcode' => 'Advent of Code (data)',
'modules::anstatistics' => 'AlephNote Stats (data)',
'modules::updateslog' => 'Program Updates (data)',
'modules::webapps' => 'Webapps (data)',
'modules::highscores' => 'Highscores (data)',
'egg::db-check' => 'ExtendedGitGraph (db-check)',
'backend::git' => 'Git Repository',
'web::main' => 'Website (http)',
'web::programs' => 'Programs (http)',
'web::programs-ext' => 'Programs (links)',
'web::programs-dl' => 'Programs (download)',
'web::books' => 'Books (http)',
'web::blog' => 'Blog (http)',
'web::webapps' => 'WebApps (http)',
'web::euler' => 'Project Euler (http)',
'web::aoc' => 'Advent of Code (http)',
'api::default' => 'API',
'api::highscore' => 'Highscores API',
'modules::database' => 'Database',
'modules::blog' => 'Blog (data)',
'modules::euler' => 'Project Euler (data)',
'modules::books' => 'Books (data)',
'modules::extgitgraph' => 'ExtendedGitGraph (data)',
'modules::programs' => 'Programs (data)',
'modules::adventofcode' => 'Advent of Code (data)',
'modules::anstatistics' => 'AlephNote Stats (data)',
'modules::updateslog' => 'Program Updates (data)',
'modules::webapps' => 'Webapps (data)',
'modules::highscores' => 'Highscores (data)',
'modules::projectlawful' => 'ProjectLawful-ebook (files)',
'egg::db-check' => 'ExtendedGitGraph (db-check)',
'backend::git' => 'Git Repository',
];
private $methods = [];
@ -51,9 +52,9 @@ class SelfTest implements IWebsiteModule
$this->addMethodPathStatus("web::main::about-2", 200, '/msmain/about');
$this->addMethodPathStatus("web::main::login-1", 200, '/login');
$this->addMethodPathStatus("web::main::404-1", 404, '/asdf');
$this->addHTTPSRedirect( "web::main::redirect-1", '');
$this->addHTTPSRedirect( "web::main::redirect-2", '/about');
$this->addHTTPSRedirect( "web::main::redirect-3", '/about');
$this->addHTTPSRedirect( "web::main::redirect-1", '');
$this->addHTTPSRedirect( "web::main::redirect-2", '/about');
$this->addHTTPSRedirect( "web::main::redirect-3", '/about');
$this->addMethodPathStatus( "web::programs::programs-list-1", 200, '/programs');
$this->addMethodPathStatus( "web::programs::programs-list-2", 200, '/programs/index');
@ -106,18 +107,19 @@ class SelfTest implements IWebsiteModule
$this->addMethodMultiPathStatus("web::aoc::aoc-redirect-1", 302, '{0}', function(){ return array_key_map(Website::inst()->modules->AdventOfCode()->listAllDays(), 'url-alternative'); });
$this->addMethodMultiPathStatus("web::aoc::aoc-redirect-1", 302, '/adventofcode/{0}', function(){ return Website::inst()->modules->AdventOfCode()->listYears(); });
$this->addCheckConsistency("modules::database::database-check-consistency", function(){ return Website::inst()->modules->Database(); });
$this->addCheckConsistency("modules::blog::blog-check-consistency", function(){ return Website::inst()->modules->Blog(); });
$this->addCheckConsistency("modules::euler::euler-check-consistency", function(){ return Website::inst()->modules->Euler(); });
$this->addCheckConsistency("modules::books::books-check-consistency", function(){ return Website::inst()->modules->Books(); });
$this->addCheckConsistency("modules::extgitgraph::extgitgraph-check-consistency", function(){ return Website::inst()->modules->ExtendedGitGraph(); });
$this->addCheckConsistency("modules::programs::programs-check-consistency", function(){ return Website::inst()->modules->Programs(); });
$this->addCheckConsistency("modules::books::books-check-consistency", function(){ return Website::inst()->modules->Books(); });
$this->addCheckConsistency("modules::adventofcode::adventofcode-check-consistency", function(){ return Website::inst()->modules->AdventOfCode(); });
$this->addCheckConsistency("modules::anstatistics::anstatistics-check-consistency", function(){ return Website::inst()->modules->AlephNoteStatistics(); });
$this->addCheckConsistency("modules::updateslog::updateslog-check-consistency", function(){ return Website::inst()->modules->UpdatesLog(); });
$this->addCheckConsistency("modules::webapps::webapps-check-consistency", function(){ return Website::inst()->modules->WebApps(); });
$this->addCheckConsistency("modules::highscores::highscores-check-consistency", function(){ return Website::inst()->modules->Highscores(); });
$this->addCheckConsistency("modules::database::database-check-consistency", function(){ return Website::inst()->modules->Database(); });
$this->addCheckConsistency("modules::blog::blog-check-consistency", function(){ return Website::inst()->modules->Blog(); });
$this->addCheckConsistency("modules::euler::euler-check-consistency", function(){ return Website::inst()->modules->Euler(); });
$this->addCheckConsistency("modules::books::books-check-consistency", function(){ return Website::inst()->modules->Books(); });
$this->addCheckConsistency("modules::extgitgraph::extgitgraph-check-consistency", function(){ return Website::inst()->modules->ExtendedGitGraph(); });
$this->addCheckConsistency("modules::programs::programs-check-consistency", function(){ return Website::inst()->modules->Programs(); });
$this->addCheckConsistency("modules::books::books-check-consistency", function(){ return Website::inst()->modules->Books(); });
$this->addCheckConsistency("modules::adventofcode::adventofcode-check-consistency", function(){ return Website::inst()->modules->AdventOfCode(); });
$this->addCheckConsistency("modules::anstatistics::anstatistics-check-consistency", function(){ return Website::inst()->modules->AlephNoteStatistics(); });
$this->addCheckConsistency("modules::updateslog::updateslog-check-consistency", function(){ return Website::inst()->modules->UpdatesLog(); });
$this->addCheckConsistency("modules::webapps::webapps-check-consistency", function(){ return Website::inst()->modules->WebApps(); });
$this->addCheckConsistency("modules::highscores::highscores-check-consistency", function(){ return Website::inst()->modules->Highscores(); });
$this->addCheckConsistency("modules::projectlawful::projectlawful-check-consistency", function(){ return Website::inst()->modules->ProjectLawful(); });
$this->addLambdaStatus("egg::db-check::check-db-consistency", function(){ return Website::inst()->modules->ExtendedGitGraph()->checkDatabaseConsistency(); });

View File

@ -92,6 +92,18 @@ $connected = true; try { $SITE->modules->Database(); } catch (Exception $e) { $c
<!-- - - - - - - - - - - - - - - - - - - - - -->
<div class="boxedcontent">
<div class="bc_header">Project Lawful ebook (download count)</div>
<div class="bc_data keyvaluelist kvl_250">
<?php foreach ($SITE->modules->ProjectLawful()->listDownloadCounts() as $dlc): ?>
<div><span><?php echo $dlc['variant']; ?>:</span> <span><?php echo $dlc['count']; ?></span></div>
<?php endforeach; ?>
</div>
</div>
<!-- - - - - - - - - - - - - - - - - - - - - -->
<div class="boxedcontent">
<div class="bc_header">ExtendedGitGraph</div>

View File

@ -39,7 +39,11 @@ else
{
echo $SITE->fragments->BlogviewMarkdown($post);
}
elseif ($post['type'] === 'euler')
elseif ($post['type'] === 'html')
{
echo $SITE->fragments->BlogviewHTML($post);
}
elseif ($post['type'] === 'euler')
{
if ($subview === '') echo $SITE->fragments->BlogviewEulerList($post);
else echo $SITE->fragments->BlogviewEulerSingle($post, $subview);

View File

@ -51,7 +51,7 @@ $FRAME_OPTIONS->activeHeader = 'programs';
{
if ($xurl['alert'] === null)
{
echo '<a class="iconbutton '.$xurl['css'].'" href="'.$xurl['href'].'">';
echo '<a rel="nofollow" class="iconbutton '.$xurl['css'].'" href="'.$xurl['href'].'">';
echo '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24">';
echo '<use xlink:href="/data/images/icons.svg#'.$xurl['svg'].'"/>';
echo '</svg>';

View File

@ -0,0 +1,18 @@
<?php
require_once (__DIR__ . '/../internals/website.php');
/** @var PageFrameOptions $FRAME_OPTIONS */ global $FRAME_OPTIONS;
/** @var URLRoute $ROUTE */ global $ROUTE;
/** @var Website $SITE */ global $SITE;
$variant = $ROUTE->parameter['variant'];
if (!$SITE->modules->ProjectLawful()->variantExists($variant))
{
$FRAME_OPTIONS->setForced404('epub file not found');
return;
}
$SITE->modules->ProjectLawful()->insertDownload($variant);
$FRAME_OPTIONS->setForcedRedirect('/data/projectlawful/project-lawful-' . $variant . '.epub');

View File

@ -2,32 +2,33 @@
return
[
[ 'id' => 5, 'date' => '2009-04-08', 'visible' => true, 'title' => 'Beginning the log', 'fragment' => 'initial.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 6, 'date' => '2009-05-01', 'visible' => false, 'title' => 'Mess with the best ...', 'fragment' => 'hack.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 4, 'date' => '2009-06-22', 'visible' => true, 'title' => 'New Layout!', 'fragment' => 'newlayout.txt', 'type' => 'plain', 'cat' => 'log' ],
[ 'id' => 10, 'date' => '2009-06-28', 'visible' => true, 'title' => '"FUN" update', 'fragment' => 'funupdate.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 11, 'date' => '2009-07-05', 'visible' => true, 'title' => 'New Download: LAN Control 2.0', 'fragment' => 'lancontrol.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 12, 'date' => '2009-09-07', 'visible' => false, 'title' => 'Airline BSOD', 'fragment' => 'bsod.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 13, 'date' => '2009-11-22', 'visible' => true, 'title' => 'Spammers gonna spam', 'fragment' => 'spammers.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 15, 'date' => '2012-04-14', 'visible' => true, 'title' => 'New Download: Infinity Tournament', 'fragment' => 'inftournament.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 16, 'date' => '2012-05-27', 'visible' => true, 'title' => 'New Download: Borderline Defense', 'fragment' => 'borderlinedef.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 17, 'date' => '2012-05-28', 'visible' => true, 'title' => 'Big clean up', 'fragment' => 'cleanup.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 18, 'date' => '2013-01-03', 'visible' => true, 'title' => 'New Download: Crystal Grid', 'fragment' => 'crystalgrid.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 2, 'date' => '2014-05-14', 'visible' => true, 'title' => 'Let\'s do Befunge-93', 'fragment' => 'befunge93.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 14, 'date' => '2014-06-30', 'visible' => true, 'title' => 'Language changes', 'fragment' => 'language.txt', 'type' => 'plain', 'cat' => 'log' ],
[ 'id' => 1, 'date' => '2014-07-10', 'visible' => true, 'title' => 'Project Euler with Befunge', 'fragment' => '', 'type' => 'euler', 'cat' => 'blog' ],
[ 'id' => 3, 'date' => '2014-07-15', 'visible' => true, 'title' => '.Net format specifier Cheat Sheet', 'fragment' => 'net_format_spec.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 19, 'date' => '2014-08-04', 'visible' => true, 'title' => 'I am Number Four', 'fragment' => 'v4.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 7, 'date' => '2014-09-28', 'visible' => true, 'title' => 'My BFJoust arena and battle-bot', 'fragment' => 'bfjoust.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 8, 'date' => '2014-11-05', 'visible' => true, 'title' => 'Rapla Enhancement Script', 'fragment' => 'rapla_css.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 20, 'date' => '2015-01-09', 'visible' => true, 'title' => 'More Befunge with Project Euler', 'fragment' => 'more_euler.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 9, 'date' => '2016-10-22', 'visible' => true, 'title' => 'A complete sudoku solver in Befunge-93', 'fragment' => 'sudoku_befunge.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 21, 'date' => '2018-01-02', 'visible' => true, 'title' => 'A simple javascript befunge-93 runner', 'fragment' => 'js_befrunner.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 22, 'date' => '2018-02-06', 'visible' => true, 'title' => 'Homepage iteration 5', 'fragment' => 'v5.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 23, 'date' => '2018-12-01', 'visible' => true, 'title' => 'Advent of Code 2018 - C#', 'fragment' => 'aoc2018.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2018'] ],
[ 'id' => 24, 'date' => '2019-12-01', 'visible' => true, 'title' => 'Advent of Code 2019 - Typescript', 'fragment' => 'aoc2019.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2019'] ],
[ 'id' => 25, 'date' => '2020-01-09', 'visible' => true, 'title' => 'Advent of Code 2017 - Python3', 'fragment' => 'aoc2017.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2017'] ],
[ 'id' => 26, 'date' => '2020-12-01', 'visible' => true, 'title' => 'Advent of Code 2020 - Rust', 'fragment' => 'aoc2020.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2020'] ],
[ 'id' => 27, 'date' => '2021-11-25', 'visible' => true, 'title' => 'Advent of Code 2021 - Go', 'fragment' => 'aoc2021.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2021'] ],
[ 'id' => 28, 'date' => '2022-12-01', 'visible' => true, 'title' => 'Advent of Code 2022 - Nim', 'fragment' => 'aoc2022.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2022'] ],
[ 'id' => 5, 'date' => '2009-04-08', 'visible' => true, 'title' => 'Beginning the log', 'fragment' => 'initial.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 6, 'date' => '2009-05-01', 'visible' => false, 'title' => 'Mess with the best ...', 'fragment' => 'hack.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 4, 'date' => '2009-06-22', 'visible' => true, 'title' => 'New Layout!', 'fragment' => 'newlayout.txt', 'type' => 'plain', 'cat' => 'log' ],
[ 'id' => 10, 'date' => '2009-06-28', 'visible' => true, 'title' => '"FUN" update', 'fragment' => 'funupdate.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 11, 'date' => '2009-07-05', 'visible' => true, 'title' => 'New Download: LAN Control 2.0', 'fragment' => 'lancontrol.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 12, 'date' => '2009-09-07', 'visible' => false, 'title' => 'Airline BSOD', 'fragment' => 'bsod.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 13, 'date' => '2009-11-22', 'visible' => true, 'title' => 'Spammers gonna spam', 'fragment' => 'spammers.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 15, 'date' => '2012-04-14', 'visible' => true, 'title' => 'New Download: Infinity Tournament', 'fragment' => 'inftournament.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 16, 'date' => '2012-05-27', 'visible' => true, 'title' => 'New Download: Borderline Defense', 'fragment' => 'borderlinedef.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 17, 'date' => '2012-05-28', 'visible' => true, 'title' => 'Big clean up', 'fragment' => 'cleanup.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 18, 'date' => '2013-01-03', 'visible' => true, 'title' => 'New Download: Crystal Grid', 'fragment' => 'crystalgrid.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 2, 'date' => '2014-05-14', 'visible' => true, 'title' => 'Let\'s do Befunge-93', 'fragment' => 'befunge93.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 14, 'date' => '2014-06-30', 'visible' => true, 'title' => 'Language changes', 'fragment' => 'language.txt', 'type' => 'plain', 'cat' => 'log' ],
[ 'id' => 1, 'date' => '2014-07-10', 'visible' => true, 'title' => 'Project Euler with Befunge', 'fragment' => '', 'type' => 'euler', 'cat' => 'blog' ],
[ 'id' => 3, 'date' => '2014-07-15', 'visible' => true, 'title' => '.Net format specifier Cheat Sheet', 'fragment' => 'net_format_spec.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 19, 'date' => '2014-08-04', 'visible' => true, 'title' => 'I am Number Four', 'fragment' => 'v4.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 7, 'date' => '2014-09-28', 'visible' => true, 'title' => 'My BFJoust arena and battle-bot', 'fragment' => 'bfjoust.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 8, 'date' => '2014-11-05', 'visible' => true, 'title' => 'Rapla Enhancement Script', 'fragment' => 'rapla_css.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 20, 'date' => '2015-01-09', 'visible' => true, 'title' => 'More Befunge with Project Euler', 'fragment' => 'more_euler.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 9, 'date' => '2016-10-22', 'visible' => true, 'title' => 'A complete sudoku solver in Befunge-93', 'fragment' => 'sudoku_befunge.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 21, 'date' => '2018-01-02', 'visible' => true, 'title' => 'A simple javascript befunge-93 runner', 'fragment' => 'js_befrunner.md', 'type' => 'markdown', 'cat' => 'blog' ],
[ 'id' => 22, 'date' => '2018-02-06', 'visible' => true, 'title' => 'Homepage iteration 5', 'fragment' => 'v5.md', 'type' => 'markdown', 'cat' => 'log' ],
[ 'id' => 23, 'date' => '2018-12-01', 'visible' => true, 'title' => 'Advent of Code 2018 - C#', 'fragment' => 'aoc2018.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2018'] ],
[ 'id' => 24, 'date' => '2019-12-01', 'visible' => true, 'title' => 'Advent of Code 2019 - Typescript', 'fragment' => 'aoc2019.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2019'] ],
[ 'id' => 25, 'date' => '2020-01-09', 'visible' => true, 'title' => 'Advent of Code 2017 - Python3', 'fragment' => 'aoc2017.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2017'] ],
[ 'id' => 26, 'date' => '2020-12-01', 'visible' => true, 'title' => 'Advent of Code 2020 - Rust', 'fragment' => 'aoc2020.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2020'] ],
[ 'id' => 27, 'date' => '2021-11-25', 'visible' => true, 'title' => 'Advent of Code 2021 - Go', 'fragment' => 'aoc2021.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2021'] ],
[ 'id' => 28, 'date' => '2022-12-01', 'visible' => true, 'title' => 'Advent of Code 2022 - Nim', 'fragment' => 'aoc2022.md', 'type' => 'aoc', 'cat' => 'blog', 'extras' => ['aoc:year' => '2022'] ],
[ 'id' => 29, 'date' => '2023-09-11', 'visible' => true, 'title' => 'Project Lawful ebook', 'fragment' => 'proj_lawful_ebook.html', 'type' => 'html', 'cat' => 'blog', 'extras' => ['gh_link' => 'https://github.com/Mikescher/ProjectLawfulEbook'] ],
];

View File

@ -0,0 +1,181 @@
<div class="project-lawful-ebook-blogpost">
This is my attempt at creating an ebook version of the <a href="https://www.projectlawful.com/">project lawful</a> story/glowfic.
<ul>
<li>I used the cache file of <a href="https://github.com/QuartzLibrary/glowpub">glowpub</a> as a basis, because I didn't want to write my own scraper (thanks to <a href="https://github.com/QuartzLibrary">github.com/QuartzLibrary</a>)</li>
<li>Then I <a href="https://github.com/Mikescher/ProjectLawfulEbook">hacked together a bit of C# code</a> to generate epub files</li>
<li>All external images (and optionally avatars) are included in the epub</li>
<li>I went through all the avatar files and made them square images (simplified my css, and looked better - most were already squares or almost-squares)</li>
<li>Various properties of the final file/layout can be changed, I generated the variants I found useful, but if anyone wants something special it should be enough to close the repo, edit <code>Program.cs</code> and run <code>make</code></li>
</ul>
<h2># Variants</h2>
I recommend either the <code>project-lawful-inline.epub</code> or the <code>project-lawful-avatars.epub</code> variants
(the <code>*-avatars</code> version contains the post profile-pictures, but is bigger and takes longer to open on older devices).
There are 3 different versions of the actual **content** of the files:
<ul>
<li>the normal one includes all threads from <a href="https://www.projectlawful.com/">projectlawful.com</a>, including sandboxes and lectures. If there is a SFW version of a thread, it is not included (the inline/nsfw is included instead)</li>
<li>the <code>*-sfw-*</code> versions have the NSFW threads replaced wih their SFW counterparts<br/><i>(replaces threads 5310 with 5403, 5610 with 5618, 5638 with 5671)</i></li>
<li>the <code>*-onlymainstory-*</code> versions do not include the sandbox threads or the (optional) lecture threads<br/><i>(removes threads 5775, 5778, 5785, 5826, 5864, 5880, 6029, 6124)</i></li>
</ul>
There are also 5 different layout options (that I code-built) for every version above:
<ul>
<li>the <code>*-inline</code> variant only includes the character-name of each post, and (if possible) the character-name is set inline with the first paragraph</li>
<li>the <code>*-moreinfo</code> variant also includes the alignment text of each post/character after the character-name.</li>
<li>the <code>*-avatars</code> variant show the character avatar beside each post. This forces us to include all 600-ish avatars in the epub file, which increases filesize and loading time</li>
<li>the <code>*-avatars-moreinfo</code> also includes the alignment text (additionally to the avatar). Here the avatar/character-name/alignment-text are also no longer inlined, but get their own paragraph at the start of every post.</li>
<li>the <code>*-biggerhtml</code> variant looks the same as <code>*-inline</code>, but internally every chapter is a single big file (instead of multiple split files), this makes the initial loading of the epub slower, but codevents a forced page-break after every 128th post.</li>
</ul>
As said above, it should be possible to create epub's with other combination of options (see <code>Program.cs</code> and <code>Options.cs</code>)
<h2># Tested on</h2>
<ul>
<li>Kindle Paperwhite /w KOReader</li>
<li>Foliate</li>
<li>epub.js</li>
<li>Calibre Ebook viewer</li>
</ul>
<h2># Download</h2>
<div class="variant">
<h3>project-lawful-inline.epub</h3>
<div class="dlbutton">
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-01.jpeg">
<a rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/inline">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">Download</span>
<span class="span_mb">(30.9 MB)</span>
</a>
</div>
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-01.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-02.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-03.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-04.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-inline-05.jpeg">
</div>
<div class="variant">
<h3>project-lawful-avatars.epub</h3>
<div class="dlbutton">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-01.jpeg">
<a rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/avatars">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">Download</span>
<span class="span_mb">(37.5 MB)</span>
</a>
</div>
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-01.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-02.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-03.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-04.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-05.jpeg">
</div>
<div class="variant">
<h3>project-lawful-avatars-moreinfo.epub</h3>
<div class="dlbutton">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-01.jpeg">
<a rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/avatars-moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">Download</span>
<span class="span_mb">(37.6 MB)</span>
</a>
</div>
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-01.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-02.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-03.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-04.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-avatars-moreinfo-05.jpeg">
</div>
<div class="variant">
<h3>project-lawful-moreinfo.epub</h3>
<div class="dlbutton">
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-01.jpeg">
<a rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">Download</span>
<span class="span_mb">(30.9 MB)</span>
</a>
</div>
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-01.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-02.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-03.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-04.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-moreinfo-05.jpeg">
</div>
<div class="variant">
<h3>project-lawful-biggerhtml.epub</h3>
<div class="dlbutton">
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-01.jpeg">
<a rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/biggerhtml">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">Download</span>
<span class="span_mb">(30.6 MB)</span>
</a>
</div>
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-01.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-02.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-03.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-04.jpeg">
<img alt="page preview" src="/data/images/blog/ple-scaled-biggerhtml-05.jpeg">
</div>
<div class="variant alt-variants">
<h3>Alternatives (SFW)</h3>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/sfw-inline">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-sfw-inline</span><span class="span_mb">(30.8 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/sfw-avatars">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-sfw-avatars</span><span class="span_mb">(37.4 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/sfw-avatars-moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-sfw-avatars-moreinfo</span><span class="span_mb">(37.5 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/sfw-moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-sfw-moreinfo</span><span class="span_mb">(30.8 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/sfw-biggerhtml">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-sfw-biggerhtml</span><span class="span_mb">(30.5 MB)</span>
</a>
</div>
<div class="variant alt-variants">
<h3>Alternatives (Only-Main-Story)</h3>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/onlymainstory-inline">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-onlymainstory-inline</span><span class="span_mb">(30.5 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/onlymainstory-avatars">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-onlymainstory-avatars</span><span class="span_mb">(37.2 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/onlymainstory-avatars-moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-onlymainstory-avatars-moreinfo</span><span class="span_mb">(37.3 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/onlymainstory-moreinfo">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-onlymainstory-moreinfo</span><span class="span_mb">(30.6 MB)</span>
</a>
<a class="dlbtn_alt" rel="nofollow" href="/blog/29/Project_Lawful_ebook/download/onlymainstory-biggerhtml">
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 24 24"><use xlink:href="/data/images/icons.svg#download"/></svg>
<span class="span_dl">project-lawful-onlymainstory-biggerhtml</span><span class="span_mb">(30.2 MB)</span>
</a>
</div>
</div>